Menu
Coddy logo textTech

遅延静的束縛

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
ParentClass

ChildClass::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
ChildClass

static:: を使用すると、PHP は実行時までどのクラスを使用するかを決定するのを待ちます。決定が遅れるため、これは「遅延」バインディングと呼ばれます。これは、ファクトリメソッドや、継承を尊重すべきあらゆる静的メソッドにとって不可欠です。

重要なポイント: 子クラスが静的な振る舞いを適切に継承し、カスタマイズできるようにしたい場合は、self:: の代わりに static:: を使用してください。

challenge icon

チャレンジ

簡単

遅延静的束縛(Late Static Binding)の威力を示すモデルレジストリシステムを構築しましょう。子クラスが継承できるファクトリメソッドを持つベースの Model クラスを作成します。static:: のおかげで、各子クラスは親クラスではなく、自分自身のインスタンスを正しく作成できるようになります。

コードは以下の4つのファイルに分けて構成します:

  • Model.php"models" に設定された protected static プロパティ $tableName を持つベースの Model クラスを作成します。呼び出し元のクラスの新しいインスタンスを返すために new static() を使用する static メソッド create() を追加します。static::$tableName を返す別の static メソッド getTable() を追加します。最後に、static::classstatic::$tableName を使用して "[ClassName] from table [tableName]" を返す describe() メソッドを追加します。
  • User.phpModel を継承する User クラスを作成します。親ファイルをインクルードしてください。static プロパティ $tableName"users" にオーバーライドします。このクラスは、変更を加えることなく Model からすべてのメソッドを継承します。
  • Product.phpModel を継承する Product クラスを作成します。親ファイルをインクルードしてください。static プロパティ $tableName"products" にオーバーライドします。同様に、このクラスは親からすべてのメソッドを継承します。
  • main.php — User と Product の両方のファイルをインクルードします。継承された create() ファクトリメソッドを使用して、UserProduct の両方のインスタンスを作成します。各インスタンスについて 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() を出力します

?>
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン