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::を使う必要があります。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です