php - temas - tesis sobre el sindrome de down en mexico
¿La clase derivada definida más adelante en el mismo archivo "no existe"? (2)
Esto simplemente con la clase de manejo de PHP en los archivos incluidos include dirname(__FILE__) . "/a.php";
include dirname(__FILE__) . "/a.php";
BB
existe porque extiende B
que se definió en el mismo archivo.
BA
no existe porque PHP no analizó A
línea se llama
Ambos trabajarían devuelven el mismo resultado
El uso de la class BA extends B
include dirname(__FILE__) . "/a.php";
echo "BA (before): ", class_exists("BA") ? "exists" : "doesn’t exist", "/n";
echo "BB: ", class_exists("BB") ? "exists" : "doesn’t exist", "/n";
class B {
}
class BA extends B {
}
class BB extends B {
}
echo "BA (after): ", class_exists("BA") ? "exists" : "doesn’t exist", "/n";
O definir la class A
y usar la class BA extends A
class A {
}
echo "<pre>";
echo "BA (before): ", class_exists("BA") ? "exists" : "doesn’t exist", "/n";
echo "BB: ", class_exists("BB") ? "exists" : "doesn’t exist", "/n";
class B {
}
class BA extends A {
}
class BB extends B {
}
echo "BA (after): ", class_exists("BA") ? "exists" : "doesn’t exist", "/n";
Salida
BA (before): exists
BB: exists
BA (after): exists
Conclusión
FORMULAR PHP DOC
Cuando se incluye un archivo, el código que contiene hereda el alcance variable de la línea en la que ocurre la inclusión. Cualquier variable disponible en esa línea en el archivo de llamada estará disponible dentro del archivo llamado, desde ese punto en adelante. Sin embargo, todas las funciones y clases definidas en el archivo incluido tienen el alcance global.
Creo que las clases ampliadas están cubiertas en lo que dice el documento PHP, esto se puede tratar como ERROR que debe corregirse, pero para el horario principal incluya su clase antes de llamar o usarlas
Supongamos que tenemos dos archivos php, a.php y b.php. Aquí está el contenido del archivo a.php:
<?php // content of a.php
class A {
}
Y aquí está el contenido del archivo b.php
<?php // content of b.php
include dirname(__FILE__) . "/a.php";
echo "A: ", class_exists("A") ? "exists" : "doesn’t exist", "/n";
echo "B: ", class_exists("B") ? "exists" : "doesn’t exist", "/n";
echo "BA (before): ", class_exists("BA") ? "exists" : "doesn’t exist", "/n";
echo "BB: ", class_exists("BB") ? "exists" : "doesn’t exist", "/n";
class B {
}
class BA extends A {
}
class BB extends B {
}
echo "BA (after): ", class_exists("BA") ? "exists" : "doesn’t exist", "/n";
Si ejecuta el script b.php, tiene este resultado:
A: exists
B: exists
BA (before): doesn’t exist
BB: exists
BA (after): exists
¿Por qué la clase BA existe solo después de la definición de la clase? ¿Y por qué existen las otras clases incluso antes de su definición? ¿Cuál es la diferencia? Esperaría tener un comportamiento común en ambos casos ... ¿Hay alguna manera de poder usar la clase BA incluso antes de su definición?
Gracias
Michele
Descargo de responsabilidad: no pretendo entender el funcionamiento interno de Zend. La siguiente es mi interpretación de la fuente PHP, alimentada en gran parte por conjeturas educadas. Aunque estoy completamente seguro de la conclusión, la terminología o los detalles podrían estar apagados. Me encantaría saber de alguien con experiencia en Zend Internals sobre el tema.
La investigación
Desde el analizador de PHP podemos ver que cuando se encuentra una declaración de clase, se zend_do_early_binding
función zend_do_early_binding
. Aquí está el código que maneja la declaración de las clases derivadas:
case ZEND_DECLARE_INHERITED_CLASS:
{
zend_op *fetch_class_opline = opline-1;
zval *parent_name;
zend_class_entry **pce;
parent_name = &CONSTANT(fetch_class_opline->op2.constant);
if ((zend_lookup_class(Z_STRVAL_P(parent_name), Z_STRLEN_P(parent_name), &pce TSRMLS_CC) == FAILURE) ||
((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) &&
((*pce)->type == ZEND_INTERNAL_CLASS))) {
if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
zend_uint *opline_num = &CG(active_op_array)->early_binding;
while (*opline_num != -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;
}
if (do_bind_inherited_class(CG(active_op_array), opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL) {
return;
}
/* clear unnecessary ZEND_FETCH_CLASS opcode */
zend_del_literal(CG(active_op_array), fetch_class_opline->op2.constant);
MAKE_NOP(fetch_class_opline);
table = CG(class_table);
break;
}
Este código llama inmediatamente a zend_lookup_class
para ver si la clase padre existe en la tabla de símbolos ... y luego difiere dependiendo de si el padre se encuentra o no.
Primero veamos qué hace si se encuentra la clase padre:
if (do_bind_inherited_class(CG(active_op_array), opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL) {
return;
}
Pasando a do_bind_inherited_class
, vemos que el último argumento (que en esta llamada es 1
) se llama compile_time
. Esto suena interesante. ¿Qué hace con este argumento?
if (compile_time) {
op1 = &CONSTANT_EX(op_array, opline->op1.constant);
op2 = &CONSTANT_EX(op_array, opline->op2.constant);
} else {
op1 = opline->op1.zv;
op2 = opline->op2.zv;
}
found_ce = zend_hash_quick_find(class_table, Z_STRVAL_P(op1), Z_STRLEN_P(op1), Z_HASH_P(op1), (void **) &pce);
if (found_ce == FAILURE) {
if (!compile_time) {
/* If we''re in compile time, in practice, it''s quite possible
* that we''ll never reach this class declaration at runtime,
* so we shut up about it. This allows the if (!defined(''FOO'')) { return; }
* approach to work.
*/
zend_error(E_COMPILE_ERROR, "Cannot redeclare class %s", Z_STRVAL_P(op2));
}
return NULL;
} else {
ce = *pce;
}
De acuerdo ... por lo que lee los nombres de las clases primarias y derivadas, ya sea desde una perspectiva estática (desde la perspectiva del usuario de PHP) o dinámica, dependiendo del estado compile_time
. Luego trata de encontrar la entrada de clase ("ce") en la tabla de clases, y si no se encuentra, entonces ... vuelve sin hacer nada en tiempo de compilación, pero emite un error fatal en el tiempo de ejecución .
Esto suena enormemente importante. Regresemos a zend_do_early_binding
. ¿Qué sucede si no se encuentra la clase padre?
if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
zend_uint *opline_num = &CG(active_op_array)->early_binding;
while (*opline_num != -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;
Parece que está generando do_bind_inherited_class
de do_bind_inherited_class
que activarán una llamada a do_bind_inherited_class
nuevamente, pero esta vez, el valor de compile_time
será 0
(falso).
Finalmente, ¿qué pasa con la implementación de la función PHP class_exists
? Al mirar la fuente, se muestra este fragmento:
found = zend_hash_find(EG(class_table), name, len+1, (void **) &ce);
¡Estupendo! ¡Esta variable class_table
es la misma class_table
que participa en la llamada do_bind_inherited_class
que vimos antes! Entonces, el valor de retorno de class_exists
depende de si una entrada para la clase ya ha sido insertada en class_table
por do_bind_inherited_class
.
Las conclusiones
El compilador Zend no actúa sobre las directivas include
en tiempo de compilación (incluso si el nombre del archivo está codificado).
Si lo hiciera, entonces no habría ninguna razón para emitir un error fatal de redeclaración de clase basado en el indicador compile_time
no configurado; el error podría emitirse incondicionalmente.
Cuando el compilador encuentra una declaración de clase derivada donde la clase base no ha sido declarada en el mismo archivo de script, empuja el acto de registrar la clase en sus estructuras internas de datos al tiempo de ejecución.
Esto es evidente a partir del último fragmento de código anterior, que configura un código de operación ZEND_DECLARE_INHERITED_CLASS_DELAYED
para registrar la clase cuando se ejecuta el script. En ese punto, el indicador compile_time
será false
y el comportamiento será sutilmente diferente.
El valor de retorno de class_exists
depende de si la clase ya se ha registrado.
Como esto ocurre de diferentes maneras en tiempo de compilación y en tiempo de ejecución, el comportamiento de class_exists
también es diferente:
- las clases cuyos antepasados están todas incluidas en el mismo archivo fuente se registran en tiempo de compilación; existen y se pueden crear instancias en cualquier punto de esa secuencia de comandos
- las clases que tienen un ancestro definido en otro archivo fuente se registran en tiempo de ejecución; antes de que la VM ejecute los
class_exists
false
que corresponden a la definición de la clase en la fuente, estas clases no existen para todos los propósitos prácticos (class_exists
devuelvefalse
, instanciar da un error fatal)