📜 ⬆️ ⬇️

[Translation] Magic methods in PHP

If you have ever studied the PHP code of open source projects, then you might encounter methods starting with a double underscore. These are the very magical methods by which you can determine the behavior of your object during various manipulations with its instance.

I assume that you have already encountered some of them, because there are quite common methods, and yet, I believe that a competent PHP programmer needs to be confident in all the features of the language.
I think this can be considered, as a kind, the starting point in the world of Magical methods.

Starting to study


When I myself studied this material, I used all sorts of textbooks and articles in which rather silly or useless examples were presented. I believe that in order to understand something you need to try it in the context of a real task. This is where we begin.

Imagine that we want to receive all tweets using Tweeter Api. We receive JSON of all tweets of the current user and want to turn each tweet into an object with methods that will allow to perform certain operations.
')
Below, I introduced the base class Tweet:
class Tweet { } 


Now that we have created an object, we can start exploring the methods themselves. (Approx. Translator - some designs will sometimes be omitted to emphasize the roles and possibilities of each method)

Designers and Destructors


Perhaps one of the most common magic methods is the constructor (__construct ()). If you closely followed the creation of the Cribbb application on my blog, you are quite knowledgeable about this method.

The __construct () method is automatically called when an object instance was created. In it, you can set the initial properties of the object or set dependencies.

Usage example:

 public function __construct($id, $text) { $this->id = $id; $this->text = $text; } $tweet = new Tweet(123, 'Hello world'); 


When we create an instance of the Tweet class, we can pass parameters that will go to the __construct () method. From the example above, you can see that we do not call this method and should not call it - it is called automatically.

Over time, you will need to extend the class by inheriting it. Sometimes the parent class also has a __construct () method that performs certain actions, so as not to lose the functionality of the parent class, you need to call its constructor.

 class Entity { protected $meta; public function __construct(array $meta) { $this->meta = $meta; } } class Tweet extends Entity { protected $id; protected $text; public function __construct($id, $text, array $meta) { $this->id = $id; $this->text = $text; parent::__construct($meta); } } 


When you try to delete an object, the __destruct () method will be called. Again, by analogy with the constructor, this is not what you need to call, because PHP will do everything for you. This method will allow you to clear everything that you used in the object, for example a connection to a database.

 public function __destruct() { $this->connection->destroy(); } 


To be honest, I hid from you most of the __destruct () method outlined above. PHP is actually not one of those languages ​​where the process will exist for quite a long time, so I don't think you will have anything for which a destructor might be needed. By itself, the life cycle of a request in PHP is so small that there will be more trouble than benefit from this method.

Getters and Setters


When you work with objects in PHP, you would really like to access the properties of the object in some way:

 $tweet = new Tweet(123, 'hello world'); echo $tweet->text; // 'hello world' 


However, if the protected access modifier is set for the text property, then such a call will cause an error.
The magic __get () method will catch calls to any non-public properties.

 public function __get($property) { if (property_exists($this, $property)) { return $this->$property; } } 


The __get () method takes the name of the property you are referring to as an argument. In the above example, the existence of a property in the object is first checked and, if it exists, its value is returned.

As in the examples above, you should not call this method directly; PHP will call it every time you try to access non-public properties of the class.

In the reverse situation - if you try to set the value of a property that is not public - you will get an error. And again, PHP has its own method, which will be called when trying to set a value in a non-public field. This method takes 2 parameters as arguments — the property to which the value was written, and the value itself.

If you want to use this method, your class will get a property like this:

 public function __set($property, $value) { if (property_exists($this, $property)) { $this->$property = $value; } } $tweet->text = 'Setting up my twttr'; echo $tweet->text; // 'Setting up my twttr' 


In the examples above, I showed how you can get or set the values ​​of properties that do not have the public access modifier. However, working with these magical methods is not always the best idea. It is much better to have many methods for getting and writing properties, since in this case they form a specific API and this means that when you change the way you store or process your code, it will not break.

However, you will still occasionally encounter __get () and __set () methods, which are commonly called getters and setters, respectively. This is a pretty good decision if you decide to change a value or add a little business logic.

Check property for existence


If you are familiar with PHP, you most likely know about the existence of the isset () function, which is usually used when working with arrays. You can also use this function in order to understand whether a property is set in the object or not. You can define the magic __isset () method in order to check not only public properties, but also others.

 public function __isset($property) { return isset($this->$property); } isset($tweet->text); // true 


As you can see above, the __isset () method tracks the function call for existence checking and receives the property name as an argument. In turn, in the method you can use the isset () function to check for existence.

Clearing a variable
By analogy with the isset () function, the unset () function is typically used when working with arrays. Again, you can use the unset () function to clear the value of a non-public property. To apply this method to non-public properties, you need a __unset () method that will track attempts to clear non-public class properties.

 public function __unset($property) { unset($this->$property); } 


Cast to string


The __toString () method will allow you to determine the logic of your application when trying to cast an object to a string type.
For example:

 public function __toString() { return $this->text; } $tweet = new Tweet(1, 'hello world'); echo $tweet; // 'hello world' 


It can be said that when you try to refer to an object as a string, for example, when using echo, the object will be returned as you define in the __toString () method.

A good illustration in this case can be Eloquent Models from the Laravel framework. When you try to cast the object to the string, you will get json. If you want to see how Laravel does it, I recommend referring to the source code .

Sleep and Awakening


The serialization function (serialize ()) is a fairly common way to store an object. For example, if you wanted to save an object in a database, you would first have to serialize it, then save it, and when you need it again, you would have to retrieve it and deserialize (unserialise ()).

The __sleep () method allows you to determine which properties should be saved. If we, for example, did not want to maintain any links or external resources.

Imagine that when we create an object, we want to define a mechanism for its preservation.

 $tweet = new Tweet(123, 'Hello world', new PDO ('mysql:host=localhost;dbname=twttr', 'root')); 


When we are preparing to save the object, we naturally do not need to save the connection to the database, because in the future it will be meaningless.
Therefore, in the __sleep () method, we define an array of properties that should be saved.

 public function __sleep() { return array('id', 'text'); } 


And after the time comes to wake up the object, we may need all that we did not save during serialization. In the specific example, we need to establish a connection to the database. This can be done using the __wakeup () magic method.

 public function __wakeup() { $this->storage->connect(); } 


Method call


The __call () magic method will intercept all attempts to call methods that are not public. For example, you may have an array of data that you want to change:

 class Tweet { protected $id; protected $text; protected $meta; public function __construct($id, $text, array $meta) { $this->id = $id; $this->text = $text; $this->meta = $meta; } protected function retweet() { $this->meta['retweets']++; } protected function favourite() { $this->meta['favourites']++; } public function __get($property) { var_dump($this->$property); } public function __call($method, $parameters) { if (in_array($method, array('retweet', 'favourite'))) { return call_user_func_array(array($this, $method), $parameters); } } } $tweet = new Tweet(123, 'hello world', array('retweets' => 23, 'favourites' => 17)); $tweet->retweet(); $tweet->meta; // array(2) { ["retweets"]=> int(24) ["favourites"]=> int(17) } 


Another typical example is the use of another public API in its object.

 class Location { protected $latitude; protected $longitude; public function __construct($latitude, $longitude) { $this->latitude = $latitude; $this->longitude = $longitude; } public function getLocation() { return array( 'latitude' => $this->latitude, 'longitude' => $this->longitude, ); } } class Tweet { protected $id; protected $text; protected $location; public function __construct($id, $text, Location $location) { $this->id = $id; $this->text = $text; $this->location = $location; } public function __call($method, $parameters) { if(method_exists($this->location, $method)) { return call_user_func_array(array($this->location, $method), $parameters); } } } $location = new Location('37.7821120598956', '-122.400612831116'); $tweet = new Tweet(123, 'Hello world', $location); var_dump($tweet->getLocation()); // array(2) { ["latitude"]=> string(16) "37.7821120598956" ["longitude"]=> string(17) "-122.400612831116" } 


In the above example, we can call the getLocation method on the object of the Tweet class, but in fact we delegate it to the Location class.
If you are trying to call a static method, you can also use the __callStatic () magic method. The main thing to remember is that it works only when calling static methods.

Cloning


When you create a copy of an object in PHP, in fact, it is not a new object that is written into the variable, but an identifier that refers to the original object. That is, any change in the referring object entails a change in the original object, but deleting any of the objects will not affect the existence of the others:

 $sheep1 = new stdClass; $sheep2 = $sheep1; $sheep2->name = "Polly"; $sheep1->name = "Dolly"; echo $sheep1->name; // Dolly echo $sheep2->name; // Dolly 


 $a = new StdClass; $b = $a; $a = null; var_dump($b); // object(stdClass)#1 (0) { } 


In order to create a copy of the object you should use the keyword clone.

 $sheep1 = new stdClass; $sheep2 = clone $sheep1; $sheep2->name = "Polly"; $sheep1->name = "Dolly"; echo $sheep1->name; // Dolly echo $sheep2->name; // Polly 


However, if we have several related objects, the dependencies that are in them will also be copied.

 class Notification { protected $read = false; public function markAsRead() { $this->read = true; } public function isRead() { return $this->read == true; } } class Tweet { protected $id; protected $text; protected $notification; public function __construct($id, $text, Notification $notification) { $this->id = $id; $this->text = $text; $this->notification = $notification; } public function __call($method, $parameters) { if(method_exists($this->notification, $method)) { return call_user_func_array(array($this->notification, $method), $parameters); } } } $tweet1 = new Tweet(123, 'Hello world', new Notification); $tweet2 = clone $tweet1; $tweet1->markAsRead(); var_dump($tweet1->isRead()); // true var_dump($tweet2->isRead()); // true 


In order to solve this problem, we can define the __clone () method in order to determine the correct behavior:

 class Tweet { protected $id; protected $text; protected $notification; public function __construct($id, $text, Notification $notification) { $this->id = $id; $this->text = $text; $this->notification = $notification; } public function __call($method, $parameters) { if(method_exists($this->notification, $method)) { return call_user_func_array(array($this->notification, $method), $parameters); } } public function __clone() { $this->notification = clone $this->notification; } } $tweet1 = new Tweet(123, 'Hello world', new Notification); $tweet2 = clone $tweet1; $tweet1->markAsRead(); var_dump($tweet1->isRead()); // true var_dump($tweet2->isRead()); // false 


Object call as function


The magic method __invoke () allows you to define the logic of the object, when you try to refer to the object as a function.

 class User { protected $name; protected $timeline = array(); public function __construct($name) { $this->name = $name; } public function addTweet(Tweet $tweet) { $this->timeline[] = $tweet; } } class Tweet { protected $id; protected $text; protected $read; public function __construct($id, $text) { $this->id = $id; $this->text = $text; $this->read = false; } public function __invoke($user) { $user->addTweet($this); return $user; } } $users = array(new User('Ev'), new User('Jack'), new User('Biz')); $tweet = new Tweet(123, 'Hello world'); $users = array_map($tweet, $users); var_dump($users); 


In this example, I apply the $ tweet object, as a callback function, to all values ​​of the $ users array. In this example, we will add a tweet to each user. I agree that this example is a bit artificial, but I am sure that you will really find an application to this method.

Conclusion


As you can see, PHP uses magic methods to respond to certain actions with the methods or properties of the object. Each of these methods works automatically, you simply determine what should happen, and PHP takes care of the rest.

For quite a long time, I did not understand the true meaning of the magical methods. I thought that they are needed only to do any interesting things with objects. And when I finally understood their true purpose, I was able to write more powerful objects as part of more serious applications.

I hope that in each of the examples presented in this tutorial, I was able to show how magical methods can help with everyday tasks. And I will not be the first to agree that when you explain any material, but do not explain its practical application, it is really annoying.

Posted by: Philip Brown
Original: culttt.com/2014/04/16/php-magic-methods
Acknowledgments: werdender , HighQuality

PS Sorry, I know that the translation in some places is rather clumsy. If you know how it would sound better - write to me, I will try to fix it.

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


All Articles