
Probablemente, uno debería comenzar con el hecho de que una función anónima (cierre) en PHP no es una función, sino un objeto de la clase
Closure . En realidad, este artículo podría haberse completado, pero si alguien está interesado en los detalles, bienvenido a cat.
Para no ser infundado:
$func = function (){}; var_dump($func); --------- object(Closure)
Mirando hacia el futuro, diré que este no es realmente un objeto ordinario. Vamos a resolverlo.
Por ejemplo, tal código
$func = function (){ echo 'Hello world!'; }; $func();
compila en un conjunto de códigos de operación:
line
El bloque con la descripción del cuerpo de la función no es particularmente interesante para nosotros, pero en el primer bloque hay dos
códigos de operación interesantes:
DECLARE_LAMBDA_FUNCTION e
INIT_DYNAMIC_CALL . Comencemos con el segundo.
INIT_DYNAMIC_CALL
Este código de operación se usa cuando el compilador ve una llamada de función en una variable o matriz. Es decir
$variable(); ['ClassName', 'staticMethod']();
Este no es un código de operación único específico solo para cierres. Esta sintaxis también funciona para los objetos llamando al método
__invoke () , para las variables de cadena que contienen el nombre de la función (
$ a = 'funcName'; $ a (); ), y para las matrices que contienen el nombre de la clase y el método estático.
En el caso del cierre, estamos interesados en llamar a una variable con un objeto, lo cual es lógico.
Profundizando en el código VM que procesa este código
operativo , llegamos a la función
zend_init_dynamic_call_object , en la que veremos lo siguiente (segmentación):
zend_execute_data *zend_init_dynamic_call_object(zend_object *function, uint32_t num_args) { zend_function *fbc; zend_class_entry *called_scope; zend_object *object; ... if (EXPECTED(function->handlers->get_closure) && EXPECTED(function->handlers->get_closure(function, &called_scope, &fbc, &object) == SUCCESS)) { ... } else { zend_throw_error(NULL, "Function name must be a string"); return NULL; } ... }
Es curioso que la llamada al método __invoke familiar en términos de VM sea un intento de llamar al cierre: get_closure .En realidad, en este punto, la diferencia comienza en el manejo de la llamada a la función anónima y al método
__invoke de un objeto regular.
En PHP, cada objeto tiene un conjunto de controladores diferentes que define su utilidad y métodos mágicos.
El conjunto estándar se ve así ZEND_API const zend_object_handlers std_object_handlers = { 0, zend_object_std_dtor, zend_objects_destroy_object, zend_objects_clone_obj, zend_std_read_property, zend_std_write_property, zend_std_read_dimension, zend_std_write_dimension, zend_std_get_property_ptr_ptr, NULL, NULL, zend_std_has_property, zend_std_unset_property, zend_std_has_dimension, zend_std_unset_dimension, zend_std_get_properties, zend_std_get_method, zend_std_get_constructor, zend_std_get_class_name, zend_std_compare_objects, zend_std_cast_object_tostring, NULL, zend_std_get_debug_info, zend_std_get_closure, zend_std_get_gc, NULL, NULL, NULL, };
Ahora estamos interesados en el controlador
get_closure . Para un objeto normal, apunta a la función
zend_std_get_closure , que verifica que la función
__invoke esté definida para el objeto y devuelve un puntero o un error. Pero para la clase
Closure , que implementa funciones anónimas, en este conjunto de controladores se redefinen casi todas las funciones de utilidad, incluidas las que controlan el ciclo de vida. Es decir aunque para el usuario parece un objeto ordinario, pero de hecho es un mutante con superpoderes :)
Registrar manejadores para un objeto de clase Cierre void zend_register_closure_ce(void) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Closure", closure_functions); zend_ce_closure = zend_register_internal_class(&ce); zend_ce_closure->ce_flags |= ZEND_ACC_FINAL; zend_ce_closure->create_object = zend_closure_new; zend_ce_closure->serialize = zend_class_serialize_deny; zend_ce_closure->unserialize = zend_class_unserialize_deny; memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers)); closure_handlers.free_obj = zend_closure_free_storage; closure_handlers.get_constructor = zend_closure_get_constructor; closure_handlers.get_method = zend_closure_get_method; closure_handlers.write_property = zend_closure_write_property; closure_handlers.read_property = zend_closure_read_property; closure_handlers.get_property_ptr_ptr = zend_closure_get_property_ptr_ptr; closure_handlers.has_property = zend_closure_has_property; closure_handlers.unset_property = zend_closure_unset_property; closure_handlers.compare_objects = zend_closure_compare_objects; closure_handlers.clone_obj = zend_closure_clone; closure_handlers.get_debug_info = zend_closure_get_debug_info; closure_handlers.get_closure = zend_closure_get_closure; closure_handlers.get_gc = zend_closure_get_gc; }
El manual dice:
Además de los métodos descritos aquí, esta clase también tiene un método __invoke . Este método solo es necesario para la compatibilidad con otras clases en las que se implementa la llamada mágica, ya que este método no se utiliza al llamar a la función.
Y esto es verdad. La función
get_closure para un cierre no devuelve
__invoke , sino su función a partir de la cual se creó el cierre.
Puede estudiar las fuentes con más detalle usted mismo: el archivo
zend_closure.c , y pasaremos al siguiente código de operación.
DECLARE_LAMBDA_FUNCTION
Pero este es un código de operación exclusivo para circuitos y ya no funciona con nada. Bajo el capó del procesador, hay tres operaciones principales:
- Se busca un puntero a una función compilada, que será la esencia del cierre.
- Se define el contexto para crear el cierre (en otras palabras, esto ).
- En base a los dos primeros puntos, se crea un objeto de clase Cierre .
Y aquí en este lugar comienzan noticias no muy agradables.
Entonces, ¿qué hay de malo con las funciones anónimas?
Crear un cierre es una operación más difícil que crear un objeto ordinario. No solo se llama el mecanismo estándar para crear un objeto, sino que también agrega una cierta cantidad de lógica, la más desagradable de las cuales es copiar toda la matriz de códigos de operación de su función al cuerpo del cierre. Esto en sí mismo no es tan aterrador, pero exactamente hasta que comience a usarlo "incorrectamente".
Para comprender exactamente dónde esperan los problemas, analizaremos los casos en que se crea un cierre.
El cierre se recrea:
a) en cada procesamiento del
código de operación DECLARE_LAMBDA_FUNCTION.
Intuitivamente: exactamente el caso en el que el cierre se ve bien, pero de hecho se creará un nuevo objeto de cierre en cada iteración del bucle.
foreach($values as $value){ doSomeStuff($value, function($args) { closureBody }); }
b) cada vez que se
llama a los métodos
bind y
bindTo :
Aquí el cierre se volverá a crear también en cada iteración.
$closure = function($args) { closureBody }; foreach($objects as $object){ $closure->bindTo($object); $object->doSomeStuff($closure); }
c) cada vez que se
llama al método de
llamada , si se utiliza un generador como función. Y si no es un generador, sino una función ordinaria, solo se ejecuta la parte que copia la matriz de códigos de operación. Tales cosas
Conclusiones
Si el rendimiento no es importante para usted a toda costa, entonces las funciones anónimas son convenientes y agradables. Y si es importante, entonces probablemente no valga la pena.
En cualquier caso, ahora sabe que los cierres y los ciclos, si no están preparados correctamente, son una combinación.
Gracias por su atencion!