php class opcode

php - ¿Cómo funciona una extensión de clase o interfaz?



class opcode (2)

La premisa básica es que para que la clase sea utilizada debe ser definida, es decir, conocida por el motor. Esto nunca se puede cambiar: si necesita un objeto de alguna clase, el motor de PHP necesita saber cuál es la clase.

Sin embargo, el momento en que el motor obtiene tal conocimiento puede ser diferente. En primer lugar, consumir el código PHP por el motor consiste en dos procesos separados: compilación y ejecución. En la etapa de compilación, el motor convierte el código PHP como lo conoce al conjunto de códigos de operación (con el que ya está familiarizado), en la segunda etapa el motor pasa por los códigos de operación ya que el procesador pasa por las instrucciones en la memoria y los ejecuta.

Uno de los códigos de operación es el código de operación que define una nueva clase, que generalmente se inserta en el mismo lugar donde la definición de la clase está en la fuente.

Sin embargo, cuando el compilador encuentra una definición de clase, puede ingresar la clase en la lista de las clases conocidas por el motor antes de ejecutar cualquier código. Esto se llama "vinculación temprana". Esto puede suceder si el compilador decide que ya tiene toda la información que necesita para crear una definición de clase, y no hay ninguna razón para posponer la creación de clases hasta el tiempo de ejecución real. Actualmente, el motor solo lo hace si la clase:

  1. no tiene interfaces o rasgos asociados
  2. no es abstracto
  3. o no extiende ninguna clase o extiende solo la clase que ya conoce el motor
  4. se declara como el enunciado principal (es decir, no en el interior de una condición, función, etc.)

Este comportamiento también se puede modificar mediante las opciones del compilador, pero solo están disponibles para extensiones como APC, por lo que no debería preocuparle mucho a menos que vaya a desarrollar APC o una extensión similar.

Esto también significa que esto estaría bien:

class B extends A {} class A { }

pero esto no sería:

class C extends B {} class B extends A {} class A { }

Ya que A sería un límite temprano, y por lo tanto estaría disponible para la definición de B, pero B se definiría solo en la línea 2 y, por lo tanto, no estaría disponible para la definición de C. de la línea 1.

En su caso, cuando su clase implementó la interfaz, no se enlazó de manera anticipada y, por lo tanto, se dio a conocer al motor en el momento en que se alcanzó la declaración de "clase". Cuando era una clase simple sin interfaces, se enlazó de forma anticipada y, por lo tanto, el motor la conoció tan pronto como finalizó la compilación de archivos (puede ver este punto como uno antes de la primera declaración en el archivo).

Para no molestarme con todos estos detalles extraños del motor, apoyaría la recomendación de la respuesta anterior: si el script es pequeño, solo declare las clases antes del uso. Si tiene una aplicación más grande, defina sus clases en archivos individuales y tenga una asignación de nombres de archivos y estrategias de carga automática, por ejemplo, PSR-0 o algo similar, según sea adecuado en su caso.

Me he topado con esto muchas veces y no estoy seguro de por qué, así que me dio curiosidad. Algunas clases funcionan antes de que se declaren y otras no;

Ejemplo 1

$test = new TestClass(); // top of class class TestClass { function __construct() { var_dump(__METHOD__); } }

Salida

string ''TestClass::__construct'' (length=22)

Ejemplo 2

Cuando una clase amplía otra clase o implementa cualquier interfaz

$test = new TestClass(); // top of class class TestClass implements JsonSerializable { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return json_encode(rand(1, 10)); } }

Salida

Fatal error: Class ''TestClass'' not found

Ejemplo 3

Probemos la misma clase de arriba pero cambiemos la posición

class TestClass implements JsonSerializable { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return json_encode(rand(1, 10)); } } $test = new TestClass(); // move this from top to bottom

Salida

string ''TestClass::__construct'' (length=22)

Ejemplo 4 (también probé con class_exists)

var_dump(class_exists("TestClass")); //true class TestClass { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return null; } } var_dump(class_exists("TestClass")); //true

tan pronto como implemente JsonSerializable (o cualquier otro)

var_dump(class_exists("TestClass")); //false class TestClass implements JsonSerializable { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return null; } } var_dump(class_exists("TestClass")); //true

También comprobamos códigos de operación without JsonSerializable

line # * op fetch ext return operands --------------------------------------------------------------------------------- 3 0 > SEND_VAL ''TestClass'' 1 DO_FCALL 1 $0 ''class_exists'' 2 SEND_VAR_NO_REF 6 $0 3 DO_FCALL 1 ''var_dump'' 4 4 NOP 14 5 > RETURN 1

También comprobamos códigos de operación with JsonSerializable

line # * op fetch ext return operands --------------------------------------------------------------------------------- 3 0 > SEND_VAL ''TestClass'' 1 DO_FCALL 1 $0 ''class_exists'' 2 SEND_VAR_NO_REF 6 $0 3 DO_FCALL 1 ''var_dump'' 4 4 ZEND_DECLARE_CLASS $2 ''%00testclass%2Fin%2FaDRGC0x7f563932f041'', ''testclass'' 5 ZEND_ADD_INTERFACE $2, ''JsonSerializable'' 13 6 ZEND_VERIFY_ABSTRACT_CLASS $2 14 7 > RETURN 1

Pregunta

  • Sé que el Example 3 funcionó porque la clase se declaró antes de su inicio, pero ¿por qué iba a funcionar el Example 1 en primer lugar?
  • ¿Cómo funciona todo este proceso de extensión o interfaz en PHP para que uno sea válido y el otro inválido?
  • ¿Qué está sucediendo exactamente en el Ejemplo 4?
  • Se suponía que class_exists cosas, pero lo hizo más complejo porque class_exists se llamó antes de TestClass pero TestClass lo contrario.

No puedo encontrar una escritura en las definiciones de clase de PHP; sin embargo, imagino que es exactamente lo mismo que las funciones definidas por el usuario que indican sus experimentos.

Las funciones no necesitan ser definidas antes de que sean referenciadas, excepto cuando una función se define condicionalmente como se muestra en los dos ejemplos a continuación. Cuando una función se define de una manera condicional; su definición debe procesarse antes de ser llamado.

<?php $makefoo = true; /* We can''t call foo() from here since it doesn''t exist yet, but we can call bar() */ bar(); if ($makefoo) { function foo() { echo "I don''t exist until program execution reaches me./n"; } } /* Now we can safely call foo() since $makefoo evaluated to true */ if ($makefoo) foo(); function bar() { echo "I exist immediately upon program start./n"; } ?>

Esto es cierto para las clases también:

  • El ejemplo 1 funciona porque la clase no es condicional en ninguna otra cosa.
  • El ejemplo 2 falla porque la clase es condicional a JsonSerializable .
  • El ejemplo 3 funciona porque la clase está definida correctamente antes de ser llamada.
  • El ejemplo 4 obtiene falso la primera vez porque la clase es condicional pero tiene éxito más adelante porque la clase se ha cargado.

La clase se hace condicional implementando una interfaz o ampliando otra clase desde otro archivo ( require ). Lo llamo condicional porque la definición ahora se basa en otra definición.

Imagine que el intérprete de PHP echa un vistazo al código en este archivo. Ve una clase y / o función no condicional, por lo que sigue adelante y las carga en la memoria. Ve unos pocos condicionales y salta sobre ellos.

Luego, el intérprete comienza a analizar la página para su ejecución. En el ejemplo 4, llega a la class_exists("TestClass") , verifica la memoria y dice no, no tengo eso. Si no lo tiene porque era condicional. Continúa ejecutando las instrucciones, ver la clase condicional y ejecuta las instrucciones para cargar la clase en la memoria.

Luego, desciende al último class_exists("TestClass") y ve que la clase existe efectivamente en la memoria.

Al leer sus TestClass , el TestClass no se llama antes de class_exist . Lo que ves es SEND_VAL que está enviando el valor TestClass para que esté en la memoria de la siguiente línea, que realmente llama a DO_FCALL en class_exists

A continuación, puede ver cómo maneja la definición de la clase en sí:

  1. ZEND_DECLARE_CLASS : esto está cargando la definición de tu clase
  2. ZEND_ADD_INTERFACE : esto recupera JsonSerializable y lo agrega a tu definición de clase
  3. ZEND_VERIFY_ABSTRACT_CLASS : esto verifica que todo esté en orden.

Es esa segunda pieza ZEND_ADD_INTERFACE que parece impedir que PHP Engine simplemente cargue la clase en el pico inicial.

Si desea una discusión más detallada de cómo el intérprete de PHP compila y ejecuta el código en estos escenarios, le sugiero que eche un vistazo a la respuesta de @StasM a esta pregunta , que proporciona una excelente visión general de la misma en mayor profundidad que esta respuesta.

Creo que respondimos todas sus preguntas.

Práctica recomendada: coloque cada una de sus clases en su propio archivo y luego autoload según sea necesario, como @StasM indica en su respuesta, use un nombre de archivo sensible y una estrategia de carga automática, por ejemplo, PSR-0 o algo similar . Cuando hace esto, ya no tiene que preocuparse por el orden del motor que los carga, simplemente lo maneja automáticamente.