Magic Methods Within Yii2 __set

This article is continued from the previous post about the __get method.

The basic __set method is contained within the Object class and performs a simple check to ensure a property can be set. It looks for a method with the ‘set’ prefix, executes if found, or, if not, checks to see if the property is readonly by looking for a ‘get’ prefix and finally throws an unknown property exception. 

public function __set($name, $value)
{
    $setter = 'set' . $name;
    if (method_exists($this, $setter)) {
        $this->$setter($value);
    } elseif (method_exists($this, 'get' . $name)) {
        throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
    } else {
        throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
    }
}

This works well to ensure that all objects contain the properties necessary for them to function; however, if you wish to dynamically generate properties, you will have to overwrite this method in order to do so.

The more complicated version is found within the Component class. As you can see, the same set method check as the Object class. However, instead of continuing on like the base class, it performs two more checks. Using a binary string comparison (strncmp) it checks to see if you’re trying to dynamically set an event or attach a behavior. Finally, it will attempt to set a property and attached behavior.

By prefixing your property name with ‘on ‘ you can attach an anonymous function to the property name as the name of the event.

new foo([
    'on afterSave' => function ($event){
        // this will be triggered by the afterSave method in BaseActiveRecord 
    },
])

By prefixing your property name with ‘as ‘ you can attach a behavior with the property name as the name of the behavior within the class.

new foo([
    'as Bar' => 'qualified/namespace/to/bar',
    'as fooBar' => new qualified/namespace/to/fooBar([
        'property' => 'value'
    ])
])

 

/**
 * Sets the value of a component property.
 * This method will check in the following order and act accordingly:
 *
 *  - a property defined by a setter: set the property value
 *  - an event in the format of "on xyz": attach the handler to the event "xyz"
 *  - a behavior in the format of "as xyz": attach the behavior named as "xyz"
 *  - a property of a behavior: set the behavior property value
 *
 * Do not call this method directly as it is a PHP magic method that
 * will be implicitly called when executing `$component->property = $value;`.
 * @param string $name the property name or the event name
 * @param mixed $value the property value
 * @throws UnknownPropertyException if the property is not defined
 * @throws InvalidCallException if the property is read-only.
 * @see __get()
 */
public function __set($name, $value)
{
    $setter = 'set' . $name;
    if (method_exists($this, $setter)) {
        // set property
        $this->$setter($value);

        return;
    } elseif (strncmp($name, 'on ', 3) === 0) {
        // on event: attach event handler
        $this->on(trim(substr($name, 3)), $value);

        return;
    } elseif (strncmp($name, 'as ', 3) === 0) {
        // as behavior: attach behavior
        $name = trim(substr($name, 3));
        $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

        return;
    }

    // behavior property
    $this->ensureBehaviors();
    foreach ($this->_behaviors as $behavior) {
        if ($behavior->canSetProperty($name)) {
            $behavior->$name = $value;
            return;
        }
    }

    if (method_exists($this, 'get' . $name)) {
        throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
    }

    throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}

Again, this works quite well for most situations, but, if you’re attempting to dynamically add properties to a class, this will prevent such an action.

Now, this only covers the base Object and Component class’s __set methods; however, they are the most complicated ones. Please refer to the methods for __set in BaseActiveRecord, DynamicModel, and GenerateController.

Recent Posts