PHPのオブジェクト指向では、あるクラスの性質や処理を、別のクラスに引き継がせることができます。
この仕組みを継承といいます。
継承元になるクラスを親クラス、継承して作られるクラスを子クラスと呼びます。
たとえば、「管理者ユーザー」は「ユーザー」の一種です。
このような関係は、継承で表現できます。
class User
{
public function login(): void
{
echo 'ログインしました';
}
}
class AdminUser extends User
{
public function deletePost(): void
{
echo '投稿を削除しました';
}
}
$admin = new AdminUser();
$admin->login(); // 親クラスのメソッドを使える
$admin->deletePost(); // 子クラス独自のメソッドを使える
AdminUser クラスは User クラスを継承しているため、User クラスに定義された login() メソッドを使えます。
PHPでは、クラスを継承するときに extends を使います。
class 子クラス名 extends 親クラス名
{
// 子クラス独自の処理
}
PHPで継承を使うメリット
共通処理をまとめられる
継承を使うと、複数のクラスで共通する処理を親クラスにまとめられます。
class User
{
public function login(): void
{
echo 'ログインしました';
}
public function logout(): void
{
echo 'ログアウトしました';
}
}
class AdminUser extends User
{
}
class MemberUser extends User
{
}
この例では、AdminUser も MemberUser も User を継承しているため、login() や logout() を使えます。
同じ処理を何度も書く必要がないため、コードの重複を減らせます。
子クラスごとに処理を変えられる
継承では、親クラスのメソッドを子クラスで上書きできます。
これをオーバーライドといいます。
class User
{
public function getRole(): string
{
return '一般ユーザー';
}
}
class AdminUser extends User
{
public function getRole(): string
{
return '管理者';
}
}
$user = new User();
$admin = new AdminUser();
echo $user->getRole(); // 一般ユーザー
echo $admin->getRole(); // 管理者
同じ getRole() というメソッド名でも、通常のユーザーと管理者ユーザーで異なる結果を返せます。
親クラスの型で子クラスを扱える
継承を使うと、親クラスの型で子クラスのオブジェクトを扱えます。
class User
{
public function getName(): string
{
return '田中';
}
}
class AdminUser extends User
{
}
function showUserName(User $user): void
{
echo $user->getName();
}
$admin = new AdminUser();
showUserName($admin);
showUserName() の引数は User 型ですが、AdminUser は User を継承しているため渡すことができます。
このように、親クラスの型で複数の子クラスを扱えるのも、継承の重要な特徴です。
継承で引き継がれるもの
publicとprotectedは子クラスで利用できる
PHPでは、子クラスは親クラスの public や protected のプロパティ・メソッド・定数を利用できます。
class User
{
public string $name = '田中';
protected string $role = 'user';
public function getName(): string
{
return $this->name;
}
protected function getRole(): string
{
return $this->role;
}
}
class AdminUser extends User
{
public function showRole(): void
{
echo $this->getRole();
}
}
$admin = new AdminUser();
echo $admin->name; // publicなので外部からアクセスできる
echo $admin->getName(); // publicメソッドなので外部から呼び出せる
$admin->showRole(); // 子クラス内からprotectedメソッドを使える
public はクラスの外部からもアクセスできます。
一方、protected はクラスの外部からはアクセスできませんが、自分のクラスと子クラスの中から利用できます。
privateは子クラスから直接アクセスできない
private は、定義されたクラスの内部からしか直接アクセスできません。
class User
{
private string $password = 'secret';
}
class AdminUser extends User
{
public function showPassword(): void
{
echo $this->password;
}
}
$admin = new AdminUser();
$admin->showPassword();
このコードは正しく動きません。
$password は User クラスの private プロパティなので、子クラスである AdminUser から直接アクセスできないためです。
ただし、親クラスの public や protected メソッドを通じて、親クラス内の private プロパティを扱うことはできます。
class User
{
private string $name = '田中';
public function getName(): string
{
return $this->name;
}
}
class AdminUser extends User
{
public function showName(): void
{
echo $this->getName();
}
}
$admin = new AdminUser();
$admin->showName(); // 田中
この例では、AdminUser から $name に直接アクセスしているわけではありません。
親クラスの getName() メソッドを呼び出し、そのメソッド内で private プロパティを扱っています。
アクセス修飾子の違い
継承を理解するうえで、public、protected、private の違いは非常に重要です。
| 修飾子 | クラス外部からアクセス | 子クラスからアクセス | 主な用途 |
|---|---|---|---|
public | できる | できる | 外部から呼び出したいメソッド |
protected | できない | できる | 子クラスで使わせたい処理や値 |
private | できない | 直接はできない | そのクラス内だけで管理したい値や処理 |
実務では、プロパティを安易に public にするよりも、private にして getter や setter を用意する設計がよく使われます。
class User
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
このようにすると、外部から直接プロパティを書き換えられないため、クラス内部の状態を安全に管理しやすくなります。
メソッドのオーバーライド
親クラスのメソッドを子クラスで上書きする
子クラスでは、親クラスで定義されたメソッドを同じ名前で定義し直すことができます。
これをオーバーライドといいます。
class User
{
public function getDashboardUrl(): string
{
return '/mypage';
}
}
class AdminUser extends User
{
public function getDashboardUrl(): string
{
return '/admin';
}
}
$user = new User();
$admin = new AdminUser();
echo $user->getDashboardUrl(); // /mypage
echo $admin->getDashboardUrl(); // /admin
通常ユーザーは /mypage、管理者ユーザーは /admin に移動させる、といった処理を表現できます。
オーバーライドでは型の互換性が必要
親クラスのメソッドをオーバーライドするときは、引数や戻り値の型に互換性が必要です。
次のコードは正しくありません。
class User
{
public function getRole(): string
{
return 'user';
}
}
class AdminUser extends User
{
public function getRole(): array
{
return ['admin'];
}
}
親クラスの getRole() は string を返すのに、子クラスでは array を返しているため、互換性がありません。
正しくは次のようにします。
class AdminUser extends User
{
public function getRole(): string
{
return 'admin';
}
}
戻り値の型や引数の型は、親クラスのメソッドと矛盾しないように定義する必要があります。
parent::で親クラスのメソッドを呼び出す
子クラスでメソッドを上書きしながら、親クラスの処理も使いたい場合は parent:: を使います。
class User
{
public function login(): void
{
echo 'ログイン処理を実行しました。';
}
}
class AdminUser extends User
{
public function login(): void
{
parent::login();
echo '管理者画面へ移動しました。';
}
}
$admin = new AdminUser();
$admin->login();
実行結果は次のようになります。
ログイン処理を実行しました。管理者画面へ移動しました。
parent::login() によって、親クラスの login() を呼び出しています。
コンストラクタの継承
子クラスにコンストラクタがなければ親クラスのコンストラクタが使われる
コンストラクタとは、オブジェクトを作成したときに自動で実行されるメソッドです。
PHPでは __construct() を使います。
class User
{
protected string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
class AdminUser extends User
{
}
$admin = new AdminUser('佐藤');
echo $admin->getName(); // 佐藤
AdminUser クラスにはコンストラクタがありません。
この場合、親クラスである User のコンストラクタが使われます。
子クラスでコンストラクタを書く場合はparent::__construct()を呼ぶ
子クラスで独自のコンストラクタを定義すると、親クラスのコンストラクタは自動では呼ばれません。
そのため、親クラスの初期化処理も実行したい場合は parent::__construct() を明示的に呼び出します。
class User
{
protected string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
class AdminUser extends User
{
private int $level;
public function __construct(string $name, int $level)
{
parent::__construct($name);
$this->level = $level;
}
public function showInfo(): void
{
echo "{$this->name}さんの管理者レベルは{$this->level}です";
}
}
$admin = new AdminUser('鈴木', 5);
$admin->showInfo();
この例では、親クラスの $name は User のコンストラクタで初期化し、子クラス独自の $level は AdminUser のコンストラクタで初期化しています。
親クラスのコンストラクタがprivateの場合は注意する
親クラスのコンストラクタが private の場合、子クラスから通常の形で利用することはできません。
class User
{
private function __construct()
{
}
}
class AdminUser extends User
{
}
このような設計は、継承して自由にインスタンスを作る用途には向きません。
private コンストラクタは、シングルトンやファクトリメソッドなど、特別な設計で使われることがあります。
初心者のうちは、継承させたいクラスのコンストラクタは public または必要に応じて protected にすると理解しやすいです。
abstractを使った抽象クラス
abstract classとは
abstract を使うと、直接インスタンス化できないクラスを作れます。
このようなクラスを抽象クラスといいます。
抽象クラスは、共通処理を持たせながら、一部の処理を子クラスに実装させたい場合に使います。
abstract class User
{
protected string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getProfile(): string
{
return "名前:{$this->name}";
}
abstract public function getRole(): string;
}
getProfile() には具体的な処理があります。
一方、getRole() は abstract メソッドなので、処理の中身がありません。
抽象メソッドは子クラスで実装する
抽象メソッドを持つクラスを継承した場合、子クラス側でそのメソッドを実装する必要があります。
class AdminUser extends User
{
public function getRole(): string
{
return '管理者';
}
}
class MemberUser extends User
{
public function getRole(): string
{
return '会員';
}
}
$admin = new AdminUser('佐藤');
$member = new MemberUser('田中');
echo $admin->getProfile(); // 名前:佐藤
echo $admin->getRole(); // 管理者
echo $member->getProfile(); // 名前:田中
echo $member->getRole(); // 会員
抽象クラスを使うと、「共通する処理は親クラスに置き、子クラスごとに異なる処理は子クラスで実装する」という設計ができます。
finalで継承やオーバーライドを禁止する
final classは継承できない
クラスに final を付けると、そのクラスは継承できなくなります。
final class User
{
public function login(): void
{
echo 'ログインしました';
}
}
class AdminUser extends User
{
}
このコードはエラーになります。
User クラスが final なので、AdminUser クラスは User を継承できません。
final class は、「このクラスはこれ以上拡張させたくない」という場合に使います。
final methodはオーバーライドできない
メソッドに final を付けると、そのメソッドは子クラスでオーバーライドできなくなります。
class User
{
final public function login(): void
{
echo 'ログインしました';
}
}
class AdminUser extends User
{
public function login(): void
{
echo '管理者としてログインしました';
}
}
このコードもエラーになります。
login() メソッドが final なので、子クラスで上書きできません。
重要な処理や、変更されると困る処理には final を使うことがあります。
staticメソッドと継承
staticメソッドも継承できる
static メソッドは、オブジェクトを作らずにクラスから直接呼び出せるメソッドです。
class User
{
public static function getType(): string
{
return 'user';
}
}
class AdminUser extends User
{
}
echo AdminUser::getType(); // user
AdminUser は User を継承しているため、User の static メソッドを呼び出せます。
子クラスでオーバーライドすることもできます。
class User
{
public static function getType(): string
{
return 'user';
}
}
class AdminUser extends User
{
public static function getType(): string
{
return 'admin';
}
}
echo AdminUser::getType(); // admin
self::とstatic::の違い
継承で static メソッドを扱うときは、self:: と static:: の違いに注意が必要です。
self:: は、そのメソッドが定義されているクラスを指します。
class User
{
public static function getType(): string
{
return 'user';
}
public static function showType(): string
{
return self::getType();
}
}
class AdminUser extends User
{
public static function getType(): string
{
return 'admin';
}
}
echo AdminUser::showType(); // user
AdminUser::showType() と呼んでいますが、showType() の中では self::getType() を使っています。
そのため、User::getType() が呼ばれます。
一方、static:: を使うと、実際に呼び出されたクラスを考慮します。
class User
{
public static function getType(): string
{
return 'user';
}
public static function showType(): string
{
return static::getType();
}
}
class AdminUser extends User
{
public static function getType(): string
{
return 'admin';
}
}
echo AdminUser::showType(); // admin
このような仕組みを遅延静的束縛といいます。
static:: は、継承先のクラスごとに処理を切り替えたい場合に便利です。
継承とポリモーフィズム
ポリモーフィズムとは
ポリモーフィズムとは、同じメソッド呼び出しでも、オブジェクトの種類によって異なる処理を実行できる仕組みです。
abstract class User
{
abstract public function getRole(): string;
}
class AdminUser extends User
{
public function getRole(): string
{
return '管理者';
}
}
class MemberUser extends User
{
public function getRole(): string
{
return '会員';
}
}
function showRole(User $user): void
{
echo $user->getRole();
}
showRole(new AdminUser()); // 管理者
showRole(new MemberUser()); // 会員
showRole() は User 型の引数を受け取ります。
しかし、実際には AdminUser も MemberUser も渡せます。
どちらも User を継承しているためです。
そして、呼び出される getRole() の処理は、実際に渡されたオブジェクトのクラスによって変わります。
interfaceでポリモーフィズムを実現することもある
ポリモーフィズムは継承だけでなく、interface を使って実現することもできます。
interface RoleProvider
{
public function getRole(): string;
}
class AdminUser implements RoleProvider
{
public function getRole(): string
{
return '管理者';
}
}
class MemberUser implements RoleProvider
{
public function getRole(): string
{
return '会員';
}
}
function showRole(RoleProvider $user): void
{
echo $user->getRole();
}
showRole(new AdminUser());
showRole(new MemberUser());
共通の親クラスを持たせたい場合は継承が向いています。
一方で、「同じメソッドを持っていることだけを保証したい」場合は、interface の方が適していることもあります。
PHPは多重継承できない
1つのクラスが継承できる親クラスは1つだけ
PHPでは、1つのクラスが複数の親クラスを同時に継承することはできません。
次のような書き方はできません。
class A
{
}
class B
{
}
class C extends A, B
{
}
PHPのクラス継承は、基本的に単一継承です。
正しくは次のように、1つの親クラスだけを指定します。
class A
{
}
class C extends A
{
}
複数の機能を使い回したい場合はtraitやinterfaceを使う
複数のクラスで同じ処理を使い回したい場合は、trait を使えます。
また、複数のルールをクラスに持たせたい場合は、複数の interface を実装できます。
interface LoggerInterface
{
public function log(string $message): void;
}
interface NotifierInterface
{
public function notify(string $message): void;
}
class UserService implements LoggerInterface, NotifierInterface
{
public function log(string $message): void
{
echo "ログ:{$message}";
}
public function notify(string $message): void
{
echo "通知:{$message}";
}
}
クラスの継承は1つだけですが、インターフェースは複数実装できます。
継承とtraitの違い
traitは処理を使い回すための仕組み
trait は、複数のクラスで同じメソッドを使い回したいときに使います。
trait TimestampTrait
{
public function getCurrentTime(): string
{
return date('Y-m-d H:i:s');
}
}
class Article
{
use TimestampTrait;
}
class Comment
{
use TimestampTrait;
}
$article = new Article();
echo $article->getCurrentTime();
$comment = new Comment();
echo $comment->getCurrentTime();
Article と Comment は親子関係ではありません。
しかし、どちらも TimestampTrait のメソッドを使えます。
継承は親子関係、traitは機能の共有
継承と trait の違いは次のとおりです。
| 仕組み | 主な目的 | 例 |
|---|---|---|
| 継承 | 親子関係を表す | AdminUser extends User |
| trait | 処理を複数クラスで使い回す | use TimestampTrait |
単にコードを使い回したいだけなら、継承よりも trait やコンポジションの方が適している場合があります。
継承とinterfaceの違い
interfaceは実装すべきルールを定義する
interface は、「このメソッドを必ず実装してください」というルールを定義する仕組みです。
interface Authenticatable
{
public function login(): void;
}
class User implements Authenticatable
{
public function login(): void
{
echo 'ログインしました';
}
}
Authenticatable インターフェースでは、login() メソッドを持つことを決めています。
具体的な処理は、implements したクラス側で実装します。
継承、interface、traitの使い分け
継承、interface、trait は似ている部分もありますが、目的が異なります。
| 仕組み | 目的 |
|---|---|
| 継承 | 親クラスの性質や処理を引き継ぐ |
| interface | 実装すべきメソッドのルールを決める |
| trait | メソッドの実装を複数のクラスで使い回す |
たとえば、管理者ユーザーが通常ユーザーの一種であれば、継承が自然です。
class User
{
}
class AdminUser extends User
{
}
一方で、ログイン可能であることだけを保証したい場合は、interface が向いています。
interface Loginable
{
public function login(): void;
}
また、複数のクラスに同じメソッドを持たせたいだけなら、trait を検討できます。
継承とコンポジションの違い
継承は「is-a」の関係で使う
継承を使うかどうか迷ったときは、A is a B の関係が成り立つかを考えると分かりやすいです。
たとえば、次の関係は自然です。
AdminUser is a User
管理者ユーザーはユーザーの一種である
この場合は、継承が向いています。
class User
{
}
class AdminUser extends User
{
}
コンポジションは「has-a」の関係で使う
一方で、A has a B の関係なら、継承ではなくコンポジションを使う方が自然です。
たとえば、車とエンジンの関係を考えてみます。
Car has an Engine
車はエンジンを持っている
車はエンジンの一種ではありません。
そのため、Car extends Engine とするのは不自然です。
この場合は、Car クラスの中で Engine オブジェクトを持たせます。
class Engine
{
public function start(): void
{
echo 'エンジンを始動しました';
}
}
class Car
{
private Engine $engine;
public function __construct(Engine $engine)
{
$this->engine = $engine;
}
public function drive(): void
{
$this->engine->start();
echo '車が走り出しました';
}
}
$engine = new Engine();
$car = new Car($engine);
$car->drive();
このように、別のオブジェクトを部品として持つ設計をコンポジションといいます。
実務では継承よりコンポジションが適する場合も多い
単に処理を使い回したいだけで継承を使うと、不自然な設計になりやすいです。
たとえば、次のような設計はあまり自然ではありません。
class Logger
{
public function log(string $message): void
{
echo $message;
}
}
class User extends Logger
{
}
User は Logger の一種ではありません。
「ユーザーがログを出力する機能を使う」という関係です。
この場合は、次のように Logger を部品として持たせる方が自然です。
class Logger
{
public function log(string $message): void
{
echo $message;
}
}
class User
{
private Logger $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function register(): void
{
$this->logger->log('ユーザー登録しました');
}
}
継承は強力ですが、親クラスと子クラスの結びつきが強くなります。
そのため、実務では「本当に親子関係なのか」を考えてから使うことが大切です。
継承関係を判定するinstanceof
instanceofでオブジェクトの型を確認できる
instanceof を使うと、あるオブジェクトが特定のクラスのインスタンスかどうかを判定できます。
class User
{
}
class AdminUser extends User
{
}
$admin = new AdminUser();
var_dump($admin instanceof AdminUser); // true
var_dump($admin instanceof User); // true
$admin は AdminUser のオブジェクトなので、$admin instanceof AdminUser は true です。
また、AdminUser は User を継承しているため、$admin instanceof User も true になります。
interfaceの判定にも使える
instanceof は、クラスだけでなく interface に対しても使えます。
interface Loginable
{
public function login(): void;
}
class User implements Loginable
{
public function login(): void
{
echo 'ログインしました';
}
}
$user = new User();
var_dump($user instanceof Loginable); // true
特定のインターフェースを実装しているかどうかを確認したい場合にも便利です。
実務でよくある継承の例
会員種別ごとに割引率を変える例
ECサイトで、通常会員とプレミアム会員の割引率を変えたい場合を考えます。
共通する情報は親クラスにまとめ、会員種別ごとに異なる割引率は子クラスで実装します。
abstract class User
{
protected string $name;
protected string $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
public function getProfile(): string
{
return "{$this->name} / {$this->email}";
}
abstract public function getDiscountRate(): float;
}
通常会員クラスです。
class RegularUser extends User
{
public function getDiscountRate(): float
{
return 0.05;
}
}
プレミアム会員クラスです。
class PremiumUser extends User
{
public function getDiscountRate(): float
{
return 0.15;
}
}
利用例です。
function showDiscount(User $user): void
{
echo $user->getProfile();
echo '割引率:' . ($user->getDiscountRate() * 100) . '%';
}
$regular = new RegularUser('田中', 'tanaka@example.com');
$premium = new PremiumUser('佐藤', 'sato@example.com');
showDiscount($regular);
showDiscount($premium);
RegularUser も PremiumUser も User の一種です。
このような関係であれば、継承を使うのは自然です。
管理者ユーザーと一般ユーザーの例
管理者ユーザーだけが投稿を削除できる、という例も継承で表現できます。
class User
{
protected string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
class AdminUser extends User
{
public function deletePost(): void
{
echo "{$this->name}さんが投稿を削除しました";
}
}
$admin = new AdminUser('佐藤');
echo $admin->getName();
$admin->deletePost();
AdminUser は User の基本機能を持ちながら、管理者専用の deletePost() を追加しています。
継承を使うときの注意点
コードの使い回しだけを目的にしない
継承は便利ですが、「同じ処理を使い回したい」という理由だけで使うと、設計が不自然になることがあります。
継承を使う前に、次のように考えるとよいです。
AdminUser は User の一種か?
PremiumUser は User の一種か?
Car は Engine の一種か?
User は Logger の一種か?
「AはBの一種である」と自然に言える場合は、継承が候補になります。
一方で、「AはBを持っている」「AはBを使っている」という関係なら、コンポジションの方が適していることが多いです。
protectedを増やしすぎない
protected は子クラスからアクセスできるため便利です。
しかし、親クラスの内部構造を子クラスに見せすぎると、親クラスと子クラスの結びつきが強くなります。
たとえば、親クラスのプロパティを子クラスが直接使いすぎると、親クラス側でプロパティ名や構造を変えにくくなります。
class User
{
protected string $name;
protected string $email;
protected string $role;
}
このように何でも protected にすると、子クラスが親クラスの内部に依存しやすくなります。
必要に応じて、private プロパティとメソッドを組み合わせるとよいです。
class User
{
private string $name;
public function getName(): string
{
return $this->name;
}
}
継承階層を深くしすぎない
継承階層が深くなると、どのクラスでどの処理が定義されているのか分かりにくくなります。
User
└── PaidUser
└── PremiumUser
└── CorporatePremiumUser
このような設計は、最初は整理されているように見えても、後から変更しにくくなることがあります。
継承階層が深くなりそうな場合は、次のような方法も検討するとよいです。
interfaceで振る舞いを分けるtraitで共通処理を切り出す- コンポジションで機能を部品化する
- 条件分岐ではなくクラスの責務を見直す
親クラスには安定した共通処理を置く
親クラスの変更は、子クラスに影響します。
そのため、頻繁に変わる処理を親クラスに入れると、継承している子クラス全体に影響が広がる可能性があります。
親クラスには、できるだけ安定した共通処理を置くのが理想です。
たとえば、ユーザーの名前やメールアドレスを扱う処理は共通化しやすいですが、キャンペーンごとの割引計算など、頻繁に変わる処理は別クラスに分けた方がよい場合もあります。
継承でよくあるエラー
privateプロパティに子クラスからアクセスしている
よくある間違いが、親クラスの private プロパティに子クラスから直接アクセスすることです。
class User
{
private string $name = '田中';
}
class AdminUser extends User
{
public function showName(): void
{
echo $this->name;
}
}
これは正しくありません。
子クラスから使いたい場合は、protected にするか、親クラスにメソッドを用意します。
class User
{
private string $name = '田中';
protected function getName(): string
{
return $this->name;
}
}
class AdminUser extends User
{
public function showName(): void
{
echo $this->getName();
}
}
このようにすると、子クラスは親クラスの protected メソッドを通じて名前を取得できます。
親クラスのコンストラクタを呼び忘れている
子クラスでコンストラクタを定義した場合、親クラスのコンストラクタは自動で呼ばれません。
class User
{
protected string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
class AdminUser extends User
{
private int $level;
public function __construct(int $level)
{
$this->level = $level;
}
}
このコードでは、親クラスの $name が初期化されません。
正しくは、parent::__construct() を呼び出します。
class AdminUser extends User
{
private int $level;
public function __construct(string $name, int $level)
{
parent::__construct($name);
$this->level = $level;
}
}
オーバーライド時に戻り値の型が合っていない
親クラスと子クラスで、メソッドの戻り値型が大きく異なるとエラーになります。
class User
{
public function getName(): string
{
return '田中';
}
}
class AdminUser extends User
{
public function getName(): array
{
return ['田中'];
}
}
この例では、親クラスは string を返すのに、子クラスは array を返しているため正しくありません。
次のように、互換性のある型にします。
class AdminUser extends User
{
public function getName(): string
{
return '管理者:田中';
}
}
PHPの継承を使うときの判断基準
継承が向いているケース
継承が向いているのは、親子関係が自然に表現できる場合です。
| 関係 | 継承が向いているか |
|---|---|
| 管理者ユーザーはユーザーの一種 | 向いている |
| プレミアム会員は会員の一種 | 向いている |
| PDF請求書は請求書の一種 | 向いている |
| 車はエンジンの一種 | 向いていない |
| ユーザーはメール送信機能の一種 | 向いていない |
ポイントは、「AはBの一種である」と自然に言えるかどうかです。
継承以外を検討した方がよいケース
次のような場合は、継承以外の設計を検討した方がよいです。
| 状況 | 検討したい方法 |
|---|---|
| 複数のクラスで同じメソッドを使い回したい | trait |
| 共通のルールだけを定義したい | interface |
| 別のオブジェクトの機能を使いたい | コンポジション |
| 継承階層が深くなりそう | interfaceやコンポジション |
| 親クラスの変更が多そう | コンポジション |
継承は「コードを短くするため」だけに使うものではありません。
クラス同士の関係を正しく表現するために使うものです。
まとめ
PHPの継承は、親クラスの性質や処理を子クラスに引き継がせる仕組みです。
基本構文は次のとおりです。
class AdminUser extends User
{
}
継承を使うと、共通処理を親クラスにまとめたり、子クラスごとに処理を変えたり、親クラスの型で複数の子クラスを扱ったりできます。
特に重要なポイントは次のとおりです。
| 用語・構文 | 内容 |
|---|---|
extends | クラスを継承する |
public | 外部からも子クラスからもアクセスできる |
protected | 子クラスからアクセスできる |
private | 子クラスから直接アクセスできない |
| オーバーライド | 親クラスのメソッドを子クラスで上書きする |
parent:: | 親クラスのメソッドやコンストラクタを呼び出す |
abstract | 子クラスに実装を強制する |
final | 継承やオーバーライドを禁止する |
static:: | 呼び出されたクラスを考慮して静的メソッドを呼ぶ |
instanceof | オブジェクトの型や継承関係を判定する |
ただし、継承は使いすぎるとクラス同士の結びつきが強くなり、変更しにくい設計になることがあります。
継承を使うか迷ったときは、次の基準で考えると分かりやすいです。
A is a B なら継承
A has a B ならコンポジション
たとえば、AdminUser は User の一種なので継承が自然です。
一方で、User が Logger を使う場合、User extends Logger とするのではなく、User が Logger を持つ設計にする方が自然です。
PHPの継承は、単なるコードの使い回しではなく、クラス同士の関係を表現するための機能です。
親子関係として自然か、変更に強い設計になっているかを意識しながら使うことが大切です。
以上、PHPのオブジェクトの継承についてでした。
最後までお読みいただき、ありがとうございました。










