Enlaces estáticos en tiempo de ejecución

Desde su versión 5.3.0, PHP incorpora una nueva funcionalidad llamada enlace estático en tiempo de ejecución ("late static bindings" en inglés) que permite referirse referencias a la clase invocada en un contexto de herencia estática.

De forma más precisa, un enlace estático en tiempo de ejecución funciona almacenando el nombre de clase de la última llamada que no tenga «propagación». En el caso de llamadas a métodos estáticos, se trata de la clase nombrada explícitamente (normalmente la que precede al operador ::); en los casos de llamadas a métodos no estáticos, es clase del objeto. Una «llamada con propagación» es una llamada estática que precedida por self::, parent::, static::, o, siguiendo la jerarquía de clases, forward_static_call(). La función get_called_class() puede utilizarse para obtener una cadena con el nombre de la clase invocada, y static:: revela cuál es su alcance.

A esta funcionalidad se le ha llamado «enlace estático en tiempo de ejecución» teniendo en cuenta un punto de vista interno. «Enlace en tiempo de ejecución» viene del hecho de que static:: no será resuelto usando la clase donde el método está definido, sino que en su lugar se calculará utilizando información en tiempo de ejecución. También se le llamó «enlace estático» debido a que se puede utilizar (entre otras cosas) para llamadas a métodos estáticos.

Limitaciones de self::

Las referencias estáticas a la clase en uso, como self:: o __CLASS__, se resuelven empleando la clase a la que pertenece la función:

Ejemplo #1 Uso de self::

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

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

B::test();
?>

El resultado del ejemplo sería:

A

Uso de enlaces estático en tiempo de ejecución

Los enlaces estáticos en tiempo de ejecución tratan de resolver esta limitación empleando una palabra reservada que se refiera a la clase invocada inicialmente en tiempo de ejecución; básicamente, una palabra reservada que permita referirse a a B desde test(), según el ejemplo anterior. Se decidió no crear una nueva palabra reservada, si no utilizar en su lugar static, que ya era reservada.

Ejemplo #2 Uso básico de static::

<?php
class {
    public static function 
who() {
        echo 
__CLASS__;
    }
    public static function 
test() {
        static::
who(); // He aquí el enlace estático en tiempo de ejecución
    
}
}

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

B::test();
?>

El resultado del ejemplo sería:

B

Nota:

En contextos no estáticos, la clase invocada será la clase de la instancia del objeto. Dado que $this-> trata de invocar métodos privados desde su mismo ámbito, el uso de static:: puede dar diferentes resultados. Otra diferencia es que static:: sólo puede referirse a propiedades estáticas.

Ejemplo #3 Uso de static:: en un contexto no estático

<?php
class {
    private function 
foo() {
        echo 
"¡Éxito!\n";
    }
    public function 
test() {
        
$this->foo();
        static::
foo();
    }
}

class 
extends {
   
/* foo() se copiará en B, por lo tanto su ámbito seguirá siendo A
    * y la llamada tendrá éxito */
}

class 
extends {
    private function 
foo() {
        
/* se reemplaza el método original; el ámbito del nuevo es ahora C */
    
}
}

$b = new B();
$b->test();
$c = new C();
$c->test();   //falla
?>

El resultado del ejemplo sería:

¡Éxito!
¡Éxito!
¡Éxito!


Fatal error:  Call to private method C::foo() from context 'A' in /tmp/test.php on line 9

Nota:

En una llamada que se resuelva como estática, la resolución de enlaces estáticos en tiempo de ejecución se detendrá sin propagarse. Por otra parte, las llamadas estáticas que utilicen palabras reservadas como parent:: o self:: sí propagarán la información de llamada.

Ejemplo #4 Llamadas que propagan y que no propagan la información de llamada

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

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

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

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

C::test();
?>

El resultado del ejemplo sería:

A
C
C