Should You Use PHP’s Magic Methods or Not?

PHP 

Are PHP’s magic methods worth using? When you use them, can you truly write testable code? Let’s explore one side of the argument today.


As a freelance PHP developer, I’m always looking to improve the quality of the applications I produce. So, over the last 6 - 12 months, I’ve been learning as much as possible about testing. During this time, I’ve found the way I code is dramatically changing – and improving!

Some constructs, both large and small, which I’ve used habitually for many years, have been falling by the wayside, as I find out they’re either extremely difficult or impossible to test. For example, In a recent development session, I attempted to test some of my laminas-db based classes, which use PHP’s magic methods for dynamically building where clauses.

Here’s an example:

$select
    ->where
    ->lessThanOrEqualTo(
        "dateOfBirth",
        new \Laminas\Db\Sql\Expression(
            sprintf(
                "STR_TO_DATE('%s','%%d-%%m-%%Y')",
                $searchCriteria->getValue("endDate")
            )
        )
    );

The code calls lessThanOrEqualTo() on the dynamic property, where, to find records which have a dateOfBirth field value no later than the formatted date supplied. The where property is provided by an implementation of the __get magic method in \Laminas\Db\Select, which you can see below:

public function __get($name)
{
    switch (strtolower($name)) {
        case 'where':
            return $this->where;
        case 'having':
            return $this->having;
        default:
            throw new Exception\InvalidArgumentException(
                'Not a valid magic property for this object'
            );
    }
}

The method returns $this→where which is a \Laminas\Db\Sql\Where object. This, in turn, extends \Laminas\Db\Sql\Predicate\Predicate. The level of indirection aside, it’s not that complex and, personally, I like the simplicity and flexibility of it.

But Is It Testable?

I can’t speak for what it’s like using PHPUnit’s mock objects, as I always use Mockery instead. But after attempting to do so in Mockery, I hit a stumbling block when trying to test the chained call. Not being able to resolve it, I turned to #zftalk on IRC (now https://laminas.slack.com) and after some discussion there, I revisited the manual to find the following:

PHP magic methods which are prefixed with a double underscore, e.g. _set(), pose a particular problem in mocking and unit testing in general. It is strongly recommended that unit tests and mock objects do not directly refer to magic methods. Instead, refer only to the virtual methods and properties these magic methods simulate. Following this piece of advice will ensure you are testing the real API of classes and also ensures there is no conflict should Mockery override these magic methods, which it will inevitably do in order to support its role in intercepting method calls and properties.

This lead me to start re-evaluating my attitude to PHP’s magic methods. Whereas previously I’ve loved using them for their simplicity and compactness, now I’m not so sure if the convenience is worth it.

As the manual suggests, "mock on the properties which the magic method refers to". Fine, not a deal-breaker. But perhaps it’s time to stop using them. Maybe it’s time to write more getters and setters instead, in the name of fully testable code. Maybe, while handy, they’re not worth using.

During research on the topic, I came across this Stack Overflow article, which in the comments, people make good arguments for and against.

Here are 3 examples:

Also, magic methods aren’t going to work with interfaces and abstract classes/methods. And what about visibility? It’s a leak to have a private or protected property modifiable by a magic __set method. Add these to your examples and I agree that there are many good arguments against using magic methods for getter/setters<s></s>and no real arguments in their favor. For quick one-offs, maybe use them for something, but they’re not a best practice for reusable code/libraries/APIs.

You should use stdClass if you want magic members, if you write a class - define what it contains.

Yes, this is exactly that :p. But another thing I forgot to mention is that: having getters for properties is more coherent with "real" methods where getXXX is not only returning a private property but doing real logic. You have the same naming. For example you have $user→getName() (returns property) and $user→getToken()

Here’s a summary from the article which triggered the post:

First of all, back then it was very popular to use <acronym title="Pre-Hypertext Processing">PHP</acronym>’s magic functions (_\_get, \_\_call etc.). There is nothing wrong with them from a first look, but they are actually really dangerous. They make APIs unclear, auto-completion impossible and most importantly they are slow. The use case for them was to hack <acronym title="Pre-Hypertext Processing">PHP</acronym> to do things which it didn’t want to. And it worked. But made bad things happen.

Interestingly, in the interests of speed, Google suggests to:

Avoid writing naive setters and getters.

Instead, they suggest declaring class variables with public visibility. Though, from my interpretation of the post, that’s only for the case of simple get and set, not where extra logic is required.

What’s your opinion?

I’ve decided to reevaluate using them for the time being and be more judicious in the use of them. What about you? Where do you stand? Do you avoid them, or can you not live without them? Share your thoughts in the comments.


You might also be interested in these tutorials too...

Tue, Oct 1, 2013

Using Traits for Code Reuse in Zend Framework 2

This is a post I’ve been meaning to write for a feels weeks now after I first started using Traits for simple reuse, as it solved a need I had at the time. After a while it seemed to be not too bad of a solution also. What Are Traits? If you’re not familiar with Traits the PHP manual describes them as: Traits are a mechanism for code reuse in single inheritance languages such as PHP.

Thu, Jul 11, 2013

Zend Framework 2 - The New HTML5 Form Fields - Part 2

In today’s post, we look at more HTML5 Form fields in Zend Framework 2: Month, Range, Color, Week and Number, as well as element properties and attributes. Come look around more of the great new elements available.

Simple Translations with Oxid eSales
Sun, Jan 27, 2013

Simple Translations with Oxid eSales

You may already be all over this one, but if you’re new to Oxid eSales or new to website internationalization, then today’s tip is one not to miss.


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus