遅延静的束縛
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 36/91。
親クラスで self:: を使用すると、それは常にそのコードが記述されたクラスを指し、実際に使用されているクラスを指しません。これは継承において予期しない動作を引き起こす可能性があります。遅延静的束縛 (Late static binding) は、static:: キーワードを使用することでこの問題を解決します。
self:: が期待通りに動作しない、以下の例を考えてみましょう。
<?php
class ParentClass {
public static function create() {
return new self();
}
public static function getClassName() {
return self::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
出力:
ParentClass
ParentClassChildClass::create() を呼び出したにもかかわらず、self:: はコンパイル時に解決されるため、ParentClass オブジェクトが返されました。では、代わりに static:: を使ってみましょう:
<?php
class ParentClass {
public static function create() {
return new static();
}
public static function getClassName() {
return static::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
出力:
ChildClass
ChildClassstatic:: を使用すると、PHP は実行時までどのクラスを使用するかを決定するのを待ちます。決定が遅れるため、これは「遅延」バインディングと呼ばれます。これは、ファクトリメソッドや、継承を尊重すべきあらゆる静的メソッドにとって不可欠です。
重要なポイント: 子クラスが静的な振る舞いを適切に継承し、カスタマイズできるようにしたい場合は、self:: の代わりに static:: を使用してください。
チャレンジ
簡単遅延静的束縛(Late Static Binding)の威力を示すモデルレジストリシステムを構築しましょう。子クラスが継承できるファクトリメソッドを持つベースの Model クラスを作成します。static:: のおかげで、各子クラスは親クラスではなく、自分自身のインスタンスを正しく作成できるようになります。
コードは以下の4つのファイルに分けて構成します:
Model.php—"models"に設定された protected static プロパティ$tableNameを持つベースのModelクラスを作成します。呼び出し元のクラスの新しいインスタンスを返すためにnew static()を使用する static メソッドcreate()を追加します。static::$tableNameを返す別の static メソッドgetTable()を追加します。最後に、static::classとstatic::$tableNameを使用して"[ClassName] from table [tableName]"を返すdescribe()メソッドを追加します。User.php—Modelを継承するUserクラスを作成します。親ファイルをインクルードしてください。static プロパティ$tableNameを"users"にオーバーライドします。このクラスは、変更を加えることなくModelからすべてのメソッドを継承します。Product.php—Modelを継承するProductクラスを作成します。親ファイルをインクルードしてください。static プロパティ$tableNameを"products"にオーバーライドします。同様に、このクラスは親からすべてのメソッドを継承します。main.php— User と Product の両方のファイルをインクルードします。継承されたcreate()ファクトリメソッドを使用して、UserとProductの両方のインスタンスを作成します。各インスタンスについてdescribe()を呼び出し、その結果をそれぞれの行に出力します。次に、User::getTable()とProduct::getTable()を使用して各クラスのテーブル名をそれぞれの行に出力します。
ここでのポイントは、create() メソッドと getTable() メソッドが親の Model クラスでのみ定義されているにもかかわらず、static:: が実行時まで束縛を遅らせるため、子クラスで正しく動作することです。User::create() を呼び出すと、PHP は Model インスタンスではなく User インスタンスを作成します。これは static:: が実際に呼び出されたクラスを参照するためです。
出力には、User インスタンスの説明、Product インスタンスの説明、User のテーブル名、Product のテーブル名の4行が表示されるはずです。
チートシート
親クラス内の self:: キーワードは、常にそれが記述されたクラスを指し、コンパイル時に解決されます。static:: による遅延静的束縛 (Late static binding)は、実行時に参照を解決するため、子クラスが静的な振る舞いを適切に継承し、カスタマイズすることを可能にします。
self:: の使用 (コンパイル時バインディング)
<?php
class ParentClass {
public static function create() {
return new self(); // Always creates ParentClass
}
public static function getClassName() {
return self::class; // Always returns "ParentClass"
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create(); // Returns ParentClass instance
echo ChildClass::getClassName(); // Outputs: ParentClass
static:: の使用 (実行時バインディング)
<?php
class ParentClass {
public static function create() {
return new static(); // Creates instance of calling class
}
public static function getClassName() {
return static::class; // Returns calling class name
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create(); // Returns ChildClass instance
echo ChildClass::getClassName(); // Outputs: ChildClass
重要なポイント: 子クラスが静的な振る舞いを適切に継承し、カスタマイズできるようにしたい場合は、self:: の代わりに static:: を使用してください。これは、ファクトリメソッドや、継承を尊重すべき静的メソッドにおいて不可欠です。
自分で試してみよう
<?php
require_once 'User.php';
require_once 'Product.php';
// TODO: 継承された create() ファクトリメソッドを使用して User インスタンスを作成します
// TODO: 継承された create() ファクトリメソッドを使用して Product インスタンスを作成します
// TODO: User インスタンスで describe() を呼び出し、結果を出力します
// TODO: Product インスタンスで describe() を呼び出し、結果を出力します
// TODO: User::getTable() を出力します
// TODO: Product::getTable() を出力します
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。