PHP Magic Methods: A Powerful Yet Tricky Feature

How to use PHP magic methods carefully

Lucas Pereyra
7 min readJun 22, 2022

PHP magic methods are undoubtedly an interesting feature of this programming language. Magic methods are methods that can be implemented in a class when using an OOP approach, that will be called under certain scenarios automatically by PHP. There are a bunch of different magic methods that serve different purposes and some of them are really useful when working under certain restrictions.

However, magic methods are an exclusive PHP based feature. This means that it’s not an OOP standard feature, being unsupported in other OO programming languages different from PHP. Having said that, abusing of PHP magic methods could give rise to a non-portable hard-to-understand codebase, and that’s why you should proceed consciously when using them.

Magic methods to avoid

Here’s a list of the PHP magic methods that I consider harmful to any codebase and therefore should be avoided and only implemented as a last resort:

__call & __callStatic

By implementing these methods a class can provide a default implementation for any possible public method call without explicitly declaring it as part of its interface. __call allows to define instances’ method implementations for methods that aren’t declared in the class interface, whereas __callStatic allows to define class’ method implementations following the same logic. Take a look at the following example:

<?phpclass Example
{
public function __call($method, $args = [])
{
if ($method == 'methodX') {
return "You've invoked methodX";
}
return null;
}
}
// then...
$instance = new Example();
echo $instance->methodX();

As you can see in the example above, methodX wasn’t explicitly declared in the class’ interface. This is specially dangerous. This practice limits your IDE autocompletion feature, in a way that you’ll be looking at the code of the Example class every time you need to know what’s its public interface like. This increases intellectual complexity and decreases abstraction. Advantages of inheritance and polymorphism begin to blur, too.

Generally speaking, the more complex and tricky the implementation of __call and __callStatic methods are, the more difficult will be to understand the class’ interface from the client’s point of view, and the more entangled the codebase will be. As a first approach, try to avoid them by explicitly implementing your public interface’s methods. As a second one, document and limit the scope of their usage.

__set & __get

These methods allow to implement default setters and getters for private/protected properties of an object. Both have proven to be useful in certain scenarios, simplifying the amount of code needed to define setters and getters for basic data holding objects (i.e. a DTO object).

Most of the time these methods aren’t harmful when they’re defined in a simple class that has just a few properties. Yet they increase complexity in an object that has many properties some of which require special treatment. As a rule of thumb, if you see “if” statements inside any of these methods (denoting special treatment for some properties), you’ll be better off extracting those setters and getters into their own methods.

Lastly, when you implement these methods, you’ll probably access an object’s properties like $object->property, and by doing so, not only you’re breaking encapsulation, but also you’ll be tempted to believe that property has been declared public, when in fact you could be calling the __get method indirectly. What if the __get method doesn’t do what you’re expecting? Having to be aware that there is an implementation of the __get method just increases the amount of things to keep in mind when coding. I strongly recommend explicitly declaring each setter and getter as needed.

__isset & __unset

These methods define how an object should behave when we ask if a certain property is set or is empty. I’ve never had the need to implement them before. Indeed, if you implement them, is because you’re having some conditional check like isset($object->property) in your code, which certainly doesn’t look healthy for it.

I almost feel the same as with __set & __get when it comes to these methods, thus, I’d rather implement my own explicit methods, replacing checks like isset($employee->salary) with $employee->hasSalary().

Magic methods to use carefully

This following list includes magic methods that I personally prefer to replace with my own versions. Though, used carefully, they could make a solution look better and fit your needs.

__toString

This method defines how an instance of a class (an object) should be represented as a string. It’s called behind the scenes whenever we treat an object as a string (i.e. by applying the string concatenation operator to it). I prefer not to implement it, making PHP explicitly warn me every time I misuse an object as a string. For that, I like to have an explicit toString or asString method to use whenever I need to, allowing me to totally control its behavior under all scenarios. Take a look at the following example:

<?phpclass Example1
{
public function __toString(): string {
return "Hello!";
}
}
// this won't break...
$object = new Example1();
echo $object;
class Example2
{
public function asString(): string {
return "Hello!";
}
}
// this will break...
$object = new Example2();
echo $object;
// Example2 forces you to explicitly call asString method..
$object = new Example2();
echo $object->asString();

__invoke

This method defines how an object should behave when we treat it as a function, and try to execute it (i.e. by doing $object()). This can be useful when we have classes that will have only one public method, as when implementing a Strategy pattern. However, I prefer to have an explicit execute, run or process method to explicitly call instead. By doing this, if you accidentally execute $object() in a wrong place, PHP will throw out an error that you can easily fix at an early stage. On the contrary, PHP could behave silently if the same happens using the __invoke method, therefore hiding the existence of issues that could arise later. Lastly, I’m pretty sure that executing an object as if it was a function is not something we naturally see in standard programming environments.

Useful magic methods

This following list includes the magic methods I personally prefer to use over the rest of them. Most are intended to extend PHP’s default behavior under certain scenarios or improve debugging aids. I’ve worked with almost all of them without suffering any headaches in the past.

__construct & __destruct

These methods define how an object should be initialized and cleaned up. __construct is much more popular than its opposite __destruct, and defines what’s called in OOP a constructor. Object properties should be initialized there, as well as any other internal state.

On the other hand, __destruct provides a way to define a destructor for an object. Cleaning up properties, global shared data and resources are common tasks to perform here.

Every programming language has its own syntax to define constructors and destructors, and this is how PHP does so. Therefore, trying to build your own version of these, would definitely go against any PHP standard resulting in a custom misleading approach.

__sleep, __wakeup, __serialize & __unserialize

These methods offer a way to extend PHP’s default behavior when serializing/deserializing objects, and are usually used together. By implementing them you can define how an object will be turned into a string representation and viceversa, and how internal state and resources that object consumes will be restored later. I cover more details about this topic in PHP __sleep And __wakeup Magic Methods: How And When To Use Them?.

Of course you can define your own implementation of the serializing algorithm for your own classes as private methods, but there will be no escape from implementing at least one of these methods, which would then be called automatically by PHP, hence calling your implementation.

__set_state & __debugInfo

These are very useful methods for extending PHP’s debugging aids and reporting behavior. By implementing them you can add or remove information of your objects that will be shown when executing var_export or var_dump on them. These facilities allow you to customize how an object’s internal state will be reported at the time of debugging, without useless or confusing information.

__clone

This method is specially useful to implement the Prototype design pattern in PHP. It allows to customize how an object could be cloned when using the clone operator on it. This method is automatically called by PHP once an object has been cloned by means of the clone operator, allowing you to initialize properties and clean up undesired references to other objects. Anyway, if you want to implement the Prototype design pattern and gain total control over it, you could also proceed by defining your own copy() method.

Conclusions

PHP magic methods provide a means to extend default behaviors under special scenarios, most of them handled by PHP internally. However, you should proceed carefully when using them to avoid unnecessary complexity or implicit logic to be spread throughout your codebase.

Some magic methods are interesting and have proven to be useful to address specific low level issues, which are normally related to how PHP works behind the scenes. On the other hand, many other magic methods provide additional facilities that allow you to code in a less verbose manner (as __set & __get). Beware of these tricky fancy facilities. Most of them discourage good OOP practices like information hiding, encapsulation and abstraction.

Your code will be read probably much more times than it will be modified in the future. Pay special attention to readability and try at any extent to make coding decisions as explicit as you can. Avoid implementing magic methods that break encapsulation or cut down abstraction. Avoid implementing magic methods that could hide the existence of bugs. Claim total control over your classes by implementing your own interfaces and encouraging defensive programming practices.

This article is strongly based on my experiences as a PHP developer and struggles I’ve came through in the past. I hope it was useful and I thank you for your reading! Stay connected for more content.

--

--

Lucas Pereyra

Systems Engineer, full-time Backend Developer, part-time learner.