DEV Community

Steven Yung
Steven Yung

Posted on • Edited on

Laravel Greatest Trick Revealed: Magic Methods

Photo by Mervyn Chan on Unsplash
Photo by Mervyn Chan on Unsplash

Laravel has leverage PHP to a whole new level, providing awesome Developer Experience (DX) to craft your next project. As a result, some people qualify it as "magic".

Today, I will reveal to you one of Laravel tricks, magic methods.

What is a magic method ?

It's important to understand that magic methods are not exclusive to Laravel but are available in any PHP application that you have. Laravel just happens to have some of the most interesting use cases for magic methods.

Magic methods are methods available in any classes that you declare in PHP which provide way to implement additional functionality in your class.

Here a good definition:

magic methods will never directly be called by the programmer โ€“ actually, PHP will call the method 'behind the scenes'. This is why they are called 'magic' methods โ€“ because they are never directly called, and they allow the programmer to do some pretty powerful things.

There are in total 15 magic methods:



class MyClass
{
    public function __construct() {}

    public function __destruct() {}

    public function __call() {}

    public function __callStatic() {}

    public function __get() {}

    public function __set() {}

    public function __isset() {}

    public function __unset() {}

    public function __sleep() {}

    public function __wakeup() {}

    public function __toString() {}

    public function __invoke() {}

    public function __set_state() {}

    public function __clone() {}

    public function __debuginfo() {}
}


Enter fullscreen mode Exit fullscreen mode

If you've done some Object Oriented Programming with PHP, you will certainly recognise the __construct method which is also a magic method. So you've been using magic methods all along!

You will also notice that all the magic methods are prefixed with __ as a convention.

Today, we will not dive in all of them, but only the interesting ones used across the Laravel codebase. If you are interested by others, feel free to check the documentation below ๐Ÿ‘‡

PHP: Mรฉthodes magiques - Manual

How Laravel uses magic methods

__get()

The models in Laravel are really special. They don't store the attributes data as direct attributes of the class but in an attribute protected $attributes , which is an associative array of all the data the model is holding.

Let see the differences between a simple PHP class and a Laravel model accessing attributes.



<?php

/**
 * A normal user class in PHP (without Laravel) will be just a class with the said attributes
 */
class NormalUser
{
    public $firstName = 'Alice';
}

$normalUser = new NormalUser;

$normalUser->firstName; // Will return 'Alice'


Enter fullscreen mode Exit fullscreen mode


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * A user class in Laravel
 */
class LaravelUser extends Model
{
    /**
     * Note that we store all the attributes data in one and single array
     */
    protected $attributes = [
        'firstName' => 'Alice',
    ];
}

$laravelUser = new LaravelUser;

$laravelUser->firstName; // Will return 'Alice' as well


Enter fullscreen mode Exit fullscreen mode

We can see that both PHP and Laravel classes above act exactly the same. Nevertheless, in the case of Laravel, the attributes are not stored like in plain PHP and instead all the attributes are centralised in one attribute named $attributes. Still we manage to access the right data but how ?

This is all possible because of the __get magic method. Let's try to implement our own simplify version of this in an example.



<?php

class NormalUser
{
    /**
     * We declare the attributes that same way as in Laravel
     */
    protected $attributes = [
        'firstName' => 'Alice',
    ];

    /**
     * The function __get receive one parameter
     * which will be the name of the attribute you want to access
     * in this case $key = "firstName"
     */
    public function __get(string $key)
    {
        return $this->attributes[$key];
    }
}

$normalUser = new NormalUser;

$normalUser->firstName; // Will return 'Alice'


Enter fullscreen mode Exit fullscreen mode

We did it! ๐ŸŽ‰

We need to note that the magic method __get will only be called when no attribute with the matching name are found in the class. It's sort of a fallback method called when PHP doesn't find the asked attribute in the class. So in the example below, the magic method __get will not be called at all.



<?php

class NormalUser
{
    public $firstName = 'Bob';

    protected $attributes = [
        'firstName' => 'Alice',
    ];

    public function __get($key)
    {
        return $this->attributes[$key];
    }
}

$normalUser = new NormalUser;

/**
 * Will return 'Bob' as the attribute exists in the class
 * so the magic method __get doesn't get call in this case
 */
$normalUser->firstName;


Enter fullscreen mode Exit fullscreen mode

There is way more things happening behind the scene. If you want to dig more how exactly Laravel's models uses __get you can check the source code below.

laravel/framework

__set()

The magic method __set is used when the attribute trying to be set is not declared in the class. Let's see the difference again between a normal PHP class and a model in Laravel.



<?php

class NormalUser
{
    public $firstName = 'Alice';
}

$normalUser = new NormalUser;

$normalUser->firstName = 'Bob';

$normalUser->firstName; // Will return 'Bob'


Enter fullscreen mode Exit fullscreen mode


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class LaravelUser extends Model
{
    protected $attributes = [
        'firstName' => 'Alice',
    ];
}

$laravelUser = new LaravelUser;

$laravelUser->firstName = 'Bob';

$laravelUser->firstName; // Will return 'Bob' as well


Enter fullscreen mode Exit fullscreen mode

As we can see, in this example we still try to affect the value Bob to an attribute that doesn't really exist in the class but lives inside the attribute $attributes . Let's try to implement that exact behaviour with the magic method __set.



<?php

class NormalUser
{
    public $attributes = [
        'firstName' => 'Alice',
    ];

    /**
     * The magic method __set receives the $name you want to affect the value on
     * and the value
     */
    public function __set($key, $value)
    {
        $this->attributes[$key] = $value;
    }
}

$normalUser = new NormalUser;

$normalUser->firstName = 'Bob';

/**
 * As we don't have the __get magic method define in this example for simplicity sake,
 * we will access the $attributes directly
 */
$normalUser->attributes['firstName']; // Will return 'Bob'


Enter fullscreen mode Exit fullscreen mode

And here we go! We successfully implemented the basic usage of the __get and __set magic methods in Laravel! And all it took was a few lines of code!

Keep in mind that those magic methods are as simple as possible without entering into too much details as there is more to it than just those use cases, if you are curious about how exactly it's working I invite you to dive into the code yourself for some exploring! (also feel free to hit me up on Twitter if you have any questions)

Again, if you want to dig more, here the link to the source code

laravel/framework

Let's move on to the last and most interesting case! ๐Ÿ™Œ

__call() & __callStatic()

__call is executed when a method was called but not found in a specific class. In Laravel, this magic method is what is making macros possible in PHP.

I'm not going into all the details on macro but if you interested here is a good article explaining how you can use them in your Laravel application ๐Ÿ‘‡

The Magic of Laravel Macros

Let's try to see how we could reproduce a simple macro example in plain PHP.



<?php

class NormalUser
{
    public $firstName = 'Alice';

    public $lastName = 'Bob';
}

$normalUser = new NormalUser;

$normalUser->fullName(); // This will throw an error as no method "fullName" has been declared.


Enter fullscreen mode Exit fullscreen mode

Now with __call , we could define an array which will contain closures function that we will be able to add programmatically in our app as we go.



<?php

class NormalUser
{
    public $firstName = 'Alice';

    public $lastName = 'Bob';

    /**
     * We initialise our macros as an empty array that we will fill
     */
    public static $macros = [];

    /**
     * We define this method to add new macro as we go
     * the first argument will be the name of the macro we want to define
     * the second will be a Closure function that will be executed when calling the macro
     */
    public static function addMacro($name, $macro) {
        static::$macros[$name] = $macro;
    }

    /**
     * "__call" receives two parameters,
     * $name which will be the name of the function called in our case "fullName"
     * $arguments which will be all the arguments passed in the function in our case it'll just be an empty array as we can't pass any arguments in our function
     */
    public function __call(string $name, array $arguments) {
        /**
         * We retrieve the macro with the right name
         */
        $macro = static::$macros[$name];
        /**
         * Then we execute the macro with the arguments
         * Note: we need to bind the Closure with "$this" before calling it to allow the macro method to act in the same context
         */
        return call_user_func_array($macro->bindTo($this, static::class), $arguments);
    }
}

$normalUser = new NormalUser;

$normalUser->fullName(); // This will still break as we didn't define the macro "fullName" yet and the method "fullName" doesn't exist either

/**
 * We add the macro function "fullName"
 */
NormalUser::addMacro('fullName', function () {
    return $this->firstName.' '.$this->lastName;
});

$normalUser->fullName(); // Now, returns "Alice Bob"


Enter fullscreen mode Exit fullscreen mode

Macros are a little more complex than that but again, we manage to create a simple working version of the macros by using __call magic method.

__callStatic works exactly the same as __call but for static methods.

Again if you want to dig a little bit more on your own, here is the Macroable trait source code

laravel/framework

Final take

Here you go Ladies and Gentlemen, while it is somewhat true that Laravel feels magic when you first start using it, by looking into the source code itself, you can understand how the "magic" operate behind the scene.

Like magic in real life, nothing really happens without an explanation and that's even truer in code. There is always a line of code doing the work somewhere, you just have to find it.

I encourage you to dig more into Laravel to make the magic disappear and don't forget to share your nuggets with me on Twitter!

This blog post was originally posted on my personal blog, please give it a hug ๐Ÿค—

Special Thanks: Samuel, Jean-Baptiste and Jessica

Top comments (11)

Collapse
 
mbrown1408 profile image
Michael J Brown

As somebody that is just learning the basics of php, OOP and playing with laravel passively, this article was incredibly helpful. Thank you!

Collapse
 
xstevenyung profile image
Steven Yung

If it was clear for you then my mission is done !

Thank you for the kind words and keep on learning, Laravel is incredible and extremely rewarding ! ๐Ÿ’ช

Collapse
 
ystrauch profile image
Yaron Strauch • Edited

The magic method __set however has a trap that I fell into and needed many hours to debug.

Say you have your nice object, say Airplane, with a static function to initialise an airplane from some other data.

class Airplane extends \Illuminate\Database\Eloquent\Model {
   private $name;

   public static function fromSomething($foo) {
       $airplane = new Airplane();
       // do something awesome with $foo to derive the name
       $airplane->name = "Airbus";
       $airplane->save();
       return $airplane;
    }
}
Enter fullscreen mode Exit fullscreen mode

And obviously from somewhere else you call $airplane = Airplane.fromSomething($foo), and that part works as expected. But then you check your database (or fetch the model again through eloquent) and... It's empty! Well except for the created/modified timestamps. The rest is just empty.
The caveat: Php docs specify: __set() is run when writing data to inaccessible (protected or private) or non-existing properties. So it's not called because we have write access to the method! This means that all the cool eloquent magic to update your attributes is knocked out.
Solutions? Well just don't define the variables in your class. Which goes against all OOP tutorials out there (including this one) and any programming intuition. Or don't use static initialisers, which is a well-known design pattern. Or add it manually to attributes, therefore implementing framework functionality (variant: call __set yourself). Or use inheritance and use protected (but are they gonna be protected? Don't know either). Maybe there are more solutions? I will stick to the first one and stop defining the fields in my class.

Collapse
 
tacsiazuma profile image
Krisztian Papp

Just a note that magic methods are several times slower than accessing a properly defined variable. It might sound cool and stuff for RAD, but no magic comes without a price.

Collapse
 
xstevenyung profile image
Steven Yung

Good note!

Thanks for sharing ๐Ÿ‘

Collapse
 
marlon316 profile image
Marlon Santana

Great Post!

Collapse
 
adi profile image
Adi Sk

Informative article, learnt something new about PHP and how it's utilized in Laravel.

Collapse
 
xstevenyung profile image
Steven Yung

Glad it helped someone ๐Ÿ‘

Collapse
 
chyn_km profile image
KM

Loved your post. Learnt a new feature.

Collapse
 
micstudent profile image
micstudent • Edited

What about should i read Book for Laravel to understand OOP

Collapse
 
akashdas profile image
Akash Das • Edited

There are so many magic methods in PHP. I found this article to understand all magic methods in one place.