¿Qué es la magia en PHP? Esto generalmente significa métodos como
_construct()
o
__get()
. Los métodos mágicos en PHP son lagunas que ayudan a los desarrolladores a hacer cosas increíbles. La red está llena de instrucciones para usarlas, con las cuales probablemente esté familiarizado. Pero, ¿y si decimos que ni siquiera has visto la verdadera magia? Después de todo, cuanto más te parezca que lo sabes todo, más magia se te escapa.

Dejemos caer el marco establecido de las reglas de OOP y hagamos lo imposible posible en la escuela de magia de PHP. El principal y primer maestro mágico de la escuela es
Alexander Lisachenko (
NightTiger ). Enseñará pensamiento mágico y quizás le encantarán los métodos mágicos, las formas no estándar de acceder a las propiedades, los contextos cambiantes, la programación orientada a aspectos y los filtros de flujo.
Alexander Lisachenko es el jefe del departamento de desarrollo web y arquitectura en Alpari. El autor y desarrollador principal del marco orientado a aspectos
Go! Aop Ponente en conferencias internacionales sobre PHP.
En la buena película "La ilusión del engaño" hay una frase:
"Cuanto más cerca estás, menos ves".
Lo mismo puede decirse de PHP, como un truco de magia que le permite hacer cosas inusuales. Pero antes que nada, fue creado para engañarte: "... una acción que pretende engañar, ya sea como una forma de engañar a alguien, o como una broma o una forma de entretenimiento".
Si tomamos PHP juntos y tratamos de escribir algo mágico en él, lo más probable es que te engañe. Haré algún truco y te preguntarás durante mucho tiempo por qué sucede esto. Eso es porque PHP es un lenguaje de programación conocido por sus cosas inusuales.
Equipo mágico
¿Qué necesitamos del equipo mágico? Métodos dolorosamente familiares.
__construct(), __destruct(), __clone(),
__call(), __callStatic(),
__get(), __set(), __isset(), __unset(),
__sleep(), __wakeup(),
__toString(), __invoke(), __set_state(),
__debugInfo()
Notaré el último método por separado: con él puedes poner en marcha cosas inusuales. Pero eso no es todo.
declare(ticks=1)
.
debug_backtrace()
. Este es nuestro compañero para comprender dónde estamos en el código actual, para ver quién nos llamó y por qué, con qué argumentos. Útil para decidir no seguir toda la lógica.
unset(), isset()
. Parece que nada especial, pero estos diseños esconden muchos trucos, que consideraremos más a fondo.
by-reference passing
. Cuando pasamos algún objeto o variable por referencia, deberíamos esperar que algo de magia te suceda inevitablemente.
bound closures
. Sobre los cierres y el hecho de que se pueden unir, puedes construir muchos trucos.
- Reflection API ayuda a llevar la reflexión a un nuevo nivel.
- StreamWrapper API.
El equipo está listo. Recordaré la primera regla de la magia.
Sé siempre el chico más listo de la sala.
Truco # 1. Comparación imposible
Comencemos con el primer truco, al que llamo Comparación imposible.
Observe de cerca el código y vea si esto puede suceder en PHP.

Hay una variable, declaramos su valor y, de repente, no es igual a sí misma.
No es un número
Existe un artefacto mágico como NaN - No es un número.

Su característica sorprendente es que no es igual a sí mismo. Y este es nuestro primer truco: usar NaN para confundir a un amigo. Pero NaN no es la única solución para esta tarea.
Utilizamos constantes

El truco es que podemos declarar
false
como
true
para el
namespace
de
namespace
y comparar. El desafortunado desarrollador se preguntará durante mucho tiempo por qué hay algo
true
y no
false
.
Manejador
El siguiente truco es una artillería más poderosa que las dos opciones anteriores. Veamos como funciona.

El truco se basa en
tick_function
y, como mencioné,
declare(ticks=1)
.
¿Cómo funciona todo fuera de la caja? Primero, declaramos alguna función, y por referencia toma el parámetro
isMagic
, y luego trata de cambiar este valor a
true
. Después de declarar
declare(ticks=1)
, el intérprete PHP llama a
register_tick_function
- callback después de cada operación elemental. En esta devolución de llamada, podemos cambiar el valor que anteriormente era
false
a
true
. La magia!
Truco # 2. Expresiones mágicas
Tome un ejemplo en el que se declaran dos variables. Uno de ellos es
false
, el otro es
true
. Hacemos
isBlack
e
isWhite
y var_dump el resultado. ¿Cuál crees que será el resultado?
Operadores prioritarios . La respuesta correcta es
false
, porque en PHP existe una “precedencia de operador”.

Sorprendentemente, el operador lógico
or
menos prioridad que la operación de asignación. Por lo tanto, simplemente se asigna la asignación de
false
. En
isWhite
puede haber cualquier otra expresión que se ejecutará si falla la primera parte.
Expresiones mágicas
Echa un vistazo al código a continuación. Hay alguna clase que contiene el constructor, y alguna fábrica, cuyo código será más adelante.

Presta atención a la última línea.
$value = new $factory->build(Foo::class);
Hay varias opciones que pueden suceder.
$factory
se usará como el nombre de clase new
;
- habrá un error de análisis;
- se usará la llamada
$factory->build()
, cuyo valor devolverá esta función, el resultado será new
;
- el valor de la propiedad
$factory->build
se usará para construir la clase.
Veamos la última idea. En la clase
$factory
, declaramos una serie de funciones mágicas. Escribirán lo que hacemos: llamar a la propiedad, llamar al método o incluso intentar
invoke
en el 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;} }
La respuesta correcta: no estamos llamando a un método, sino a una propiedad. Después de
$factory->build
hay un parámetro para el constructor, que pasaremos a esta clase.
Por cierto, en este marco he implementado una característica llamada "interceptar la creación de nuevos objetos": puede "bloquear" este diseño.
Escapatoria del analizador
El siguiente ejemplo es para el analizador PHP en sí. ¿Alguna vez has intentado llamar a funciones o asignar variables dentro de llaves?

Tengo este truco en Twitter, funciona extremadamente no estándar.
$result = ${'_' . !$_=getCallback()}(); $_=getCallback();
Primero, asignamos el valor de la expresión a la variable llamada
_
(guión bajo). Ya tenemos una variable, tratamos de invertir lógicamente su valor y obtenemos
false
: la línea se convierte como
true
. Luego lo pegamos todo en el nombre de la variable, a través del cual giramos dentro de las llaves.
Que es la magia Este es un entretenimiento que nos permite sentirnos inspirados, inusuales, decir: “¿Qué? ¿Entonces fue posible? ”
Truco # 3. Rompiendo las reglas
Lo que me gusta de PHP es que puedes romper las reglas que todos crean en un intento de ser súper seguro. Hay un diseño llamado "clase sellada" que tiene un constructor privado. Su tarea como estudiante del mago es crear una instancia de esta clase.

Considere tres opciones sobre cómo hacer esto.
Solución alternativa
La primera forma es la más obvia. Debe ser familiar para todos los desarrolladores: esta es la API estándar que nos ofrece el lenguaje.

La construcción
newInstanceWithoutConstructor
permite omitir las restricciones de idioma de que el constructor es privado y crear una instancia de la clase sin pasar por todas nuestras declaraciones de constructor privado.
La opción está funcionando, simple, no requiere ninguna explicación.
Cortocircuito
La segunda opción requiere más atención y habilidad. Se crea una función anónima, que luego se une al águila pescadora de esa clase.

Aquí estamos dentro de la clase y podemos llamar con seguridad a métodos privados. Usamos esto llamando
new static
desde el contexto de nuestra clase.

Deserialización
La tercera opción es la más avanzada, en mi opinión.

Si escribe una línea específica en un formato específico, sustituya ciertos valores allí; nuestra clase resultará.

Después de la deserialización, obtenemos nuestra
instance
.
paquete de doctrina / instanciador
La magia a menudo se convierte en un marco documentado o biblioteca, por ejemplo, en
doctrina / instanciador, todo esto se implementa. Podemos crear cualquier objeto con cualquier 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)
Truco # 4. Interceptar el acceso a la propiedad
Las nubes se están acumulando: la clase es secreta, las propiedades y el constructor son privados, y también la devolución de llamada.
class Secret { private $secret = 42; private function _construct() { echo 'Secret is: ', $this->secret; } private function onPropAccess(string $name) { echo "Accessing property {$name}"; return 100500; } }
Nuestra tarea, como magos, es de alguna manera llamar a una devolución de llamada.
Agregando la magia ... getter
Agrega una pizca de magia para que funcione.

Esta pizca de magia es un
getter
mágico. Llama a una función, y hasta ahora no ha sucedido nada terrible. Pero usaremos el truco anterior y crearemos una instancia de este objeto sin pasar por la construcción privada.

Ahora necesitamos llamar de alguna manera a la devolución de llamada.
"Desarmado" dentro del circuito
Para hacer esto, cree un cierre. Dentro del cierre que está dentro del alcance de la clase, eliminamos esta variable con la función
unset()
.

unset
permite excluir temporalmente una variable, lo que permitirá
get
a nuestro método magic
get
.
Llamamos al constructor privado
Como tenemos un constructor privado que muestra
echo
, puede obtener este constructor, hacerlo accesible llamándolo.

Entonces nuestra clase secreta se desmoronó en pedazos.

Recibimos un mensaje que nosotros:
- interceptado
- devolvió algo completamente diferente.
paquete leedavis / altr-ego
Mucha magia ya está documentada. El paquete
altr-ego solo pretende ser tu 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)
Puede crear uno de sus objetos y adjuntarle un segundo. Esto permitirá cambios en la instalación. Él cambiará obedientemente y cumplirá todos tus deseos.
Truco # 5. Objetos inmutables en PHP
¿Hay objetos inmutables en PHP? Sí, y mucho, mucho tiempo.
namespace Magic { $object = (object) [ "\0Secret\0property" => 'test' ]; var_dump($object); }
Solo consíguelos de una manera interesante. Lo interesante es que creamos una matriz que tiene una
clave especial . Comienza con la construcción
\0
: este es un carácter de cero bytes, y después de
Secret
también vemos
\0
.
La construcción se usa en PHP para declarar una propiedad privada dentro de una clase. Si intentamos lanzar un objeto a una matriz, veremos las mismas claves. No tendremos más que
stdClass
. Contiene una propiedad privada de la clase
Secret
, que es igual a
test
.
object(stdClass) [1] private 'property' (Secret) => string 'test' (length=4)
El único problema es que no puede obtener esta propiedad desde allí. Está creado, pero no está disponible.
Pensé que era bastante inconveniente: tenemos objetos inmutables, pero no puedes usarlo. Por lo tanto, decidí que era hora de presentar mi decisión. Utilicé todo mi conocimiento y magia que está disponible en PHP para crear una construcción basada en todos nuestros trucos de magia.
Comencemos con uno simple: cree un DTO e intente interceptar todas las propiedades que contiene (consulte el truco anterior).
Guardamos en un lugar seguro los valores que capturamos desde allí. Serán inaccesibles por cualquier método: ni
reflection
, ni cierres, ni otra magia. Pero hay incertidumbre: ¿existe tal lugar en PHP que garantice guardar algunas variables para que ningún programador joven astuto llegue allí?
Proporcionamos un método mágico para que pueda leer este valor. Para hacer esto, tenemos
isset
mágicos, métodos mágicos que nos permiten proporcionar API.
Volvamos a un lugar confiable e intentemos buscar.
Global variables
cancelan; cualquiera puede cambiarlas.
Public properties
tampoco Public properties
adecuadas.
Protected properties
más o menos, porque la clase secundaria pasará.
Private properties
No hay confianza, porque a través del cierre o la reflection
se puede cambiar.
- Se pueden probar
Private static properties
, pero la reflection
también se rompe.
Parece que no hay ningún lugar para ocultar los valores de las variables. Pero había algo mágico:
Static variables in functions
, estas son variables que están dentro de las funciones.
Almacenamiento seguro de valores
Le pregunté a
Nikita Popov y
Jay Watkins sobre esto en el canal especial Stack Overflow.

Esta es una función dentro de la cual se declara una variable estática. ¿Es posible salir de alguna manera, cambiarlo? La respuesta es no.
Hemos encontrado un pequeño vacío en el otro mundo de variables protegidas y queremos usarlo.
Paso de valores por enlace
Lo usaremos de manera no estándar como una propiedad del objeto. Pero no puede pasar una propiedad, por lo que usamos la transferencia clásica de valores por referencia.

Resulta que hay una clase en la que hay un método mágico
callStatic
, y la variable
Static
se declara en él. En cualquier llamada a alguna función, pasamos el valor de la variable del objeto Inmutable por referencia a todos nuestros métodos anidados. Entonces, de alguna manera proporcionamos contexto.
Guardar estado
Veamos cómo se guarda el estado.

Es muy simple Para el objeto transferido, usamos la función
spl_object_id
, que devuelve un identificador separado para cada instancia. El
State
que ya obtuvimos del objeto está tratando de guardar allí. Nada especial
Aplicar el estado del objeto
Aquí de nuevo hay una construcción para pasar valores por referencia y propiedades no
unset
. Desarmamos todas las propiedades actuales, habiéndolas guardado previamente en la variable
State
, y configuramos este contexto para el objeto. El objeto ya no contiene ninguna propiedad, sino solo su identificador, que se declara usando
spl_object_id
y se adjunta a este objeto mientras aún está vivo.

Obtener estado
Entonces todo es simple.

Obtenemos este contexto en el
getter
mágico y llamamos a nuestra propiedad desde él. Ahora nadie ni nada puede cambiar el valor después de que este
Trait
conectado.

Todos los métodos mágicos se anulan e implementan la inmutabilidad del objeto.

lisachenko / objeto inmutable
Como se esperaba, todo se formaliza inmediatamente en la biblioteca y está listo para usar.
composer show /immutable-object --all name : /immutable-object descrip. : Immutable object library keywords : versions : * dev-master type : library license : MIT License (MIT)
La biblioteca se ve bastante simple. Lo conectamos y creamos nuestra clase. Tiene diferentes propiedades: privadas, protegidas y públicas. Conectamos
ImmutableTrait
.

Después de eso, puede iniciar el objeto una vez, ver su valor. Realmente se guarda allí e incluso parece un verdadero DTO.
object (MagicObject) [3] public 'value' => int 200
Pero si tratamos de cambiarlo, por ejemplo, así ...

... entonces inmediatamente recibo una
fatal exception
. No podemos cambiar una propiedad porque es inmutable. ¿Cómo es eso?

Si te involucras en un desafío emocionante e intentas degradarlo, obtienes lo siguiente.

Este es mi regalo Tan pronto como intente fallar dentro de PHPStorm dentro de esta clase, se detendrá instantáneamente la ejecución de su comando. No quiero que profundices en este código, es demasiado peligroso. Él advertirá que no hay nada que hacer.
Truco # 6. Procesamiento de hilos
Considera el diseño.
include 'php://filter/read=string.toupper/resource=magic.php';
Hay algo mágico: un filtro PHP,
read
, al final
está conectado algún tipo de
archivo magic.php. Este archivo parece bastante simple.
<?php echo 'Hello, world!'
Tenga en cuenta que el caso es diferente. Sin embargo, si "llenamos" el archivo a través de nuestro diseño, obtenemos esto:
HELLO, WORLD!
¿Qué pasó en ese momento? El uso de la construcción del filtro PHP en
include
permite conectar cualquier filtro, incluido el suyo, para analizar el código fuente. Está administrando todo en este código fuente. Puede eliminar el
final
de las clases y los métodos, hacer públicas las propiedades, cualquier cosa que pueda analizar.
Parte de mi marco de aspecto se basa en esto. Cuando su clase está conectada, la analiza y la transforma en lo que se ejecutará.
Fuera de la caja en PHP ya hay un montón de filtros listos para usar.
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)
Le permiten "comprimir" el contenido, traducirlo a mayúsculas o minúsculas.
Los principales trucos que quería mostrar han terminado. Ahora pasemos a la quintaesencia de todo lo que puedo hacer: programación orientada a aspectos.
Truco # 7. Programación Orientada a Aspectos
Mire este código y piense si es bueno o malo desde su punto de vista.

El código parece ser bastante adecuado. Comprueba los derechos de acceso, realiza el registro, crea un usuario, persiste, intenta detectar
exception
.
Si observa todos estos fideos, se repite en cada uno de nuestros métodos, y lo único valioso aquí es que está marcado en verde.

Todo lo demás: "preocupaciones secundarias" o "preocupaciones transversales" es una funcionalidad transversal.
La OOP convencional hace que sea imposible copiar todas estas estructuras con copiar y pegar, eliminarlas y sacarlas en alguna parte. Esto es malo porque tienes que repetirlo. Deseo que el código siempre se vea limpio y ordenado.

Para que no contenga registro (deje que se aplique de alguna manera) y no contenga
security
.

Eso es todo, incluido el registro ...

... y un control de seguridad, ...

... realizado por sí mismo.
Y es posible.
Glosario "Aspecto"
Hay una cosa llamada Aspecto. Este es un ejemplo simple que verifica los permisos. Gracias a él, puedes ver algunas anotaciones, que también se resaltan con el complemento para PhpStorm. "Aspecto" declara expresiones SQL a los puntos del código para aplicar esta condición. A continuación, por ejemplo, queremos aplicar un cierre a todos los métodos públicos de la clase
UserService
.

Obtenemos el cierre utilizando el método de
$invocation
.

Este es algún tipo de contenedor sobre el método de
reflection
, que contiene más argumentos.
Además en esta devolución de llamada para cada método, puede verificar los derechos de acceso necesarios, esto se llama el "Consejo".

Decimos el lenguaje: "Estimado PHP, aplique este método antes de cada llamada al método público desde la clase
UserService
"
UserService
Solo una línea, pero muy útil.
Aspecto vs oyente de eventos
Para hacerlo más claro, hice una comparación de Aspect con Event Listener. Muchos trabajan en Symfony y saben qué es Event Dispatcher.

Son similares en el sentido de que pasamos algún tipo de dependencia, por ejemplo,
AutorizationChecker
, y declaramos dónde aplicar en este caso. En el caso de Listener, este es un
SubscrabingEvent
llamado
UserCreate
, y en el caso de
Aspect
nos suscribimos a todas las llamadas a métodos públicos desde el
UserService
.
Al mismo tiempo, el contenido del controlador de devolución de llamada en sí es aproximadamente el mismo: simplemente verificamos algo y reaccionamos en consecuencia.Considere cómo funciona todo bajo el capó.El primer paso que requiere un marco de aspecto es el registro Aspects
.
Segunda etapa . Para manejar esto, se utiliza nuevamente el truco anterior con el filtro PHP .
Este es un componente especial que convierte el código fuente. Lo hace en secreto, pero funcionará bien en la producción, porque está integrado con OPcache.La tercera etapa . Todo está integrado en un nivel Composer
. Una vez que Go está instalado! AOP, comienza a comunicarse estrechamente conComposer
, .

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

. ,
goaop/parser-reflection .

AST- , . , , , .
.

, ,
Aspect
.

, .

, , . — , - , .

, . , .
Aspect MOCK. «», , . .
—
joinPoint
. -
joinPoint
: , , .
Que sigue
.
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
.

, . - , , .
Eso es todo. , - . , .
PHP Russia 2020 . — . telegram- , PHP Russia 2020.