
Provavelmente, deve-se começar com o fato de que uma função anônima (fechamento) no PHP não é uma função, mas um objeto da classe
Closure . Na verdade, este artigo poderia ter sido concluído, mas se alguém estiver interessado nos detalhes, bem-vindo ao gato.
Para não ser infundado:
$func = function (){}; var_dump($func); --------- object(Closure)
Olhando para o futuro, direi que esse não é realmente um objeto comum. Vamos descobrir.
Por exemplo, esse código
$func = function (){ echo 'Hello world!'; }; $func();
compila em um conjunto de códigos de operação:
line
O bloco com a descrição do corpo da função não é particularmente interessante para nós, mas no primeiro bloco existem dois códigos de
operação interessantes:
DECLARE_LAMBDA_FUNCTION e
INIT_DYNAMIC_CALL . Vamos começar com o segundo.
INIT_DYNAMIC_CALL
Esse código de operação é usado quando o compilador vê uma chamada de função em uma variável ou matriz. I.e.
$variable(); ['ClassName', 'staticMethod']();
Este não é um código de operação exclusivo específico apenas para fechamentos. Essa sintaxe também funciona para objetos chamando o método
__invoke () , para variáveis de string que contêm o nome da função (
$ a = 'funcName'; $ a (); ) e para matrizes que contêm o nome da classe e o método estático nele.
No caso de fechamento, estamos interessados em chamar uma variável com um objeto, o que é lógico.
Indo mais fundo no código da VM que processa esse código de operação, chegamos à função
zend_init_dynamic_call_object , na qual veremos o seguinte (fatia):
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; } ... }
É engraçado que a chamada familiar do método __invoke em termos de VM seja uma tentativa de chamar encerramento - get_closure .Na verdade, nesse ponto, a diferença começa no tratamento da chamada para a função anônima e o método
__invoke de um objeto regular.
No PHP, cada objeto possui um conjunto de manipuladores diferentes que definem seus métodos de utilidade e mágica.
O conjunto padrão se parece com isso 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, };
Agora estamos interessados no manipulador
get_closure . Para um objeto regular, ele aponta para a função
zend_std_get_closure , que verifica se a função
__invoke está definida para o objeto e retorna um ponteiro para ele ou um erro. Mas para a classe
Closure , que implementa funções anônimas, nessa matriz de manipuladores, quase todas as funções utilitárias são redefinidas, incluindo aquelas que controlam o ciclo de vida. I.e. embora para o usuário pareça um objeto comum, mas na verdade é um mutante com superpoderes :)
Registrar manipuladores para um objeto da classe Closure 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; }
O manual diz:
Além dos métodos descritos aqui, esta classe também possui um método __invoke . Esse método é necessário apenas para compatibilidade com outras classes nas quais a chamada mágica é implementada, pois esse método não é usado ao chamar a função.
E isso é verdade. A função
get_closure para um fechamento não retorna
__invoke , mas sua função a partir da qual o fechamento foi criado.
Você pode estudar as fontes com mais detalhes - o arquivo
zend_closure.c , e passaremos para o próximo opcode.
DECLARE_LAMBDA_FUNCTION
Mas este é um código de operação exclusivo para circuito e não está mais funcionando com nada. Sob o capô do processador, há três operações principais:
- É procurado um ponteiro para uma função compilada, que será a essência do fechamento.
- O contexto para a criação do fechamento (em outras palavras, isso ) é definido.
- Com base nos dois primeiros pontos, um objeto da classe Closure é criado .
E aqui neste lugar não começam notícias muito agradáveis.
Então, o que há de errado com funções anônimas?
Criar um fechamento é uma operação mais difícil do que criar um objeto comum. Não é apenas o mecanismo padrão para criar um objeto chamado, mas também adiciona uma certa quantidade de lógica, a mais desagradável é copiar toda a matriz de códigos de operação da sua função para o corpo do fechamento. Isso por si só não é tão assustador, mas exatamente até você começar a usá-lo "incorretamente".
Para entender exatamente onde os problemas aguardam, analisaremos os casos quando um encerramento for criado.
O fechamento é recriado:
a) em cada processamento do
código de operação DECLARE_LAMBDA_FUNCTION.
Intuitivamente - exatamente o caso em que o fechamento parece bom, mas, na verdade, um novo objeto de fechamento será criado a cada iteração do loop.
foreach($values as $value){ doSomeStuff($value, function($args) { closureBody }); }
b) sempre que os métodos
bind e
bindTo são
chamados :
Aqui, o fechamento será recriado também a cada iteração.
$closure = function($args) { closureBody }; foreach($objects as $object){ $closure->bindTo($object); $object->doSomeStuff($closure); }
c) sempre que o método de
chamada for
chamado , se um gerador for usado como uma função. E se não for um gerador, mas uma função comum, somente a parte com a cópia do conjunto de códigos de operação é executada. Essas coisas.
Conclusões
Se o desempenho não for importante para você a todo custo, as funções anônimas são convenientes e agradáveis. E se importante, então provavelmente não vale a pena.
De qualquer forma, agora você sabe que fechamentos e ciclos, se não forem preparados corretamente, são uma combinação.
Obrigado pela atenção!