Escola de Magia PHP

O que é mágica em PHP? Isso geralmente significa métodos como _construct() ou __get() . Os métodos mágicos no PHP são brechas que ajudam os desenvolvedores a fazer coisas incríveis. A rede está cheia de instruções para usá-los, com as quais você provavelmente está familiarizado. Mas e se dissermos que você nem viu a verdadeira magia? Afinal, quanto mais lhe parece que você sabe tudo, mais a ilude.



Vamos abandonar a estrutura estabelecida das regras de OOP e tornar o impossível possível na escola de magia do PHP. O principal e primeiro professor de mágica da escola é Alexander Lisachenko ( NightTiger ). Ele ensinará o pensamento mágico e talvez você goste de métodos mágicos, maneiras não padronizadas de acessar propriedades, contextos de mudança, programação orientada a aspectos e filtros de fluxo.


Alexander Lisachenko é o chefe do departamento de desenvolvimento web e arquitetura da Alpari. O autor e desenvolvedor líder da estrutura orientada a aspectos Go! Aop . Palestrante em conferências internacionais sobre PHP.

No bom filme "A Ilusão da Decepção", há uma frase:
"Quanto mais perto você está, menos você vê."

O mesmo pode ser dito sobre o PHP, como um truque de mágica que permite fazer coisas incomuns. Mas, antes de tudo, foi criado para enganá-lo: "... uma ação que se destina a enganar, seja como uma maneira de enganar alguém, seja como uma piada ou forma de entretenimento".

Se juntarmos o PHP e tentarmos escrever algo mágico nele, provavelmente eu o enganarei. Vou fazer algum truque, e você ficará pensando por um longo tempo por que isso acontece. Isso ocorre porque o PHP é uma linguagem de programação conhecida por suas coisas incomuns.

Equipamento mágico


O que precisamos de equipamentos mágicos? Métodos dolorosamente familiares.

__construct(), __destruct(), __clone(),
__call(), __callStatic(),
__get(), __set(), __isset(), __unset(),
__sleep(), __wakeup(),
__toString(), __invoke(), __set_state(),
__debugInfo()


Vou anotar o último método separadamente - com ele você pode acionar coisas incomuns. Mas isso não é tudo.

  • declare(ticks=1) .
  • debug_backtrace() . Este é o nosso companheiro para entender onde estamos no código atual, para ver quem nos chamou e por quê, com quais argumentos. Útil para decidir não seguir toda a lógica.
  • unset(), isset() . Parece que nada de especial, mas esses designs ocultam muitos truques, que consideraremos mais adiante.
  • by-reference passing . Quando passamos algum objeto ou variável por referência, devemos esperar que alguma mágica aconteça inevitavelmente com você.
  • bound closures . Nos fechamentos e no fato de que eles podem se vincular, você pode criar muitos truques.
  • A API de reflexão ajuda a levar a reflexão a um novo nível.
  • API do StreamWrapper.

O equipamento está pronto - vou lembrar a primeira regra da magia.

Seja sempre o cara mais inteligente da sala.

Truque # 1. Comparação impossível


Vamos começar com o primeiro truque, que chamo de comparação impossível.
Dê uma olhada no código e veja se isso pode acontecer no PHP.



Existe uma variável, declaramos seu valor e, de repente, ela não é igual a si mesma.

Não é um número


Existe um artefato mágico como NaN - Não é um número.



Sua característica surpreendente é que não é igual a si mesmo. E este é o nosso primeiro truque: use NaN para confundir um amigo. Mas o NaN não é a única solução para esta tarefa.

Usamos constantes




O truque é que podemos declarar false como true para namespace e comparar. O desenvolvedor azarado se perguntará por um longo tempo por que existe true e não é false .

Handler


O próximo truque é a artilharia mais poderosa do que as duas opções anteriores. Vamos ver como isso funciona.



O truque é baseado na função tick_function e, como mencionei, declare(ticks=1) .

Como tudo isso funciona imediatamente? Primeiro, declaramos alguma função e, por referência, recebe o parâmetro isMagic e, em seguida, tenta alterar esse valor para true . Depois que declaramos declare(ticks=1) , o interpretador PHP chama register_tick_function - callback após cada operação elementar. Nesse retorno de chamada, podemos alterar o valor que anteriormente era false para true . A magia!

Truque # 2. Expressões mágicas


Tomemos um exemplo no qual duas variáveis ​​são declaradas. Um deles é false , o outro é true . Fazemos isBlack e isWhite e var_dump o resultado. Qual você acha que será o resultado?



Operadores prioritários . A resposta correta é false , porque no PHP existe algo como "precedência do operador".



Surpreendentemente, o operador lógico or menos prioridade que a operação de atribuição. Portanto, simplesmente atribuir false ocorre. Em isWhite pode haver qualquer outra expressão que será executada se a primeira parte falhar.

Expressões mágicas


Dê uma olhada no código abaixo. Há alguma classe que contém o construtor e alguma fábrica, cujo código será mais adiante.



Preste atenção na última linha.

 $value = new $factory->build(Foo::class); 

Existem várias opções que podem acontecer.

  • $factory será usado como o nome da classe new ;
  • haverá um erro de análise;
  • a chamada $factory->build() será usada, o valor do qual essa função retornará, o resultado será new ;
  • o valor da propriedade $factory->build será usado para construir a classe.

Vamos conferir a última ideia. Na classe $factory , declaramos uma série de funções mágicas. Eles escreverão o que fazemos: chamar a propriedade, chamar o método ou até tentar chamar invoke no objeto.

 class Factory { public function _get($name) {echo "Getting property {$name}"; return Foo::class;} public function _call($name, $arg) {echo "Calling method {$name}"; return Foo::class;} public function _invoke($name) {echo "Invoking {$name}"; return Foo::class;} } 

A resposta correta: não estamos chamando um método, mas uma propriedade. Após $factory->build existe um parâmetro para o construtor, que passaremos para esta classe.

A propósito, neste framework eu implementei um recurso chamado "interceptando a criação de novos objetos" - você pode "bloquear" esse design.

Fenda do analisador


O exemplo a seguir é para o próprio analisador PHP. Você já tentou chamar funções ou atribuir variáveis ​​dentro de chaves?



Eu tenho esse truque no Twitter, ele funciona extremamente fora do padrão.

 $result = ${'_' . !$_=getCallback()}(); $_=getCallback(); // string(5) "hello" !$_=getCallback()}(); // bool(false) '_'.!$_=getCallback()}(); // string(1) "_" ${'_'.!$_=getCallback()}(); // string(5) "hello" 

Primeiro, atribuímos o valor da expressão à variável denominada _ (sublinhado). Já temos uma variável, tentamos inverter logicamente seu valor e ficamos false - a linha é lançada como se fosse true . Em seguida, colamos tudo no nome da variável, através da qual giramos dentro dos colchetes.

O que é magia? É um entretenimento que nos permite sentir inspiração, incomum, dizer: “O quê? Então foi possível ?!

Truque # 3. Quebrando as regras


O que eu mais gosto no PHP é que você pode quebrar as regras que todo mundo cria na tentativa de ser super seguro. Existe um design chamado "classe selada" que possui um construtor privado. Sua tarefa como aluno do mágico é criar uma instância dessa classe.



Considere três opções de como fazer isso.

Solução alternativa


A primeira maneira é a mais óbvia. Deve ser familiar para todos os desenvolvedores - essa é a API padrão que o idioma nos oferece.



A construção newInstanceWithoutConstructor permite ignorar as restrições de idioma que o construtor é privado e criar uma instância da classe ignorando todas as nossas declarações de construtor privado.

A opção está funcionando, simples, não requer nenhuma explicação.

Curto-circuito


A segunda opção requer mais atenção e habilidade. Uma função anônima é criada, que então se liga ao águia-pescadora dessa classe.



Aqui estamos dentro da classe e podemos chamar com segurança métodos privados. Usamos isso chamando new static do contexto de nossa classe.



Desserialização


A terceira opção é a mais avançada, na minha opinião.



Se você escrever uma linha específica em um formato específico, substitua certos valores - nossa classe será exibida.



Após a desserialização, obtemos nossa instance .

pacote doutrina / instanciador


A magia geralmente se torna uma estrutura ou biblioteca documentada - por exemplo, na doutrina / instanciador tudo isso é implementado. Podemos criar qualquer objeto com qualquer código.

 composer show doctrine/instantiator --all name : doctrine/instantiator descrip. : A small, lightweight utility to instantiate objects in PHP without invoking their constructors keywords : constructor, instantiate type : library license : MIT License (MIT) 

Truque # 4. Interceptando o acesso à propriedade


As nuvens estão se reunindo: a classe é secreta, as propriedades e o construtor são privados e também o retorno de chamada.

 class Secret { private $secret = 42; private function _construct() { echo 'Secret is: ', $this->secret; } private function onPropAccess(string $name) { echo "Accessing property {$name}"; return 100500; } } // How to create a secret instance and intercept the secret value? 

Nossa tarefa, como assistentes, é de alguma forma chamar um retorno de chamada.

Adicionando a mágica ... getter


Adicione uma pitada de mágica para fazê-la funcionar.



Esta pitada de magia é uma mágica. Chama uma função, e até agora nada de terrível aconteceu. Mas usaremos o truque anterior e criaremos uma instância desse objeto ignorando a construção privada.



Agora precisamos, de alguma forma, ligar para retorno de chamada.

"Desativar" dentro do circuito


Para fazer isso, crie um fechamento. Dentro do fechamento que está no escopo da classe, excluímos essa variável com a função unset() .



unset permite excluir temporariamente uma variável, o que permitirá que nosso método get mágico seja chamado.

Chamamos o construtor privado


Como temos um construtor privado que exibe echo , você pode simplesmente obter esse construtor, torná-lo acessível chamando-o.



Então nossa classe secreta se desfez em pedaços.



Recebemos uma mensagem de que nós:

  • interceptado;
  • retornou algo completamente diferente.

pacote leedavis / altr-ego


Muita mágica já está documentada. O pacote altr-ego está apenas fingindo ser seu componente.

 composer show leedavis81/altr-ego --all name : leedavis81/altr-ego descrip. : Access an objects protected / private properties and methods keywords : php, break scope versions : dev-master, v1.0.2, v1.0.1, v1.0.0 type : library license : MIT License (MIT) 

Você pode criar um de seus objetos e anexar um segundo a ele. Isso permitirá alterações nas instalações. Ele mudará obedientemente e cumprirá todos os seus desejos.

Truque # 5. Objetos imutáveis ​​em PHP


Existem objetos imutáveis ​​no PHP? Sim, e muito, muito tempo.

 namespace Magic { $object = (object) [ "\0Secret\0property" => 'test' ]; var_dump($object); } 

Basta obtê-los de uma maneira interessante. O interessante é que criamos uma matriz que possui uma chave especial . Começa com a construção \0 - esse é um caractere de zero byte e, depois de Secret , também vemos \0 .

A construção é usada no PHP para declarar uma propriedade privada dentro de uma classe. Se tentarmos converter um objeto em uma matriz, veremos as mesmas chaves. Não teremos nada além de stdClass . Ele contém uma propriedade privada da classe Secret , que é igual a test .

 object(stdClass) [1] private 'property' (Secret) => string 'test' (length=4) 

O único problema é: não é possível obter essa propriedade a partir daí. É criado, mas não está disponível.

Eu pensei que era bastante inconveniente - temos objetos imutáveis, mas você não pode usá-lo. Portanto, decidi que era hora de registrar minha decisão. Usei todo o meu conhecimento e magia disponíveis em PHP para criar uma construção baseada em todos os nossos truques de mágica.

Vamos começar com um simples - crie um DTO e tente interceptar todas as propriedades nele (veja o truque anterior).

Salvamos em um local seguro os valores que capturamos a partir daí. Eles serão inacessíveis por qualquer método: nem reflection , nem fechamento, nem outra mágica. Mas há incerteza - existe um lugar no PHP que garanta salvar algumas variáveis ​​para que nenhum programador jovem e astuto chegue lá?

Nós fornecemos um método mágico para que você possa ler esse valor. Para fazer isso, temos getters mágicos, métodos de isset mágicos que nos permitem fornecer APIs.

Vamos voltar para um local confiável e tentar pesquisar.

  • Global variables canceladas - qualquer pessoa pode alterá-las.
  • Public properties também não Public properties adequadas.
  • Protected properties menos assim, porque a classe filho será concluída.
  • Private properties . Não há confiança, porque através do fechamento ou da reflection isso pode ser alterado.
  • Private static properties podem ser tentadas, mas a reflection também é interrompida.

Parece que não há lugar para ocultar os valores das variáveis. Mas havia uma coisa mágica - Static variables in functions - essas são variáveis ​​que estão dentro das funções.

Armazenamento seguro de valores


Perguntei a Nikita Popov e Jay Watkins sobre isso no canal especial Stack Overflow.



Esta é uma função na qual uma variável estática é declarada. É possível sair de alguma forma, mudar? A resposta é não.

Nós encontramos uma pequena brecha no outro mundo de variáveis ​​protegidas e queremos usá-la.

Passagem de valores por link


Nós o usaremos de maneira não padronizada como uma propriedade do objeto. Como você não pode passar uma propriedade, usamos a transferência clássica de valores por referência.



Acontece que existe uma classe na qual existe um método callStatic mágico e a variável Static é declarada nele. Em qualquer chamada para alguma função, passamos o valor da variável do objeto Imutável por referência a todos os nossos métodos aninhados. Então nós meio que fornecemos contexto.

Salvar estado


Vamos ver como o estado é salvo.



É bem simples. Para o objeto transferido, usamos a função spl_object_id , que retorna um identificador separado para cada instância. O State que já obtivemos do objeto está tentando salvar lá. Nada de especial.

Aplique o estado do objeto


Aqui, novamente, há uma construção para a passagem de valores por referência e propriedades unset . Anulamos todas as propriedades atuais, salvando-as anteriormente na variável State , e configuramos esse contexto para o objeto. O objeto não contém mais nenhuma propriedade, mas apenas seu identificador, que é declarado usando spl_object_id e é anexado a esse objeto enquanto ele ainda está ativo.



Obter Estado


Então tudo é simples.



Obtemos esse contexto no getter mágico e chamamos nossa propriedade a partir dele. Agora, ninguém e nada podem alterar o valor após a conexão desta característica.



Todos os métodos mágicos são substituídos e implementam a imutabilidade do objeto.



lisachenko / de objeto imutável


Como esperado, tudo é imediatamente formalizado na biblioteca e pronto para uso.

 composer show /immutable-object --all name : /immutable-object descrip. : Immutable object library keywords : versions : * dev-master type : library license : MIT License (MIT) 

A biblioteca parece bem simples. Nós o conectamos e criamos nossa classe. Possui propriedades diferentes: privada, protegida e pública. Nós conectamos o ImmutableTrait .



Depois disso, você pode iniciar o objeto uma vez - veja seu valor. Realmente é salvo lá e até parece um verdadeiro DTO.

 object (MagicObject) [3] public 'value' => int 200 

Mas se tentarmos mudar, por exemplo, assim ...



... e imediatamente receba uma fatal exception . Não podemos alterar uma propriedade porque ela é Imutável. Como assim?



Se você se envolver em um desafio emocionante e tentar rebaixá-lo, poderá obter o seguinte.



Este é o meu presente. Assim que você tentar falhar dentro do PHPStorm nessa classe, ele interromperá instantaneamente a execução do seu comando. Eu não quero que você mergulhe nesse código - é muito perigoso. Ele avisará que não há nada a fazer.

Truque # 6. Processamento de threads


Considere o design.

 include 'php://filter/read=string.toupper/resource=magic.php'; 

Há algo de mágico: um filtro PHP, read , no final, algum tipo de arquivo magic.php está conectado . Este arquivo parece bem simples.

 <?php echo 'Hello, world!' 

Observe que o caso é diferente. No entanto, se "preenchermos" o arquivo por meio de nosso design, obtemos o seguinte:

 HELLO, WORLD! 

O que aconteceu naquele momento? O uso da construção do filtro PHP em include permite conectar qualquer filtro, incluindo o seu, para analisar o código-fonte. Você está gerenciando tudo neste código-fonte. Você pode remover final de classes e métodos, tornar públicas as propriedades - qualquer coisa que você possa pôr em marcha através dessa coisa.

Parte da minha estrutura de aspecto é baseada nisso. Quando sua classe está conectada, ela a analisa e a transforma no que será executado.

Pronto para uso no PHP já existe um monte de filtros prontos.

 var_dump(stream_get_filters()); array (size=10) 0 => string 'zlib.*' (length=6) 1 => string 'bzip2.*' (length=7) 2 => string 'convert.iconv.*' (length=15) 3 => string ' string.rotl3' (length=12) 4 => string 'string.toupper' (length=14) 5 => string 'string.tolower' (length=14) 6 => string 'string.strip_tags' (length=17) 7 => string 'convert.*' (length=9) 8 => string 'consumed' (length=8) 9 => string 'dechunk' (length=7) 

Eles permitem "compactar" o conteúdo, traduzi-lo para maiúsculas ou minúsculas.

Os principais truques que eu queria mostrar acabaram. Agora vamos para a quintessência de tudo o que posso fazer - programação orientada a aspectos.

Truque # 7. Programação Orientada a Aspectos


Observe este código e pense se é bom ou ruim do seu ponto de vista.



O código parece ser bastante adequado. Ele verifica os direitos de acesso, realiza o registro, cria um usuário, persiste, tenta capturar uma exception .

Se você observar todo esse macarrão, ele será repetido em cada um de nossos métodos, e a única coisa valiosa aqui é que está marcado em verde.



Tudo o resto: "preocupações secundárias" ou "preocupações transversais" são funcionalidades transversais.

OOP convencional torna impossível copiar todas essas estruturas com copiar e colar, removê-las e removê-las em algum lugar. Isso é ruim porque você precisa repetir. Eu gostaria que o código sempre parecesse limpo e arrumado.



Para que não contenha log (que seja aplicado de alguma forma) e não contenha security .



Então é isso, incluindo o registro ...



... e uma verificação de segurança, ...



... realizado por si só.

E é possível.

Glossário "Aspecto"


Há uma coisa chamada Aspect. Este é um exemplo simples que verifica as permissões. Graças a ele, você pode ver algumas anotações, que também são destacadas pelo plugin do PhpStorm. "Aspecto" declara expressões SQL para quais pontos do código aplicar esta condição. Abaixo, por exemplo, queremos aplicar um fechamento a todos os métodos públicos da classe UserService .



Obtemos o fechamento usando o método $invocation .



Esse é algum tipo de invólucro no topo do método de reflection que contém mais argumentos.

Além disso, neste retorno de chamada para cada método, você pode verificar os direitos de acesso necessários, isso é chamado de "Conselho".



Dizemos o idioma: "Caro PHP, aplique esse método antes de cada chamada ao método público da classe UserService " UserService Apenas uma linha, mas muito útil.

Aspecto vs Ouvinte de evento


Para tornar mais claro, fiz uma comparação do Aspect com o Event Listener. Muitos trabalham no Symfony e sabem o que é o Event Dispatcher.



Eles são semelhantes, pois passamos algum tipo de dependência, por exemplo, AutorizationChecker , e declaramos onde aplicar nesse caso. No caso do Listener, é um SubscrabingEvent chamado UserCreate e, no caso da Aspect SubscrabingEvent todas as chamadas para métodos públicos do UserService . callback : - .

, .

, , Aspects .



. , PHP- .



, . , , OPcache.

. Composer . Go! AOP, Composer , .



Aspects , Aspects . .

.

PHP-Parser . , , . , , PHP-Parser . .



. , goaop/parser-reflection .



AST- , . , , , .

.



, , Aspect .



, .



, , . — , - , .



, . , .

Aspect MOCK. «», , . .

joinPoint . - joinPoint : , , .

O que vem a seguir?


.

OPcache preloading for AOP Core . AOP- . 10 . Bootstrapping , PHP.

FFI integration to modify binary opcodes . , , . PHP-opcodes, .bin . FFI .

Modifying PHP engine internal callbacks PHP- userland. PHP . FFI PHP userland, , , . .

, .

#8. goaop/framework


, GitHub .

 composer show goaop/framework --all name : goaop/framework descrip. : Framework for aspect-oriented programming in PHP. keywords : php, aop, library, aspect versions : dev-master, 3.0.x-dev, 2.x-dev, 2.3.1, … type : library license : MIT License 

, PhpStorm.



, Pointcuts. , , . — , , .

Trick #9.


. , .

: , - . fastcgi_finish_request . , , , - callback — .

?

, Deffered , , .



Aspect , , , Deffered , .



Aspect : , , , callback, , callback. .

React , promise . , - , - , .

, .



shutdown_function Aspect . , callback, , , , callback onPhpTerminate . fastcgi_finish_request : «, , , , ». .

, sendPushNotification .



, - — 2 .



, , , 2 .

, Deferred .



, . - , , .

Só isso. , - . , .

PHP Russia 2020 . — . telegram- , PHP Russia 2020.

Source: https://habr.com/ru/post/pt478618/


All Articles