late static bindingとstatic::、selfとの違いを腹落ちさせる
遅延静的束縛(late static binding)とは、static::が実際に呼び出されたクラスに解決される仕組みです。書いた場所に固定されるself::と対になる機能です。
selfは書いた場所に固定される
self:: と static:: の違いは、継承が絡んだ瞬間に効いてきます。self は「その行を書いたクラス」に固定的に束縛され、static は「実際に呼ばれたクラス」に解決される、というのが要点ですね。ファクトリメソッドを親クラスに置くとこの差がはっきり出ます。
class Model
{
public static function create(): static
{
return new self(); // 常に Model を作ってしまう
}
}
class User extends Model {}
$u = User::create();
var_dump($u instanceof User); // false、中身は Model
User::create() と呼んでいるのに、self が Model に固定されているせいで Model のインスタンスが返ってきます。継承したのに親が出てくる、という気持ち悪い挙動ですね。
static::にすると呼び出し元で解決される
これが late static binding、つまり「遅延静的束縛」です。self を static に変えるだけで、実際に呼び出されたクラスに基づいて解決されるようになります。戻り値の型 static とセットで使うのが定番です。
class Model
{
public static function create(): static
{
return new static(); // 呼び出し元のクラスを生成
}
}
class User extends Model {}
$u = User::create();
var_dump($u instanceof User); // true
定数やメソッドでも同じ差が出る
この束縛はプロパティやメソッド、定数の解決にも及びます。子クラスで上書きした値を親のメソッドから参照したいときは static:: を使う必要があります。self:: だと親の値を見に行ってしまうので、意図と食い違うわけですね。
class Base
{
protected const LABEL = 'base';
public function label(): string
{
return static::LABEL; // self だと常に 'base'
}
}
class Special extends Base
{
protected const LABEL = 'special';
}
echo (new Special())->label(); // static:: なら 'special'
まとめ
ざっくり言えば、継承先の挙動を尊重したいなら static、書いた場所に固定したいなら self、という使い分けになります。ファクトリやテンプレート的なメソッドを親に置くときは static:: が正解になりやすいですが、逆に「絶対にこのクラスの値を使う」と決めたいなら self:: が意図を守ってくれます。どちらも正しく、狙いに合わせて選ぶものだと思います。
よくある質問
Q. selfとstaticの違いは何ですか?
A. selfは書いた場所のクラスに固定され、staticは実際に呼び出されたクラスに解決されます。継承が絡むと差が出ます。
Q. ファクトリメソッドではどちらを使いますか?
A. 親に置いたファクトリでは呼び出し元のクラスを生成したいので、new static()と戻り値型staticが定番です。
Q. 定数の解決にも影響しますか?
A. します。子で上書きした定数を親のメソッドから参照したいときはstatic::を使う必要があります。