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();
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; } }
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.