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);
Fatal error: Uncaught TypeError: Base::create(): Return value must be of type Derived, Base returned in main.php:4
戻り値の型は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);
Fatal error: Uncaught TypeError: Animal::make(): Return value must be of type Cat, Animal returned in main.php:4
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);
Fatal error: Uncaught TypeError: Shape::build(): Return value must be of type Square, Shape returned in main.php:4
new self()は必ずShapeのインスタンスを作ります。戻り値の型staticが要求するSquareを返すにはnew static()にする必要があります。
直し方: self を static にします。
よくある誤解
「self::とstatic::はどちらも自分のクラスを指すので同じはず」という思い込みは誤りです。selfは定義されたクラスに固定されますが、staticは実際に呼び出されたクラス(遅延静的束縛)を指します。
まとめ
self:: で作ったインスタンスは継承先のサブクラスにならない(static:: との違い)は上級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。