Assigning sub-properties using dotted paths.

After getting 'WontFix' for #416 I decided to describe the problem with a bit more details. Hopefully, this will impress core developers.

There is no core mechanism to set one or several values of an array. All you can do is override entire array. Here's an example (TSomeWidget):

public $htmlOptions = array('class' => 'some', 'style' => 'width: 100px');
<? $this->beginWidget('TSomeWidget', array('htmlOptions' => array(...))) ?>

Notice the place I marked with '…'. There you need to repeat all widget-defined

array values to add some attribute to the widget. Of course, this is all about

convenience. Just comparing Prado's XML approach with YII's PHP.

Isn't it possible to implement some mechanism so we could override magic methods

using behaviors? For example, dot-format could be something like:

<? $this->beginWidget('TSomeWidget', array('htmlOptions.class' => 'new_class')) ?>

In this situation existing array values would be preserved. If you care about

performance, the following might be an alternative (thought it's complicated):

<? $this->beginWidget('TSomeWidget', array('prop:htmlOptions.class' => 'new_one')) ?>

Where 'prop:' would invoke/autoload a behavior named 'prop' responding for assigning

sub-property values. Prop might also be a method autoloading matching behavior.

In fact, this is not a matter of XML/PHP-based approach but an implementation itself.

Prado allows dotted property assignment while YII doesn't. This will definitely save

developers from writing tons of dummy lines of code such as:

        $htmlOptions = $this->htmlOptions;


        if (!empty($this->class)) $htmlOptions['class'] = $this->class;


        if (!empty($this->style)) $htmlOptions['style'] = $this->style;


        


        $labelOptions = $this->htmlOptions;


        if (!empty($this->class)) $labelOptions['class'] = $this->class;


        if (!empty($this->style)) $labelOptions['style'] = $this->style;


        


        $inputOptions = $this->htmlOptions;


        if (!empty($this->class)) $inputOptions['class'] = $this->class;


        if (!empty($this->style)) $inputOptions['style'] = $this->style;

Also, with YII developers could use shared classes for complex properties.

At this very moment any property being an object cannot be configured without

tweaking around every property like this.

[SOLVED] See here: http://code.google.c…s/detail?id=416

Alternatively you can use the following class to plug this functionality in.

It works as a behavior and provides static methods at the same time. Here are some usage examples:



class TMyWidget extends CWidget


{


    public $htmlOptions = array('class' => 'default');


}





// Variant #1 - Attaches behavior to $widget


$widget = new TMyWidget();


$setter = new TSubProperty($widget);


$setter->setSubProperty('htmlOptions.class', 'new_class');





// Variant #2 - Memory-saving with static calls


$widget = new TMyWidget();


TSubProperty::setSubProperty($widget, 'htmlOptions.class', 'new_class');


Class itself:

class TSubProperty extends Cbehavior


{


    public function __construct($owner = null)


    {


        if ($owner !== null)


            $this->attach($owner);


    }


    


    public function __get($name)


    {


        return self::getSubProperty($this->getOwner(), $name);


    }


    


    public function __set($name, $value)


    {


        self::setSubProperty($this->getOwner(), $name, $value);


    }


    


    static function &getSubProperty($object, $path)


    {


        foreach (explode('.', $path) as $property)


            $object = &$object->$property;


        return $object;


    }


    


    static function setSubProperty($object, $path, $value)


    {


        if (($pos = strrpos($path, '.')) === false)


            $property = $path;


        else


        {


            $object = &self::getSubProperty($object, substr($path, 0, $pos));


            $property = substr($path, $pos + 1);


        }


        if (is_array($object))


            $object[$property] = $value;


        else


            $object->$property = $value;


    }


}