Your Extensive Guide To PHP Magic Methods
Eyad Bereh
Software Engineer | Senior PHP & Laravel Developer | Master of Web Science Student at SVU
Introduction
PHP is a programming language that's known for its high flexibility and efficiency, and when we speak about the OOP paradigm in PHP we often come across a not-so-familiar concept: magic methods.
In this article, we'll be talking in extensive and exhaustive detail about these magic methods, what are they, how to use them, what benefits they provide, and how they can add a spray of additional dynamicity to our PHP code. So get your coffee cup (or whatever you love to drink), and let's get started.
What are "magic methods"?
In PHP, magic methods are special predefined methods that are automatically invoked by the PHP interpreter under specific circumstances. These methods have reserved names starting with a double underscore (__) and are used to implement various functionalities within classes. By defining these methods within a class, developers can customize the behavior and interactions of objects, providing a powerful tool for dynamic code execution.
Famous PHP Frameworks such as Laravel make heavy use of magic methods in many areas to provide a high level of flexibility during the development process.
In the next section, we'll take a look at some of these methods and what (magical) effect they have.
The __construct() method and __destruct() method
The __construct() method is called automatically when an object is being instantiated, allowing initialization and setup operations to be performed. Classes that have a constructor method call this method on each newly created object, so it is suitable for any initialization that the object may need before it is used.
On the other hand, the __destruct() method is invoked when an object is no longer referenced or explicitly destroyed, providing an opportunity for cleanup operations. The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.
The concept of the constructor and destructor here is similar to that in other programming languages that support the OOP paradigm such as C++.
Let's understand this in depth through several examples. In the following piece of code, we have defined a simple class and created an object from that class.
If we run this code, we get the following output:
So what exactly is going on here? first, we created an object, assigned it to a variable, and then assigned another value to the variable. The PHP Garbage Collector (let's call it GC, for short) works according to a complex algorithm that I'm not going to discuss here (because it's outside the scope of the article), but let's just say that the GC watches all created objects and checks the number of references pointing at each object. As soon the this object has no references anymore, the GC cleans it from the memory. The word "reference" here means to use the object somehow, like reading its values, writing to its values, passing it around in parameters, or more simply let's just say that whenever you "touch" the object.
So after we assigned the string "Test" to a variable "$user", the instance of User class is now being left without anything to refer to it anymore, so the GC removes it. During this process, the __destruct() method gets called.
Now let's see another interesting example. In the following code, we instantiate the User class once more, but this time we don't assign another value to the variable that refers to the object, instead we just let the script run normally.
If we run this code, we get the following output:
The script ran normally, and at the very end, the GC cleaned up all objects from memory implicitly. Interesting behavior. That's why we said above that the __destruct() method is the perfect place to define any cleanup operations.
I will try to write a detailed article about PHP garbage collector in the future.
The __call() method and __callStatic() method
The __call() method is a magic method in PHP that is automatically invoked when an inaccessible or non-existing method is called on an object. It provides a mechanism for dynamic method dispatch, allowing developers to handle method calls that are not explicitly defined within the class.
When a method is called on an object and that method is not accessible or does not exist, PHP will search for the call() method within the class definition. If the __call() method is present, PHP will pass the method name and arguments as parameters to this method, giving developers the opportunity to handle the method call programmatically.
The syntax for the __call() method is as follows:
Where the $name parameter defines the name of the non-existent function that was called, and the $arguments parameters represent an array of values representing the parameters passed to the invocation.
Let's take a look at the following code. Here we have the same old User class, but this time we defined the __call() function so that it just prints the name and the arguments.
Running this code will give the following output:
Now this use case may not be terribly useful, so let's take a look at an actual useful implementation done by the Laravel framework.
In Laravel, if you want to get the user whose username is "john.doe" using Eloquent ORM, you may write something like:
$user = User::where("username", "john.doe")->get()->first();
However, there's a shorter way where you can write:
$user = User::whereUsername("john.doe")->get()->first();
Notice that the latter invocation is a little bit different because the field name is being used as a part of the method name. HOW ON EARTH DID THEY DO THAT?
To answer this question, I had to open the file src/Illuminate/Database/Query/Builder.php, line 3957, on the Laravel GitHub repository (click here to open the file). Right there, I found the following implementation:
Surprise surprise, they're implementing the __call() magic method in this class, but here's what's really going on:
When you make a call like whereUsername(), then this function doesn't actually exist inside the code, so instead the call is delegated to the __call() method which receives the method name (that's whereUsername in our case) and checks that if the passed method name starts with the string "where" and tries to build a dynamic WHERE statement depending on it, and the value exists inside the $parameters parameter. Nice move from the Laravel development team, but we got them uncovered anyway ??.
This method has a relative called __callStatic() which does almost the same job but with a little difference: it's specifically designed to handle static method calls within a class. It is automatically triggered when an inaccessible or non-existing static method is invoked.
When a static method is called on a class and that method is not accessible or does not exist, PHP will search for the __callStatic() method within the class definition. If the method is present, PHP will pass the method name and arguments as parameters to this method, allowing developers to handle the static method call dynamically.
The syntax for the __callStatic() method is as follows:
Of course, this method is also used in many places across the Laravel framework, a good example is the file src/Illuminate/Database/Eloquent/Model.php, Line 2343, on the Laravel GitHub repository (click here to open the file). Right there, you can see the following implementation:
What's happening here is really simple: when you try to invoke a non-existent method statically on Eloquent, this function instantiates a Model object and tries to run the specified method on it, saving you from having to instantiate the object yourself. Thanks to this implementation, you can simply write:
$user = User::where("username", "someone");
Instead of:
$user = new User();
$user->where("username", "someone);
The __get() method and __set() method
The __get() method is a magic method in PHP that is automatically called when an attempt is made to access an inaccessible (private or protected) or non-existing property of an object. It provides a way to dynamically intercept and handle property access, allowing developers to define custom behavior for property retrieval.
When a property is accessed on an object and that property is not accessible or does not exist, PHP will search for the __get() method within the class definition. If this method is present, PHP will invoke it, passing the name of the property being accessed as a parameter to the method.
The syntax for the __get() method is as follows:
Where the $name parameter represents the name of the property being accessed.
If we want to see an interesting use case for this method, then there's no better example than the Request class.
When a form is submitted to a server whose backend is built using Laravel, you can use the Illuminate/Http/Request to receive the entered values. However, the interesting part is that you're accessing them like they're predefined attributes inside the Request class, but in fact, they're NOT.
To understand what's going on under the hood, we can take a look at the file src/Illuminate/Http/Request.php, line 734, on the Laravel GitHub repository (click here to open the file). Over there, we can see the following implementation:
So what's going on here exactly? we can see the __get() method being implemented inside the class. When we try to access the field name on the Request object, this method gets triggered and fetches all data coming through the HTTP request as an associative array, and the get() method on the Arr helper is called, passing the specified key to it so that it can return the equivalent value from the HTTP data. In case this key didn't exist inside the associative array, the callback function specified in the third parameter is executed and it's value is returned as a default value.
Thanks to this implementation, you can easily write:
$request->username
Instead of:
$request->get("username");
Regarding the __set() method, it's a magic method in PHP that is automatically invoked when an attempt is made to assign a value to an inaccessible or non-existing property of an object. It provides a way to dynamically intercept and handle property assignments, allowing developers to define custom behavior for property settings.
When a value is assigned to a property of an object and that property is not accessible or does not exist, PHP will search for the __set() method within the class definition. If this method is present, PHP will call it, passing the name of the property being set and the value being assigned as parameters to the method.
The syntax for the __set() method is as follows:
Where the $name parameter represents the name of the property being assigned and the $value parameter represents the value that's being assigned to that property.
A good use case for this method is using it to validate and modify the values being passed to the attributes of the class before being finally assigned.
In the following piece of code, we have the same old User class we used earlier. This time we defined a __set() method to verify that any value being assigned to the email field is a valid E-mail address by checking whether it contained an @ symbol or not. In case it doesn't contain, an error message is thrown without assigning the faulty value to the field.
Running this code gives us the following output:
Laravel also makes heavy use of this method in some areas like Eloquent ORM for similar kinds of checks.
The __toString() method
The __toString() method is a magic method in PHP that allows an object to be treated as a string when it is used in a string context. It provides a way to define the string representation of an object, allowing developers to customize how the object is converted to a string.
When an object is used in a string context, such as when it is concatenated with a string or passed to a function that expects a string argument, PHP will automatically look for the __toString() method within the class definition of the object. If the __toString() method is present, PHP will invoke it and use the returned value as the string representation of the object.
The syntax for the __toString() method is as follows:
Let's assume we have the following implementation of the User class.
Normally, when we try to run the previous code, we may get an error that's similar to:
Nothing unusual here, because PHP doesn't know how to convert this object into a string. However, if implemented the __toString() method as follows:
Then we will get the following output after running the code:
Once again, Laravel also makes use of this method, especially in the Eloquent ORM. Actually, in Laravel, this is easily doable:
$user = User::first();
echo $user;
Thanks to the implementation of __toString() method that takes the model and serializes it into a JSON string, saving you from having to write:
$user = User::first();
$serialized = json_encode($user);
echo $serialized;
The __invoke() method
The __invoke() method is a magic method in PHP that allows an object to be invoked as a function. It provides a way to define custom behavior when an object is treated like a function and called as such.
When an object is used as a function and called with parentheses, PHP will automatically look for the __invoke() method within the class definition of the object. If this method is present, PHP will invoke it, passing any arguments provided during the function call.
The syntax for the __invoke() method is as follows:
Where the $arguments parameters represent a list of comma-separated single parameters that get passed to the invocation.
In the following code, we define the __invoke() method
If we run this code, we get the following output:
In fact, this method is somewhat associated with classes that have only a single action in Laravel, such as single-action controllers and scheduled tasks.
Other methods
There exist a number of additional methods which I didn't talk about because they're less significant. However, I'll list some of them quickly below, just so that you know they exist:
A few important things to notice
Magic methods are actually great because of the high level of flexibility and dynamic behavior they add to PHP. However, there are a few things one should watch out for:
Conclusion
In conclusion, magic methods in PHP, such as __get(), __set(), __toString(), and __invoke(), provide a versatile and powerful way to customize the behavior of objects in a flexible and expressive manner. They allow developers to intercept and handle property access, assignment, string conversion, and object invocation, enabling the creation of more intuitive and readable code.
By leveraging magic methods, developers can design APIs that are intuitive, expressive, and easy to use. They enable objects to seamlessly integrate with language constructs and provide a more natural and intuitive coding experience.