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

Позднее статическое связывание в 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