26.

PHP のオーバーライド|可視性の制約・シグネチャ互換・Override 属性

編集
この記事の要点
  • オーバーライドは親と同じ名前のメソッドを子で書き直すこと。特別なキーワードは要らない
  • 可視性は広げられるが狭められないpublicprotected にすると Fatal error
  • 引数はゆるく、戻り値は厳しくできる(反変・共変)
  • final を付けたメソッドと private メソッドはオーバーライドできない
  • PHP 8.3 の #[\Override] を付けると、親に無いメソッドを書いたときに気づける

基本

class Shape
{
    public function area(): float
    {
        return 0.0;
    }

    public function describe(): string
    {
        return static::class . ' の面積: ' . $this->area();
    }
}

class Circle extends Shape
{
    public function __construct(private float $r) {}

    public function area(): float      // 上書き
    {
        return M_PI * $this->r ** 2;
    }
}

class Square extends Shape
{
    public function __construct(private float $side) {}

    public function area(): float
    {
        return $this->side ** 2;
    }
}

foreach ([new Circle(2), new Square(3)] as $s) {
    echo $s->describe();
}
// Circle の面積: 12.566...
// Square の面積: 9

describe() は親にしかありませんが、その中の $this->area()実際のインスタンスのメソッドが呼ばれます。これがポリモーフィズムで、オーバーライドの一番の使いどころです。

親の実装も動かす

class Logger
{
    public function log(string $msg): void
    {
        error_log($msg);
    }
}

class TimestampLogger extends Logger
{
    public function log(string $msg): void
    {
        parent::log(date('[Y-m-d H:i:s] ') . $msg);   // 加工してから親へ
    }
}

(new TimestampLogger())->log('開始');

parent::その場所に書かれているクラスの親を指します。3 段以上の継承でも、1 つ上を呼ぶだけです。

可視性のルール

class Base
{
    protected function run(): void {}
}

class OK extends Base
{
    public function run(): void {}       // protected → public は OK(広げる)
}

class NG extends Base
{
    private function run(): void {}      // Fatal error:
    // Access level to NG::run() must be protected (as in class Base) or weaker
}
子で書ける可視性
publicpublic のみ
protectedprotected / public
private制約なし(そもそもオーバーライドではない)

親として扱ったときに呼べたメソッドが、子では呼べない、という状態を防ぐためのルールです。

private はオーバーライドされない

class Base
{
    private function secret(): string { return 'Base'; }

    public function show(): string
    {
        return $this->secret();     // 常に Base::secret() を呼ぶ
    }
}

class Child extends Base
{
    private function secret(): string { return 'Child'; }   // 別物として共存する
}

echo (new Child())->show();    // Base   ← Child のほうは呼ばれない

private メソッドはそのクラスの中でだけ解決されます。子で差し替えられるようにしたいなら protected にしてください。

シグネチャの互換性

class Animal {}
class Dog extends Animal {}

class Base
{
    public function feed(Dog $d): Animal { return $d; }
}

class OK1 extends Base
{
    public function feed(Animal $d): Dog { return new Dog(); }
    // 引数は広く(Dog → Animal)、戻り値は狭く(Animal → Dog): 許される
}

class NG1 extends Base
{
    public function feed(Dog $d): Animal { return new Animal(); }   // OK(同じ)
}

class NG2 extends Base
{
    public function feed(Animal $d, int $extra): Animal { return $d; }
    // Fatal error: 必須の引数が増えている
}

class OK2 extends Base
{
    public function feed(Dog $d, int $extra = 0): Animal { return $d; }
    // 既定値付きなら増やせる
}

「親として呼んでいたコードが、子に差し替えても壊れないこと」が条件です。引数はゆるく、戻り値は厳しくと覚えます。

#[\Override] 属性(PHP 8.3)

class Base
{
    public function handle(): void {}
}

class Child extends Base
{
    #[\Override]
    public function handle(): void {}     // OK

    #[\Override]
    public function handl(): void {}      // Fatal error:
    // Child::handl() has #[\Override] attribute, but no matching parent method exists
}

スペルミスや、親のメソッド名が変わったときに気づけないまま上書きしていないメソッドが増えるのを防げます。オーバーライドのつもりで書いたメソッドには付けておくと安全です。

プロパティの再定義

class Base
{
    protected string $label = 'Base';
    protected array $options = ['a'];
}

class Child extends Base
{
    protected string $label = 'Child';         // 既定値の変更は OK
    // protected int $label = 1;               // Fatal error: 型は変えられない
    protected array $options = ['a', 'b'];

    public function label(): string { return $this->label; }
}

echo (new Child())->label();   // Child

プロパティは型を変えられません。既定値だけを変えられます。設定値の一覧のように差し替えたいものは、メソッドで返す形にすると柔軟になります。

class Base
{
    protected function options(): array { return ['a']; }
}

class Child extends Base
{
    protected function options(): array
    {
        return [...parent::options(), 'b'];   // 親の分に足せる
    }
}

static メソッドの扱い

class Base
{
    public static function label(): string { return 'Base'; }

    public static function selfCall(): string   { return self::label(); }
    public static function staticCall(): string { return static::label(); }
}

class Child extends Base
{
    public static function label(): string { return 'Child'; }
}

echo Child::selfCall();     // Base    ← 差し替わらない
echo Child::staticCall();   // Child   ← 差し替わる

static メソッドも上書きできますが、self:: で呼んでいる箇所は差し替わりません。差し替えを期待するなら static:: を使ってください(static関数)。

抽象メソッドの実装

abstract class Exporter
{
    abstract protected function format(array $rows): string;   // 実装は子に任せる

    final public function export(array $rows, string $path): void
    {
        file_put_contents($path, $this->format($rows));        // 流れは固定
    }
}

class CsvExporter extends Exporter
{
    protected function format(array $rows): string
    {
        return implode("\n", array_map(fn($r) => implode(',', $r), $rows));
    }
}

抽象メソッドの実装は必須のオーバーライドです。実装しないとインスタンス化した時点でエラーになるため、実装漏れを実行前に検出できます(抽象クラス・メソッドの構文と具体例)。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本事項
  2. HTMLへの埋め込み
  3. 変数
  4. 可変変数
  5. 定数
  6. データ型
  7. キャスト
  8. エスケープ文字
  9. 配列
  10. 演算子
  11. 代入の際の注意点
  12. 条件分岐
  13. 繰り返し処理
  14. クラスとインスタンス
  15. コンストラクタ
  16. 関数
  17. スーパーグローバル変数
  18. スコープ
  19. staticについて
  20. yieldについて
  21. ファイルのアップロード方法
  22. DB接続方法
  23. SQL実行方法
  24. カプセル化の具体例
  25. 継承の構文
  26. オーバーライド
  27. ポリモーフィズム(多様性)の具体例
  28. 抽象クラス・メソッドの構文と具体例
  29. GET通信
  30. try catchで全てのエラーを拾う方法

最近更新/作成されたページ