遅延的束縛完全理解した

PHP

PHP技術者認定試験の勉強で遅延的束縛(Late Static Bind)について調べて完全に理解したので、備忘録として残しておきます!

遅延的束縛とは

PHP5.3.0から導入された遅延的束縛(Late Static Binding)は簡単にいうと、呼び出すまで実行結果がわからないというものです。

まずは実行前から結果が固定されているパターンから見ていきます。

<?php
class A
{
  public static function who()
  {
    echo __CLASS__;
  }
  public static function test()
  {
    self::who();
  }
}

class B extends A
{
  public static function who()
  {
    echo __CLASS__;
  }
}

A::test();
B::test();

このコードの10行目に記述されているselfは呼び出し元に関係なく、selfが記述されたクラス内の静的メソッドを呼び出します。
なので、Aを継承しているBからtestメソッドを呼び出しても必ずAクラス内のwho()メソッドが呼び出されてAという結果が出力されます。

次は遅延的束縛を利用したパターンです。

<?php
class A
{
  public static function who()
  {
    echo __CLASS__;
  }
  public static function test()
  {
    static::who();
  }
}

class B extends A
{
  public static function who()
  {
    echo __CLASS__;
  }
}

A::test();
B::test();

selfと記述されたいた箇所をstaticに変更しただけで他は同じです。
staticは呼び出し元クラスを参照するので、B::test()を呼び出した時のtestメソッド内の処理はB::who()と同じ意味となり、Bと出力します。

このように、呼び出してからstaticの参照先が決まるので遅延的束縛と呼ばれます。

遅延的束縛をもっと詳しく

冒頭で分かりやすくするためにざっくりと紹介しましたが、遅延的束縛を詳しく説明すると、非転送コール時に明示されたクラス名を保持する機能と言えます。

まずは非転送コールと転送コールの違いを理解しておきましょう。

非転送コール

X::foo()や$x->foo()といったクラス名やオブジェクトを明示的に指定した呼び出し

転送コール

self::, parent::, static::のこと

非転送コールと転送コールを使った例

<?php
class A
{
  public static function foo()
  {
    static::who();
  }

  public static function who()
  {
    echo __CLASS__ . "\n";
  }
}

class B extends A
{
  public static function test()
  {
    A::foo();
    parent::foo();
    self::foo();
  }

  public static function who()
  {
    echo __CLASS__ . "\n";
  }
}
class C extends B
{
  public static function who()
  {
    echo __CLASS__ . "\n";
  }
}

C::test();

Cクラスのtest()メソッドを呼び出すことで、

A::foo() 転送コール
parent::foo() 非転送コール
self::foo() 非転送コール

が実行されます。

C::test()は非転送コールでA::foo()も非転送コール。
Aクラス内のwho()メソッドのstaticは直近の非転送コールのクラス名を保持しているためAを指し、A::whoを呼び出すことになり、Aを出力する。

parent::foo()は転送コールなので直近の非転送コールのクラス名を保持します。
つまり、foo()メソッド内でC::who()が実行され、Cが出力される。

self::foo()も転送コールなので直近の非転送コールを保持し、foo()メソッド内でC::who()が呼び出されることになり、Cが出力される。