Magic Methods Within Yii2

by | Nov 14, 2016

Yii and Yii2 utilize various magic methods within the framework, the implementation of which can be found in the Object class, which most of the framework extends off of.  These magic methods __get, __set, __isset, __unset, and __call enforce a set of standards that are constant throughout the application. Understanding how these magic methods are written is important to understand how to interact with objects within the Yii framework.

The __get method within Yii2 performs several checks when it is called. First, it generates a variable that is the combination of the name of the property being called and appending to get to it. A check if that method exists is then run, if true the getter is executed. If not, it checks to see if a setter exists for the property. If so, an error is thrown indicating the attempt to access a write-only property. If all else fails, it results in a standard, getting unknown property error.

/**
 * Returns the value of an object property.
 *
 * Do not call this method directly as it is a PHP magic method that
 * will be implicitly called when executing `$value = $object->property;`.
 * @param string $name the property name
 * @return mixed the property value
 * @throws UnknownPropertyException if the property is not defined
 * @throws InvalidCallException if the property is write-only
 * @see __set()
 */
public function __get($name)
{
    $getter = 'get' . $name;
    if (method_exists($this, $getter)) {
        return $this->$getter();
    } elseif (method_exists($this, 'set' . $name)) {
        throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
    } else {
        throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
    }
}

Getters allow for the manipulation or creation of variables data on the fly. If you have two variables first_name and last_name in an object, you can create a method like the following

public function getFullName(){
    return $this->first_name . ' ' . $this->last_name;
}

Which when called like $foo->fullName will combine the two variables into one for easy display and concise code.

It is important to note that when utilizing these methods within a model object, where data is pulled from a data source, the get magic method is called before the variable generated by the data source is accessed.

Example:

class foo extends Object
{
    
    public function getBar() {
        return $this->bar;
    }   
}</pre>
<pre class="lang:default decode:true ">&lt;?= $foo-&gt;bar ?&gt;

In the above, even though it is unnecessary to have the getBar method to get the variable, it is still executed. This is because within the base __get call, a check is made with:

$getter = 'get' . $name;
if (method_exists($this, $getter)) {

And since method_exists is a case-insensitive function, it returns true resulting in its execution. It is usually unwise to overwrite the normal accessing of a model’s data attributes this way as it can lead to unintended manipulation of the data that is only visible when the code is executed.

Example:

class foo extends Object
{
    
    public function getBar() {
        return $this-&gt;bar;
    }   
}</pre>
<pre class="lang:php decode:true" title="Unintended Consequences">&lt;?= $foo-&gt;bar ?&gt;

As you can see above, the bar is now different than what is in the data source, which unless you are looking at both at the same time, you wouldn’t be able to tell. Tests, when not checking for explicit values, would pass even though they should fail. If this is unavoidable for whatever reason, the data source should be updated by triggering the save() method within the same execution the getBar was called.

Recent Posts