Late Static Bindings (Resolución estática en tiempo de ejecución)

PHP implementa una funcionalidad llamada late static binding, en español la resolución estática en tiempo de ejecución, que puede ser utilizada para referenciar la clase llamada en un contexto de herencia estática.

Más precisamente, las resoluciones estáticas en tiempo de ejecución funcionan registrando el nombre de la clase en la última "llamada no reenviada". En el caso de las llamadas de métodos estáticos, se trata de la clase explícitamente nombrada (generalmente, la que está a la izquierda del operador ::) ; en el caso de métodos no estáticos, se trata de la clase del objeto. Una "llamada reenviada" es una llamada estática introducida por self::, parent::, static::, o, subiendo en la jerarquía de clases, forward_static_call(). La función get_called_class() puede ser utilizada para recuperar una cadena que contiene el nombre de la clase llamada, y static:: introduce su ámbito.

Esta funcionalidad ha sido bautizada como "late static bindings", con un punto de vista interno. "Late binding", literalmente resolución tardía, proviene del hecho de que los elementos static:: no serán resueltos utilizando la clase donde el método ha sido definido, sino que será calculada utilizando la información en tiempo de ejecución. También se denomina "static binding" ya que puede ser utilizado para (sin estar limitado a) los métodos estáticos.

Limitaciones de self::

Las referencias estáticas a la clase actual, con self:: o __CLASS__, son resueltas utilizando la clase a la que pertenecen las funciones, es decir, donde fueron definidas:

Ejemplo #1 Uso de 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();

?>

El ejemplo anterior mostrará:

A

Uso de la resolución estática en tiempo de ejecución

La resolución estática en tiempo de ejecución intenta superar esta limitación introduciendo una palabra clave que hace referencia a la clase que ha sido llamada durante la ejecución. Para simplificar, esta palabra clave permite la referencia a B desde test(), en el ejemplo anterior. Se decidió no introducir una nueva palabra clave, sino más bien utilizar la palabra static que ya estaba reservada.

Ejemplo #2 Uso simple de static::

<?php

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

public static function
test()
{
static::
who(); // Aquí, resolución estática en tiempo de ejecución
}
}

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

B::test();

?>

El ejemplo anterior mostrará:

B

Nota:

En contextos no estáticos, la clase llamada será la del objeto. Como $this-> intentará llamar a métodos privados desde el mismo ámbito, utilizar static:: podría dar resultados diferentes. Tenga en cuenta también que static:: solo puede hacer referencia a propiedades estáticas.

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

<?php

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

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

class
B extends A
{
/* foo() será copiada en B, por lo tanto su ámbito será siempre A
* y la llamada se realizará sin problemas */
}

class
C extends A
{
private function
foo()
{
/* El método original es reemplazado; el ámbito es el de C */
}
}

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

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

?>

El ejemplo anterior mostrará:

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

Nota:

La resolución de estáticos en tiempo de ejecución se detendrá en una llamada estática completamente resuelta. Por otro lado, las llamadas estáticas utilizando una palabra clave como parent:: o self:: reenviarán la información de llamada.

Ejemplo #4 Llamadas con o sin reenvío

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

?>

El ejemplo anterior mostrará:

A
C
C