Enlace estático tardío
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 36 de 91.
Cuando usas self:: en una clase padre, siempre se refiere a la clase donde está escrito, no a la clase que se está utilizando realmente. Esto puede causar un comportamiento inesperado en la herencia. El enlace estático tardío (late static binding) resuelve este problema utilizando la palabra clave static::.
Considera este ejemplo donde self:: no funciona como se espera:
<?php
class ParentClass {
public static function create() {
return new self();
}
public static function getClassName() {
return self::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
Salida:
ParentClass
ParentClassAunque llamamos a ChildClass::create(), obtuvimos un objeto ParentClass porque self:: se resuelve en tiempo de compilación. Ahora usemos static:: en su lugar:
<?php
class ParentClass {
public static function create() {
return new static();
}
public static function getClassName() {
return static::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
Salida:
ChildClass
ChildClassCon static::, PHP espera hasta el tiempo de ejecución para determinar qué clase usar. Esto se llama vinculación "tardía" porque la decisión se retrasa. Es esencial para los métodos de fábrica y cualquier método estático que deba respetar la herencia.
Punto clave: Utiliza static:: en lugar de self:: cuando quieras que las clases hijas hereden y personalicen correctamente el comportamiento estático.
Desafío
FácilVamos a construir un sistema de registro de modelos que demuestre el poder del enlace estático tardío (late static binding). Crearás una clase base Model con un método de fábrica que las clases hijas puedan heredar — y gracias a static::, cada clase hija creará correctamente instancias de sí misma en lugar del padre.
Organizarás tu código en cuatro archivos:
Model.php— Crea una clase baseModelcon una propiedad estática protegida$tableNameestablecida en"models". Añade un método estáticocreate()que usenew static()para devolver una nueva instancia de cualquier clase que lo haya llamado. Añade otro método estáticogetTable()que devuelvastatic::$tableName. Finalmente, añade un métododescribe()que devuelva"[ClassName] from table [tableName]"usandostatic::classystatic::$tableName.User.php— Crea una claseUserque extienda deModel. Incluye el archivo padre. Sobrescribe la propiedad estática$tableNamea"users". La clase hereda todos los métodos deModelsin ningún cambio.Product.php— Crea una claseProductque extienda deModel. Incluye el archivo padre. Sobrescribe la propiedad estática$tableNamea"products". De nuevo, la clase hereda todos los métodos del padre.main.php— Incluye tanto el archivo de User como el de Product. Usa el método de fábrica heredadocreate()para crear instancias tanto deUsercomo deProduct. Para cada instancia, llama adescribe()e imprime el resultado en su propia línea. Luego imprime el nombre de la tabla para cada clase usandoUser::getTable()yProduct::getTable(), cada uno en su propia línea.
La magia aquí es que los métodos create() y getTable() se definen solo en la clase padre Model, pero funcionan correctamente con las clases hijas porque static:: retrasa el enlace hasta el tiempo de ejecución. Cuando llamas a User::create(), PHP crea una instancia de User — no una instancia de Model — porque static:: se refiere a la clase que fue realmente llamada.
Tu salida debería mostrar cuatro líneas: la descripción para la instancia de User, la descripción para la instancia de Product, el nombre de la tabla de User y el nombre de la tabla de Product.
Hoja de referencia
La palabra clave self:: en una clase padre siempre se refiere a la clase donde está escrita, resolviéndose en tiempo de compilación. El enlace estático tardío (Late static binding) con static:: resuelve la referencia en tiempo de ejecución, permitiendo que las clases hijas hereden y personalicen correctamente el comportamiento estático.
Uso de self:: (enlace en tiempo de compilación)
<?php
class ParentClass {
public static function create() {
return new self(); // Siempre crea ParentClass
}
public static function getClassName() {
return self::class; // Siempre devuelve "ParentClass"
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create(); // Devuelve una instancia de ParentClass
echo ChildClass::getClassName(); // Salida: ParentClass
Uso de static:: (enlace en tiempo de ejecución)
<?php
class ParentClass {
public static function create() {
return new static(); // Crea una instancia de la clase que realiza la llamada
}
public static function getClassName() {
return static::class; // Devuelve el nombre de la clase que realiza la llamada
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create(); // Devuelve una instancia de ChildClass
echo ChildClass::getClassName(); // Salida: ChildClass
Punto clave: Utilice static:: en lugar de self:: cuando desee que las clases hijas hereden y personalicen correctamente el comportamiento estático. Esto es esencial para los métodos de fábrica y cualquier método estático que deba respetar la herencia.
Pruébalo tú mismo
<?php
require_once 'User.php';
require_once 'Product.php';
// TODO: Usa el método de fábrica heredado create() para crear una instancia de User
// TODO: Usa el método de fábrica heredado create() para crear una instancia de Product
// TODO: Llama a describe() en la instancia de User e imprime el resultado
// TODO: Llama a describe() en la instancia de Product e imprime el resultado
// TODO: Imprime User::getTable()
// TODO: Imprime Product::getTable()
?>Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - Calculadora de Formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeResumen - Colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy