📜 ⬆️ ⬇️

Features when intercepting method calls using __call () and __callStatic () in PHP

Prologue


My experiments with the Object Oriented Paradigm helped to write this article.
Actually, the php-guru this article is unlikely to seem interesting, but I hope that it will help you bypass the pitfalls just becoming acquainted with the language. The article does not claim to manual on the PLO, but only explains some points.

What are __call () and __callStatic ()?


Let's start with a simple one: you have a class that describes the methods and properties of an object (which, in principle, is logical). Imagine that you have decided to refer to a nonexistent method of this object. What will you get? That's right - a fatal mistake! Below is the simplest code.

<?php class OurClass{} $Object=new OurClass; $Object->DynamicMethod(); # Fatal error: Call to undefined method OurClass::DynamicMethod() ?> 


In a static context, we observe a similar behavior:
')
 <?php class OurClass{} OurClass::StaticMethod(); # Fatal error: Call to undefined method OurClass::StaticMethod() ?> 


So, sometimes there is a need to either execute some code in the absence of the method we need, or find out what method we tried to call, or use another API to call the method we need. For this purpose, there are methods __ call () and __ callStatic () - they intercept the call to a non-existent method in the context of the object and in the static context, respectively.
Rewrite our examples using the "magic methods". Note: Each of these wizards takes two parameters: the first is the name of the method we are trying to call, the second is a list containing the parameters of the method being called. The key is the parameter number of the method being called, the value is the parameter itself:

 <?php class OurClass { public function __call($name,array $params) { echo '   $Object->'.$name.',    ,    '.__METHOD__.'()<br>' .PHP_EOL; return; } public static function __callStatic($name,array $params) { echo '   '.__CLASS__.'::'.$name.',    ,    '.__METHOD__.'()'; return; } } $Object=new OurClass; #   $Object->DynamicMethod,    ,    OurClass::__call() $Object->DynamicMethod(); #   OurClass::StaticMethod,    ,    OurClass::__callStatic() OurClass::StaticMethod(); ?> 


The practical application of these two comrades depends only on your imagination. As an example, I will give a sketch of the implementation of the Fluent Interface programming technique (some consider this a design pattern, but the name does not change the essence). In short, the fluent interface allows you to make chains of object calls (it looks like something like jQuery). On Habré there are a couple of articles about the implementation of such algorithms. Fluent interfaces sound like “flowing interfaces” in broken Russian:

 <?php abstract class Manager { public $content_storage=''; public function __toString() { return $this->content_storage; } public function __call($name,array $params) { $this->content_storage.=$this->_GetObject($name,$params).'<br>'.PHP_EOL; return $this; } } abstract class EntryClass { public static function Launch() { return new FluentInterface; } } class FluentInterface extends Manager { public function __construct() { /** * -  */ } public static function _GetObject($n,array $params) { return $n; } } echo $FI=EntryClass::Launch() ->First() ->Second() ->Third(); /*  First Second Third */ ?> 


It seems you wanted to tell us something about the features of the interception?


Be sure to tell. Sitting yesterday at the computer, I decided to systematize my knowledge of PHP.
I sketched about such a piece of code (it was slightly different in the original, but I reduced it for the article, because the rest did not carry the meaning for this problem):

 <?php abstract class Base { public function __call($n,array$p) { echo __METHOD__.' says: '.$n; } } class Main extends Base { public function __construct() { self::Launch(); } } $M=new Main; ?> 


Updated the page. To my surprise, there was no limit. I immediately ran to the php.net to watch the manual.
Here is an excerpt from the documentation
 public mixed __call ( string $name , array $arguments ) public static mixed __callStatic ( string $name , array $arguments ) 

In the context of an object, calling the inaccessible methods calls the __call () method.
In a static context, when calling inaccessible methods, the __callStatic () method is called.

For a long time I could not understand what the problem was. PHP version: 5.4.13. That is, those times when calls to non-existent methods from any context led to the call to __call () long gone. Why instead of logical Fatal Error, I get a call to __call ()? I went to explore further. Added the __callStatic () method to the Base abstract class. Refreshed the page again. The call was still addressed in __call (). After tormenting half a day, I still understood what the problem was. It turns out PHP perceives a static context inside a class and outside of it differently. Not understood? I will try to illustrate. Take the previous example and add one line to it:

 <?php abstract class Base { public function __call($n,array$p) { echo __METHOD__.' says: '.$n; } } class Main extends Base { public function __construct() { self::Launch(); } } $M=new Main; Main::Launch(); #    .    Fatal error: Call to undefined method Main::Launch() ?> 


That is, static context - static context is different.
Miracles and only. When I studied “magical methods”, I did not think that the name should be taken so literally.

Well, everything becomes clear here: if we add the __callStatic () method to the Base class of the above example, instead of displaying a fatal error, PHP will execute __callStatic ().

Summary


If everything is not yet clear to you: it’s about the fact that a call to a static method inside an instance of a class and a call to a static method outside of an instance of a class are interpreted differently by the interpreter. If you change self :: Launch () to Main :: Launch (), the call context will not change. The behavior in this case will be the same. Again, illustrate:

 <?php abstract class Base { public function __call($n,array$p) { echo __METHOD__.' says: '.$n; } } class Main extends Base { public function __construct() { #      Base::__call() self::Launch(); static::Launch(); Main::Launch(); } } $M=new Main; ?> 


The result of the article is simple: be careful (as always) and check the behavior when calling methods.

Poscriptum


I looked through the bugtracker, it turns out that the problem is not with me alone . But the developers apparently decided that such a bug is not a bug, so they left as is.

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


All Articles