Позднее статическое связывание

Позднее статическое связывание в PHP — механизм, который разрешает ссылаться на класс вызова в контексте статического наследования.

Точнее, позднее статическое связывание сохраняет класс, название которого указали в последнем «неперенаправленном вызове». При вызове статических методов это тот класс, название которого явно указали слева от оператора ::; при нестатических вызовах это класс объекта. «Перенаправленным вызовом» называется статический вызов через конструкции self::, parent::, static:: или через функцию forward_static_call() при движении вверх по иерархии классов. Строку с названием класса вызова получают функцией get_called_class(), а конструкция static:: вводит область действия вызываемого класса.

Природа названия «позднее статическое связывание» возникает из внутренней логики работы языка. Связывание называется «поздним», потому что конструкция static:: разрешается не в тот класс, в котором определили метод, а вычисляется на основе информации в ходе исполнения программы. Связывание также назвали «статическим», поскольку этот механизм в числе прочего умеет вызывать статические методы.

Ограничения конструкции self::

Статические ссылки на текущий класс наподобие конструкции self:: или константы __CLASS__ разрешаются в класс, которому принадлежит функция, — в котором функцию определили:

Пример #1 Пример обращения к члену класса через конструкцию self::

<?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__;
    }
}

B::test();

?>

Результат выполнения приведённого примера:

A

Пример позднего статического связывания

Позднее статическое связывание стремится устранить это ограничение и вводит ключевое слово для ссылки на класс, который изначально вызвали в ходе исполнения программы. По сути, это ключевое слово, которое в предыдущем примере разрешило бы ссылаться на класс B из метода test(). Вместо введения нового ключевого слова для позднего статического связывания разработчики языка выбрали ключевое слово static, которое зарезервировали прежде.

Пример #2 Пример позднего статического связывания через конструкцию static::

<?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__;
    }
}

B::test();

?>

Результат выполнения приведённого примера:

B

Замечание:

В нестатическом контексте классом вызова будет класс экземпляра объекта. Обращение через конструкцию $this-> попытается вызывать закрытые методы из той же области действия, тогда как результат конструкции static:: зависит от контекста вызова. Другое отличие состоит в том, что обращение через конструкцию static:: умеет ссылаться только на статические свойства.

Пример #3 Пример ссылки через конструкцию static:: в нестатическом контексте

<?php

class A
{
    private function foo()
    {
        echo "Success!\n";
    }

    public function test()
    {
        $this->foo();
        static::foo();
    }
}

class B extends A
{
    /* Метод foo() скопируется в класс В из класса A, поэтому областью действия метода
      по-прежнему будет класс А, и вызов будет успешным */
}

class C extends A
{
    private function foo()
    {
        /* Этот метод заменил собой исходный; область действия нового метода — класс С */
    }
}

$b = new B();
$b->test();

$c = new C();
try {
    $c->test();
} catch (Error $e) {
    echo $e->getMessage();
}

?>

Результат выполнения приведённого примера:

Success!
Success!
Success!
Call to private method C::foo() from scope A

Замечание:

Разрешение поздних статических связок останавливается на статическом вызове по полному названию класса, без попытки перенаправить вызов в класс, в котором сделали последний неперенаправленный вызов. Между тем, статические вызовы через конструкции parent:: или self:: перенаправляют информацию о вызове.

Пример #4 Пример перенаправленных и неперенаправленных вызовов

<?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();

?>

Результат выполнения приведённого примера:

A
C
C