php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

PHP’s Magic Methods

Posted by on May 19, 2024

As PHP developers, we need to know a lot of language features to make our code easy to write and maintain. Without explicitly being told about a part of the language, it’s hard to even know it exists, so today, we’re going to discuss the magic methods our classes have and how we should use them to write our code.

What Are Magic Methods?

Magic methods are special methods defined inside the PHP core language that are called when certain actions are performed on an object. They allow us to override how PHP would interact with the object normally and inject our own logic in its place.

Magic methods are prefixed with two underscores (__) and have a wide variety of uses. All of the magic methods are optional, so don’t feel like you must create them. They’re here to make our job easier and not to create extra work for us. At least not intentionally.

construct() and destruct()

Without a doubt, the most important magic method in PHP is the __construct method. The __construct() method is used to define how a class should be constructed (or initialized). The body of the __construct method includes things like initializing variables and calling other functions inside the class.

class User
{
    public int $id;

    public function __construct(public string $name)
    {
        $this->id = rand(1, 9999);
    }
}

$testUser = new User("Scott Keck-Warren");
echo $testUser->id;

Unlike other programming languages PHP only allows a single constructor so if you need to have multiple ways to initialize a class you’ll either need to jump through some hoops by providing internal logic that determines what to do based on the types of the parameters or use the Factory Method Design Pattern which provides static functions to provide different ways to initialize the class.

The __destruct function is used when there are no references to a particular object or during PHPs shutdown sequence. This is useful to clean up resources like connections to external services or pointers to files that your class may have created during it’s lifecycle.

For example in this class we’re going to echo the name of the functions as they’re called an you can see the sequence of function calls.

<?php
class StartUpAndShutDown
{
    public function __construct()
    {
        echo "__construct", PHP_EOL;
    }

    public function __destruct()
    {
        echo "__destruct", PHP_EOL;
    }

    public function doSomething(): void
    {
        echo "doSomething", PHP_EOL;
    }
}

$testClass = new StartUpAndShutDown();
$testClass->doSomething();
unset($testClass);

The output of this is:

__construct
doSomething
__destruct

call() and callStatic()

The next two functions we’re going to discuss are the __call and __callStatic functions. These are called by PHP when we attempt to call a function or static function that doesn’t exist, so instead of issuing a fatal error, we can intercept the call and perform some kind of action in its place.

<?php
class ClassWithCallAndStaticCall
{
    public function __call(string $name, array $arguments): mixed
    {
        echo PHP_EOL, PHP_EOL;
        echo $name, " ", var_export($arguments);

        return null;
    }

    public static function __callStatic(string $name, array $arguments): mixed
    {
        echo PHP_EOL, PHP_EOL;
        echo $name, " ", var_export($arguments);

        return null;
    }
}

$testClass = new ClassWithCallAndStaticCall();
$testClass->oldFunctionName();
ClassWithCallAndStaticCall::mispelledFunction();

The $name argument is the name of the method being called and the $arguments argument is an array containing the parameters passed to the function we’re trying to call.

There are several use cases for this, but the one I always like to use them for is to be able to write functions that allow you to pass a parameter as part of the function name. That way we can write a function call like whereName("Scott") and have it be equivalent of where("name", "Scott") it’s a small difference that makes the code a little bit easier to read.

<?php
class ClassWithCallAndStaticCall
{
    public function __call(string $name, array $arguments): mixed
    {
        if ($name == "whereName") {
            $this->where("name", $arguments[0]);
            return $this;
        }

        return $this;
    }

    public function where(string $key, string $value): void
    {
        var_dump("where {$key} = {$value}");
    }
}

$testClass = new ClassWithCallAndStaticCall();
$testClass->whereName("Scott");

The other use case for this is to be able to dynamically remap a function call. For example, we might be renaming a function (one of the best refactoring tools we have in our toolbox) and need to keep the old function name around but want to keep it “hidden” from new development. We can do so using the __call() and __callStatic function.

<?php
class OurClass
{
    public function __call(string $name, array $arguments): mixed
    {
        if ($name == "oldName") {
            $this->newName($arguments[0]);
            return $this;
        }

        return $this;
    }

    public function newName(string $value): void
    {
        var_dump("newName with {$value}");
    }
}

$testClass = new OurClass();
$testClass->oldName("Scott");

The huge downside to using __call and __callStatic is that our editors (and any static code analysis tools like PHPStan) won’t know about them. To get around this we can define the function as part of the docBlock at the start of the class definition.

/** 
 * @method oldMethodName(): void
 */ 
class OurClass {

}

get(), set(), isset(), and unset()

This next batch of magic methods provides logic to support inaccessible or non-existant properties. The get(), set() magic methods are used when reading or writing, respectively, to an inaccessible or non-existant property. The isset() magic method is used when calling isset() or empty() on an inaccessible or non-existant property. The unset() magic methods is called when running unset() on an inaccessible or non-existant property.

There are a couple of use cases for these but there are two that I’m fond of.

The first is providing a way to have a class track all it’s data inside an array instead of properties. This is helpful is we’re loading an unknown number of properties (maybe from a database) and need to keep them in a form we can easily manipulate and then sent back to a persistance layer or external service.

<?php
class DynamicFields
{
    public array $properties = [];

    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }

    public function __get(string $name): mixed
    {
        return $this->properties[$name];
    }

    public function __isset(string $name): bool
    {
        return isset($this->properties[$name]);
    }

    public function __unset(string $name): void 
    {
        unset($this->properties[$name]);
    }
}

$dynamicField = new DynamicFields();
$dynamicField->name = "Scott";
echo $dynamicField->name, PHP_EOL;
echo isset($dynamicField->name) ? "Yes" : "No", PHP_EOL;
unset($dynamicField->name);
echo isset($dynamicField->name) ? "Yes" : "No", PHP_EOL;

This will output:

Scott
Yes
No

The other is to rename a property and provide access to the old name while you (or others) update their code.

<?php

class DeprecatedName
{
    public string $email = "scott@phparch.com";

    public function __set(string $name, mixed $value): void
    {
        if ($name == "emailAddress") {
            $this->email = $value;
        }
    }

    public function __get(string $name): mixed
    {
        if ($name == "emailAddress") {
            return $this->email;
        }
    }
}

$deprecatedName = new DeprecatedName();
$deprecatedName->emailAddress = "updatedEmail@phparch.com";

// output is "updatedEmail@phparch.com"
echo $deprecatedName->email, PHP_EOL;

Again, the huge downside to these functions is that our editors and static code analysis tools won’t know about the properties. To get around this we can define the properties as part of the docBlock at the start of the class definition.

serialize() and unserialize()

The serialize() and unserialize() magic methods are used by PHP when we serialize or unserialize an instance of our class to determine what properties should be included and how we want to encode the data. We did a whole video about serialization in PHP and you should check that out [()].

As an example, we have a user class below.

<?php

class User
{
    public string $password = "";

    public function __construct(
        public string $name,
        public string $email
    ) {
        $this->password = "originalPassword";
    }

    public function __serialize(): array
    {
        return [
            "name" => $this->name,
            "email" => $this->email,
        ];
    }

    public function __unserialize(array $data): void
    {
        $this->name = $data["name"];
        $this->email = $data["email"];
        $this->password = "Monkey1234!";
    }
}

$user = new User("Scott", "scott@phparch.com");
echo $user->name, PHP_EOL;
echo $user->password, PHP_EOL;
$serilized = serialize($user);
$redone = unserialize($serilized);
echo $redone->name, PHP_EOL;
echo $redone->password, PHP_EOL;

We can call the serialize() function on an instance of the class and get a string that represents the class. Normally PHP would just take all the properties and put in in the string. In this case we’ve defined a __serialize method on the class which gets called to define what properties get exported.

Then we can call the unserialize() function on the string and recreate the class as it existed. In this case we’ve defined the __unserialize() method on the class which gets called instead with an assosative array containing the values.

This will generate the following output:

Scott
originalPassword
Scott
Monkey1234!

There’s also a __set_state() magic method is called much like the __unserialize function but it’s used to recreate a class exported using the var_export() function. I’m not going to demo how it works because it’s a really hard to argue why most people would need it when there are better options. If I’m wrong let me know in the comments section.

__toString()

The __toString magic method is used if we attempt to convert an instance of our class to a string.

This is useful if you want to be able to easily output your class in a format that’s readable. I will sometimes include this in classes I need to apply some formatting to, like a user class where we track the first and last names separately. We can then use the __toString method to concatate them automatically when we display the user.

<?php

class StringableUser
{
    public function __construct(private string $first, private string $last) { }

    public function __toString(): string
    {
        return "{$this->first} {$this->last}";
    }
}

$stringableUser = new StringableUser("Scott", "Keck-Warren");

// output: "Scott Keck-Warren"
echo $stringableUser, PHP_EOL;

__invoke()

The __invoke() magic method is used to allow us to call an object as a function. This is useful if you need to pass a Callable argument to a function and need a way to organize the callable for later use (rather than writing an idential closure in multiple locations).

For example if we have the following code:

<?php

function displayInformation(Callable $func) {
    echo "Calling Callable", PHP_EOL;
    $func();
}

class Scott {
  public function __invoke() {
    echo "Keck-Warren";
  }
}

$callableClass = new Scott();
echo displayInformation($callableClass);

This will display:

Calling Callable
Keck-Warren

__clone()

The __clone() magic method is called just after an instance of a class has been cloned. This is helpful if you need to update a property or do a deep copy of the object.

As an example, in the class below we’re keeping track of when the class was created. When we clone the class we need to update the created property with a new DateTimeImmutable [()] which we can easily do in the __clone() method.

<?php

class CloneableClass
{
    public \DateTimeImmutable $created;

    public function __construct()
    {
        $this->created = new \DateTimeImmutable();
    }

    public function __clone()
    {
        $this->created = new \DateTimeImmutable();
    }
}

$original = new CloneableClass();
// output: 2023-12-28 12:14:35
echo $original->created->format("Y-m-d H:i:s"), PHP_EOL;
sleep(4);
$cloned = clone $original;
// output: 2023-12-28 12:14:39
echo $cloned->created->format("Y-m-d H:i:s"), PHP_EOL;

__debugInfo()

The __debugInfo() magic method, which annoyingly is the only camelCase magic method, is called by the var_dump() function to get the properties that should be shown.

<?php

class User
{
    private string $id = "neverShowThis";

    public function __construct(public string $email, private string $password) { }

    public function __debugInfo()
    {
        return [
            "email" => $this->email,
            "password" => "it's a secret",
        ];
    }
}

$testUser = new User("scott@phparch.com", "mySecurePassword");
var_dump($testUser);

The output of this is:

object(User)#1 (2) {
  ["email"]=>
  string(17) "scott@phparch.com"
  ["password"]=>
  string(13) "it's a secret"
}

What You Need to Know

  • Magic methods are methods we can define on our classes
  • Interrupt the standard PHP logic
  • Lots of different options and use cases

Tags: , ,
 

Leave a comment

Use the form below to leave a comment: