self:: で作ったインスタンスは継承先のサブクラスにならない(static:: との違い)
戻り値の型を static と宣言したメソッドの中で new self() を使うと、呼び出し元がサブクラスであっても常に定義元のクラスのインスタンスが作られます。呼び出し時点のクラスを使いたい場合は new static() が必要です。
エラーメッセージの読み方
Fatal error: Uncaught TypeError: Base::create(): Return value must be of type Derived, Base returned in main.php:4
main.php- ファイル名
4- 行番号 — 実際にクラッシュした行
TypeError- 例外クラス — 何が起きたか。ここを検索するのが最短です
Base::create(): Return value must be of type Derived, Base returned- 内容 — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 <?php 2 class Base { 3 public static function create(): static { 4 return new self (); ^ 5 } 6 } 7 class Derived extends Base {} 8 $obj = Derived::create(); 9 echo get_class($obj);
戻り値の型はstaticと宣言されているのに、new self()は常に定義元のBaseを作ります。呼び出し時点のクラス(この場合Derived)を作るにはnew static()が必要です。
直し方: self を static にします。
パターン2
1 <?php 2 class Animal { 3 public static function make(): static { 4 return new self (); ^ 5 } 6 } 7 class Cat extends Animal {} 8 $obj = Cat::make(); 9 echo get_class($obj);
self::は定義されたクラス(Animal)に固定されます。呼び出し元のサブクラス(Cat)を返したい場合はstatic(遅延静的束縛)を使う必要があります。
直し方: self を static にします。
パターン3
1 <?php 2 class Shape { 3 public static function build(): static { 4 return new self (); ^ 5 } 6 } 7 class Square extends Shape {} 8 $obj = Square::build(); 9 echo get_class($obj);
new self()は必ずShapeのインスタンスを作ります。戻り値の型staticが要求するSquareを返すにはnew static()にする必要があります。
直し方: self を static にします。
パターン4
1 <?php 2 class Vehicle { 3 public static function spawn(): static { 4 return new self (); ^ 5 } 6 } 7 class Truck extends Vehicle {} 8 $obj = Truck::spawn(); 9 echo get_class($obj);
戻り値の型はstaticと宣言されているのに、new self()は常に定義元のVehicleを作ります。呼び出し時点のクラス(Truck)を作るにはnew static()が必要です。
直し方: self を static にします。
パターン5
1 <?php 2 class Widget { 3 public static function make(): static { 4 return new self (); ^ 5 } 6 } 7 class Button extends Widget {} 8 $obj = Button::make(); 9 echo get_class($obj);
self::は定義されたクラス(Widget)に固定されます。呼び出し元のサブクラス(Button)を返したい場合はstaticを使う必要があります。
直し方: self を static にします。
よくある誤解
「self::とstatic::はどちらも自分のクラスを指すので同じはず」という思い込みは誤りです。selfは定義されたクラスに固定されますが、staticは実際に呼び出されたクラス(遅延静的束縛)を指します。
実務での勘所
「遅延静的束縛」という名前の「遅延」は、staticキーワードがどのクラスを指すかの決定を、メソッドが定義された時点ではなく、実際に呼び出された瞬間まで先延ばしにすることを意味します。selfはメソッドが書かれているクラスに常に固定される「早期」束縛ですが、staticは呼び出しの起点になった実際のクラスを実行時に遡って特定します。PHP 5.3でこの機能が追加される前は、継承先のクラスを正しく返すファクトリメソッドをself::だけで書くことができませんでした。