Позднее статическое связывание
Начиная с версии PHP 5.3.0 появилась особенность, называемая позднее статическое связывание, которая может быть использована для того, чтобы получить ссылку на вызываемый класс в контексте статического наследования.
Если говорить более точно, позднее статическое связывание сохраняет имя класса
указанного в последнем "неперенаправленном вызове". В случае статических
вызовов это явно указанный класс (обычно слева от оператора
::
);
в случае не статических вызовов это класс объекта. "Перенаправленный вызов" - это
статический вызов, начинающийся с 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() будет скопирован в В, следовательно его область действия по прежнему А,
и вызов будет успешным */
}
class C extends A {
private function foo() {
/* исходный метод заменен; область действия нового метода - С */
}
}
$b = new B();
$b->test();
$c = new C();
$c->test(); // потерпит ошибку
?>
Результат выполнения данного примера:
success! success! success! Fatal error: Call to private method C::foo() from context 'A' in /tmp/test.php on line 9
Замечание:
Разрешающая область позднего статического связывания будет фиксирована вычисляющем ее статическим вызовом. С другой стороны, статические вызовы с использованием таких директив как
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