credits: Bubble Wizard via photopin (license). posted on April 19th, 2016.

Magento's Magical Getters and Setters Demystified

Nothing new, but good to know!

For those of you who are already familiar with Magento 1 or 2, this article will tell you nothing new. For those who  are new to Magento, this article is fundamental information for almost every class in Magento. If you are one of those pupils that are new to Magento, keep on reading...

Getters and Setters

If you don't know what getters and setters are, they are a design pattern almost available in any programming language. They offer a way to keep private parameters private, and perform additional actions when someone from outside tries to access or set such parameter. An example:

class Example
{
    /** @var int */
    private $var;

    /**
     * @param $param
     */
    public function setVar($param)
    {
        $this->var = (int) $param;
    }

    /**
     * @return int
     */
    public function getVar()
    {
        return (int) $this->var;
    }
}

This is the most basic example of a getter and a setter in PHP. We have a private variable, $var, which we want to be able to set or get from the outside. We use 2 methods to make sure that what is saved or retrieved is always an integer.

So why not use public variables?

A good question. Consider the following example:

class AnotherExample
{
    /** @var int */
    public $var;

    /**
     * @return int
     */
    public function timesTwo()
    {
        return $this->var * 2;
    }
}

$instance = new AnotherExample();
$instance->var = 'I am not an integer!';

In this example we have a method called timesTwo(), who expects $var to be of an integer. Making $var a public accessible (and therefore, changeable) variable, all kind of weird stuff can happen since we can no longer be sure that $var will always be an integer.

How does Magento handle this?

You might already have used this without knowing how it works, but Magento models (and some other class types) have an interesting way to set and get data. Take the following example:

$this->setSomething('foo');
echo $this->getSomething();  // 'foo'

This code will work, without the need to declare the methods setSomething() and getSomething(). So how does this work? Well under water Magento maps above methods to the following code:

$this->setData('something', 'foo');
$this->getData('something');        // 'foo'

And this is perfectly valid code, because most class in Magento 1 extend from Varien_Object and most classes in Magento 2 extend from Magento\Framework\DataObject. Inside those classes is a method named __call(), which more or less has the following code:

public function __call($method, $args)
{
    switch (substr($method, 0, 3)) {
        case 'get':
            $key = $this->_underscore(substr($method, 3));
            $index = isset($args[0]) ? $args[0] : null;
            return $this->getData($key, $index);
        case 'set':
            $key = $this->_underscore(substr($method, 3));
            $value = isset($args[0]) ? $args[0] : null;
            return $this->setData($key, $value);
        case 'uns':
            $key = $this->_underscore(substr($method, 3));
            return $this->unsetData($key);
        case 'has':
            $key = $this->_underscore(substr($method, 3));
            return isset($this->_data[$key]);
    }
    // Exception handling
}

What kind of sorcery is this ?!?

For those who don't know __call() : it's awesome, but in the wrong hands is a great recipe for spaghetti code. What it does is the following: when PHP cannot find the method that is called (setSomething() in our example), it executes the magic method __call(). This method gets 2 parameters:

  • $method: this is the name of the method.
  • $args: these are the arguments passed to the method.

Now in Magento's case, it checks if the method starts with either get, set, uns or has and if so, maps it to getData(), setData(), unsetData() and isset().

This is of course a great little trick, but it's also important to know this when you want to develop with Magento 1 or 2.

Benefits and drawbacks

This way of working has 2 sides. The benefits are:

  • Easier to write code.
  • Great when rewriting or extending classes just to catch the reading of one parameter. For example, the getDescription()-method of the Product class doesn't exists. It just bubbles down to getData('description'). This means that if you want to do something whenever getDescription() is called, you can simply rewrite the Product class and only declare this method (however, only use rewrites as a last resort in Magento 2).
  • It works great with XML configuration too!. For example, in Magento 1, you can simply add <action method="setSomething"><var>foo</var></action> to make getSomething() work in your template file.

There are however some drawbacks as well:

  • Code completion: Needless to say, your IDE doesn't care about magic methods. This means that you have to add a bunch of @method-annotations to the docblock of your class. Ugly!
  • Performance: an extra call is an extra call. So if you let PHP figure out that when you use getSomething(), you actually want getData('something'), your wasting precious clock cycles. On performance intensive tasks, you might want to ditch the magic getters and setters, although it's performance impact might be small.

Magento 1 Magento 2 Undocumented