tipos que poo new metodos metodo herencia example ejemplos ejemplo clases abstractos abstracto php inheritance php-internals

poo - que es un constructor en php



¿Por qué no puedes heredar de una clase aún no definida que hereda de una clase aún no definida? (1)

Entonces, PHP usa algo llamado "enlace tardío". Básicamente, la herencia y la definición de clase no ocurre hasta el final de la compilación del archivo.

Hay un número de razones para esto. El primero es el ejemplo que mostró ( first extends second {} trabajando). La segunda razón es opcache.

Para que la compilación funcione correctamente en el ámbito de opcache, la compilación debe producirse sin estado de otros archivos compilados. Esto significa que mientras está compilando un archivo, la tabla de símbolos de clase se vacía.

Entonces, el resultado de esa compilación se almacena en caché. Luego, en tiempo de ejecución, cuando el archivo compilado se carga desde la memoria, opcache ejecuta el último enlace que luego hace la herencia y declara las clases.

class First {}

Cuando se ve esa clase, se agrega inmediatamente a la tabla de símbolos. No importa dónde esté en el archivo. Debido a que no hay necesidad de enlazar nada en el último momento, ya está completamente definido. Esta técnica se llama vinculación temprana y es lo que le permite utilizar una clase o función antes de su declaración.

class Third extends Second {}

Cuando eso se ve, se compila, pero en realidad no se declara. En cambio, se agrega a una lista de "enlace tardío".

class Second extends First {}

Cuando finalmente se ve esto, también se compila y no se declara realmente. Se agrega a la lista de enlaces finales, pero después de Third .

Así que ahora, cuando se produce el proceso de vinculación tardía, pasa por la lista de clases "de destino tardío" una a una. El primero que ve es el Third . Luego trata de encontrar la Second clase, pero no puede (ya que aún no está declarada). Entonces el error es lanzado.

Si reorganizas las clases:

class Second extends First {} class Third extends Second {} class First {}

Entonces verás que funciona bien.

¿Por qué hacer esto en absoluto?

Bueno, PHP es divertido. Imaginemos una serie de archivos:

<?php // a.php class Foo extends Bar {} <?php // b1.php class Bar { //impl 1 } <?php // b2.php class Bar { //impl 2 }

Ahora, qué instancia final de Foo obtendrás dependerá del archivo b que hayas cargado. Si requirió b2.php obtendrá Foo extends Bar (impl2) . Si requirió b1.php , obtendrá Foo extends Bar (impl1) .

Normalmente no escribimos el código de esta manera, pero hay algunos casos en que puede suceder.

En una solicitud PHP normal, esto es trivial de tratar. La razón es que podemos saber sobre Bar mientras compilamos Foo . Entonces podemos ajustar nuestro proceso de compilación en consecuencia.

Pero cuando traemos un caché de código de operación en la mezcla, las cosas se vuelven mucho más complicadas. Si b1.php Foo con el estado global de b1.php , luego (en una solicitud diferente) cambiamos a b2.php , las cosas se romperían de maneras extrañas.

Por lo tanto, en su lugar, los cachés de código de operación anulan el estado global antes de compilar un archivo. Entonces a.php se compilaría como si fuera el único archivo en la aplicación.

Una vez completada la compilación, se almacena en caché en la memoria (para ser reutilizada por solicitudes posteriores).

Luego, después de ese punto (o después de que se cargue desde la memoria en una solicitud futura), se dan los pasos "retrasados". Esto luego combina el archivo compilado con el estado de la solicitud.

De esta forma, opcache puede almacenar en caché de manera más eficiente los archivos como entidades independientes, ya que el enlace al estado global ocurre después de que se lee el caché.

El código fuente

Para ver por qué, veamos el código fuente.

En Zend/zend_compile.c podemos ver la función que compila la clase: zend_compile_class_decl() . A mitad de camino verás el siguiente código:

if (extends_ast) { opline->opcode = ZEND_DECLARE_INHERITED_CLASS; opline->extended_value = extends_node.u.op.var; } else { opline->opcode = ZEND_DECLARE_CLASS; }

Por lo tanto, inicialmente emite un código de operación para declarar la clase heredada. Luego, después de que se produce la compilación, se llama a una función llamada zend_do_early_binding() . Esto declara de antemano funciones y clases en un archivo (para que estén disponibles en la parte superior). Para clases y funciones normales, simplemente las agrega a la tabla de símbolos (las declara).

El bit interesante está en el caso heredado:

if (((ce = zend_lookup_class_ex(Z_STR_P(parent_name), parent_name + 1, 0)) == NULL) || ((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) && (ce->type == ZEND_INTERNAL_CLASS))) { if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) { uint32_t *opline_num = &CG(active_op_array)->early_binding; while (*opline_num != (uint32_t)-1) { opline_num = &CG(active_op_array)->opcodes[*opline_num].result.opline_num; } *opline_num = opline - CG(active_op_array)->opcodes; opline->opcode = ZEND_DECLARE_INHERITED_CLASS_DELAYED; opline->result_type = IS_UNUSED; opline->result.opline_num = -1; } return; }

El externo si básicamente trata de obtener la clase de la tabla de símbolos y verifica si no existe. El segundo si comprueba si estamos utilizando un enlace retrasado (el opcache está habilitado).

Luego, copia el código de operación para declarar la clase en la matriz de enlace temprana diferida.

Finalmente, se llama a la función zend_do_delayed_early_binding() (generalmente por un opcache), que recorre la lista y vincula realmente las clases heredadas:

while (opline_num != (uint32_t)-1) { zval *parent_name = RT_CONSTANT(op_array, op_array->opcodes[opline_num-1].op2); if ((ce = zend_lookup_class_ex(Z_STR_P(parent_name), parent_name + 1, 0)) != NULL) { do_bind_inherited_class(op_array, &op_array->opcodes[opline_num], EG(class_table), ce, 0); } opline_num = op_array->opcodes[opline_num].result.opline_num; }

TL; DR

El orden no importa para las clases que no amplían otra clase.

Cualquier clase que se amplíe debe definirse antes del punto en que se implementó (o se debe usar un autocargador).

Investigo acerca de la compilación de clases, su secuencia y lógica.

Si declaro una clase ante un padre simple:

class First extends Second{} class Second{}

Esto funcionará bien. Ver ejemplo en vivo en todas las versiones de PHP.

Pero si la clase principal también tiene algunos padres aún no declarados (extiende o implementa), como en este ejemplo:

class First extends Second{} class Second extends Third{} class Third{}

Tendré un error:

Error fatal: clase ''Segunda'' no encontrada ...

Ver ejemplo en vivo en todas las versiones de PHP.

Entonces, ¿por qué en el segundo ejemplo no puede encontrar Second clase? Quizás php no puede compilar esta clase porque también necesita compilar Third clase, ¿o qué?

Estoy tratando de averiguar por qué en el primer ejemplo, clase de compilación PHP en segundo lugar, pero si tendrá algunas clases principales, no lo hará. Investigué mucho, pero nada exactamente.

  • No estoy tratando de escribir código de esta manera, pero en este ejemplo trato de entender cómo funciona la compilación y su secuencia.