School of Magic PHP

What is magic in PHP? This usually means methods like _construct()



or __get()



. The magic methods in PHP are loopholes that help developers do amazing things. The network is full of instructions for using them, with which you are probably familiar. But what if we say that you have not even seen the real magic? After all, the more it seems to you that you know everything, the more magic eludes you.







Let's drop the established framework of OOP rules and make the impossible possible at the PHP school of magic. The main and first magical teacher of the school is Alexander Lisachenko ( NightTiger ). He will teach magic thinking and perhaps you will love magic methods, non-standard ways of accessing properties, changing contexts, aspect-oriented programming and stream filters.





Alexander Lisachenko is the head of the department of web development and architecture in Alpari. The author and lead developer of the aspect-oriented framework Go! Aop . Speaker at international conferences on PHP.



In the good movie "The Illusion of Deception" there is a phrase:

"The closer you are, the less you see."


The same can be said about PHP, as a magic trick that allows you to do unusual things. But first of all, it was created to deceive you: "... an action that is intended to deceive, either as a way of cheating someone, or as a joke or form of entertainment."



If we take PHP together and try to write something magical on it, most likely I will deceive you. I will do some trick, and you will be wondering for a long time why this happens. That's because PHP is a programming language known for its unusual stuff.



Magic equipment



What do we need from magic equipment? Painfully familiar methods.



__construct(), __destruct(), __clone(),

__call(), __callStatic(),

__get(), __set(), __isset(), __unset(),

__sleep(), __wakeup(),

__toString(), __invoke(), __set_state(),

__debugInfo()








I will note the last method separately - with it you can crank up unusual things. But that's not all.





The equipment is ready - I recall the first rule of magic.



Always be the smartest guy in the room.


Trick # 1. Impossible comparison



Let's start with the first trick, which I call Impossible Comparison.

Take a good look at the code and see if this can happen in PHP.







There is a variable, we declare its value, and then suddenly it is not equal to itself.



Not-a-number



There is such a magical artifact as NaN - Not-a-number.







Its amazing feature is that it is not equal to itself. And this is our first trick: use NaN to puzzle a friend. But NaN is not the only solution for this task.



We use constants







The trick is that we can declare false



as true



for namespace



and compare. The unlucky developer will wonder for a long time why there is true



and not false



.



Handler



The next trick is more powerful artillery than the two previous options. Let's see how it works.







The trick is based on tick_function



and, as I mentioned, declare(ticks=1)



.



How does it all work out of the box? First, we declare some function, and by reference it takes the isMagic



parameter, and then it tries to change this value to true



. After we declared declare(ticks=1)



, the PHP interpreter calls register_tick_function



- callback after each elementary operation. In this callback, we can change the value that was previously false



to true



. Magic!



Trick # 2. Magic expressions



Take an example in which two variables are declared. One of them is false



, the other is true



. We isBlack



and isWhite



and var_dump the result. What do you think will be the result?







Priority operators . The correct answer is false



, because in PHP there is such a thing as โ€œoperator precedenceโ€.







Surprisingly, the logical operator or



less priority than the assignment operation. Therefore, simply assigning false



occurs. In isWhite



can be any other expression that will execute if the first part fails.



Magic expressions



Take a look at the code below. There is some class that contains the constructor, and some factory, the code of which will be further.







Pay attention to the last line.



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





There are several options that can happen.





Let's check out the last idea. In the $factory



class, we declare a series of magic functions. They will write what we do: call the property, call the method, or even try to call invoke



on the object.



 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;} }
      
      





The correct answer: we are not calling a method, but a property. After $factory->build



there is a parameter for the constructor, which we will pass to this class.



By the way, in this framework I have implemented a feature called "intercepting the creation of new objects" - you can "lock" this design.



Parser loophole



The following example is for the PHP parser itself. Have you ever tried calling functions or assigning variables inside curly braces?







I got this trick on Twitter, it works extremely non-standard.



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





First, we assign the value of the expression to the variable named _



(underscore). We already have a variable, we try to logically invert its value, and we get false



- the line is casted as if to true



. Then we glue it all together in the variable name, through which we then turn inside the curly brackets.



What is magic? This entertainment, which allows us to feel inspired, unusual, say: โ€œWhat? So it was possible ?! โ€


Trick # 3. Breaking the rules



What I like about PHP is that you can break the rules that everyone creates while trying to be super secure. There is a design called a "sealed class" that has a private constructor. Your task as a student of the magician is to create an instance of this class.







Consider three options for how to do this.



Workaround



The first way is the most obvious. It should be familiar to every developer - this is the standard API that the language offers us.







The newInstanceWithoutConstructor



construct allows you to bypass the language restrictions that the constructor is private and create an instance of the class bypassing all of our private constructor declarations.



The option is working, simple, does not require any explanation.



Short circuit



The second option requires more attention and skill. An anonymous function is created, which then binds to the osprey of that class.







Here we are inside the class and can safely call private methods. We use this by calling new static



from the context of our class.







Deserialization



The third option is the most advanced, in my opinion.







If you write a specific line in a specific format, substitute certain values โ€‹โ€‹there - our class will turn out.







After deserialization, we get our instance



.



doctrine / instantiator package



Magic often becomes a documented framework or library - for example, in doctrine / instantiator all this is implemented. We can create any objects with any code.



 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)
      
      





Trick # 4. Intercepting property access



The clouds are gathering: the class is secret, the properties and constructor are private, and also callback.



 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?
      
      





Our task, as wizards, is somehow to call a callback.



Adding the magic ... getter



Add a pinch of magic to make it work.







This pinch of magic is a magical getter



. It calls a function, and so far nothing terrible has happened. But we will use the previous trick and create an instance of this object bypassing the private construct.







Now we need to somehow call callback.



"Unset" inside the circuit



To do this, create a closure. Inside the closure that is in the classโ€™s scope, we delete this variable with the unset()



function.







unset



allows you to temporarily exclude a variable, which will allow our magic get



method to be called.



We call the private constructor



Since we have a private constructor that displays echo



, you can just get this constructor, make it accessible by calling it.







So our secret class crumbled to pieces.







We received a message that we:





leedavis / altr-ego package



A lot of magic is already documented. The altr-ego package is just pretending to be your component.



 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)
      
      





You can create one of your objects and attach a second to it. This will allow changes to the facility. He will change obediently and fulfill all your wishes.



Trick # 5. Immutable objects in PHP



Are there Immutable object in PHP? Yes, and a very, very long time.



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





Just get them in an interesting way. The interesting thing is that we create an array that has a special key . It starts with the construction \0



- this is a zero byte character, and after Secret



we also see \0



.



The construct is used in PHP to declare a private property within a class. If we try to cast an object to an array, we will see the same keys. We will have nothing but stdClass



. It contains a private property from the Secret



class, which is equal to test



.



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





The only problem is - then you canโ€™t get this property from there. It is created, but not available.



I thought it was rather inconvenient - we have Immutable objects, but you cannot use it. Therefore, I decided that it was time to file my decision. I used all my knowledge and magic that is available in PHP to create a construction based on all our magic tricks.



Let's start with a simple one - create a DTO and try to intercept all the properties in it (see the previous trick).



We save in a safe place the values that we capture from there. They will be inaccessible by any methods: neither reflection



, nor closures, nor other magic. But uncertainty arises - is there such a place in PHP that would guarantee to save some variables so that no cunning young programmer gets there at all?



We provide a magic method so that you can read this value. To do this, we have magic getters



, magic isset



methods that allow us to provide APIs.



Let's go back to a reliable place and try to search.





It would seem that there is nowhere to hide the values โ€‹โ€‹of variables. But there was a magic thing - Static variables in functions



- these are variables that are inside functions.



Safe storage of values



I asked Nikita Popov and Jay Watkins about it on the special Stack Overflow channel.







This is a function within which a static variable is declared. Is there any way to get it out of it? The answer is no.



We have found a small loophole in the other world of protected variables and we want to use it.



Passing Values โ€‹โ€‹by Link



We will use it non-standardly, as a property of the object. But you cannot pass a property, so we use the classic transfer of values โ€‹โ€‹by reference.







It turns out that there is a class in which there is a magic callStatic



method, and the Static



variable is declared in it. In any call to some function, we pass the value of the variable from the Immutable object by reference to all our nested methods. So we kind of provide context.



Save state



Let's see how the state is saved.







It's pretty simple. For the transferred object, we use the spl_object_id



function, which returns a separate identifier for each instance. The State



that we already got from the object is trying to save there. Nothing special.



Apply the state of the object



Here again there is a construction for passing values โ€‹โ€‹by reference and unset



properties. We unset all current properties, having previously saved them in the State



variable, and set this context for the object. The object no longer contains any properties, but only its identifier, which is declared using spl_object_id



and is bound to this object while it is still alive.







Get State



Then everything is simple.







We get this context on the magic getter



and call our property from it. Now no one and nothing can change the value after this Trait



connected.







All magic methods are overridden and implement the immutability of the object.







lisachenko / immutable-object



As expected, everything is immediately formalized in the library and ready for use.



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





The library looks pretty simple. We connect it and create our class. It has different properties: private, protected and public. We connect ImmutableTrait



.







After that, you can initiate the object once - see its value. It really is saved there and even looks like a real DTO.



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





But if we try to change it, for example, like this ...







... then immediately get a fatal exception



. We cannot change a property because it is Immutable. How so?







If you get involved in an exciting challenge and try to debase it, you get the following.







This is my present. As soon as you try to fail inside PHPStorm inside this class, it will instantly stop the execution of your command. I do not want you to delve into this code - it is too dangerous. He will warn that there is nothing to do.



Trick # 6. Thread processing



Consider the design.



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





There is something magical: a PHP filter, read



, at the end some kind of magic.php file is connected . This file looks pretty simple.



 <?php echo 'Hello, world!'
      
      





Note that the case is different. However, if we โ€œpopulateโ€ the file through our design, then we get this:



 HELLO, WORLD!
      
      





What happened at that moment? Using the construction of the PHP filter in include



allows you to connect any filter, including yours, to analyze the source code. You are managing everything in this source code. You can remove final



from classes and methods, make properties public - anything you can crank through this thing.



Part of my aspect framework is based on this. When your class is connected, it analyzes it and transforms it into what will be executed.



Out of the box in PHP there is already a whole bunch of ready-made filters.



 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)
      
      





They allow you to "zip" the content, translate it into upper or lower case.



The main tricks that I wanted to show are over. Now let's move on to the quintessence of everything that I can do - aspect-oriented programming.



Trick # 7. Aspect Oriented Programming



Look at this code and think whether it is good or bad from your point of view.







The code seems to be quite adequate. It checks access rights, performs logging, creates a user, persists, tries to catch exception



.



If you look at all this noodles, it is repeated in each of our methods, and the valuable here is only that which is marked in green.







Everything else: โ€œsecondary concernsโ€ or โ€œcrosscutting concernsโ€ is cross-cutting functionality.



Conventional OOP does not make it possible to copy all of these structures with copy-paste, remove them and remove them somewhere. This is bad because you have to repeat it. I wish the code always looked clean and tidy.







So that it does not contain logging (let it be applied somehow) and does not contain security



.







So thatโ€™s all, including logging ...







... and security checks, ...







... performed by itself.



And it is possible.



Glossary "Aspect"



There is a thing called Aspect. This is a simple example that checks permissions. Thanks to him, you can see some annotation, which is also highlighted by the plugin for PhpStorm. โ€œAspectโ€ declares SQL expressions to which points in the code to apply this condition. Below, for example, we want to apply a closure to all public methods from the UserService



class.







We get the closure using the $invocation



method.







This is some kind of wrapper on top of the reflection



method, which contains more arguments.



Further in this callback for each method, you can check the necessary access rights, this is called the โ€œAdviceโ€.







We kind of say the language: โ€œDear PHP, please apply this method before every call to the public method from the UserService



class UserService



โ€ Just one line, but a lot of useful.



Aspect vs Event Listener



To make it clearer, I made a comparison of Aspect with Event Listener. Many people work in Symfony and know what Event Dispatcher is.







They are similar in that we pass some kind of dependency, for example, AutorizationChecker



, and declare where to apply in this case. In the case of Listener, this is a SubscrabingEvent



called UserCreate



, and in the case of Aspect



we subscribe to all calls to public methods from the UserService



. At the same time, the content of the callback handler itself is approximately the same: we just check something and react accordingly.



, .



, , Aspects



.







. , PHP- .







, . , , OPcache.



. Composer



. Go! AOP, Composer



, .







Aspects



, Aspects



. .



.



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







. , goaop/parser-reflection .







AST- , . , , , .



.







, , Aspect



.







, .







, , . โ€” , - , .







, . , .



Aspect MOCK. ยซยป, , . .



โ€” joinPoint



. - joinPoint



: , , .



?



.



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



.







, . - , , .



That's all. , - . , .



PHP Russia 2020 . โ€” . telegram- , PHP Russia 2020.



All Articles