Attached Behavior Doesn't Get Access To The Owner From Within Getter Method.

So, i have this bevahior, called AttributesMap:




class AttributesMap extends \CBehavior

{

    // writable attributes list

    protected $_writeList ;


    public function setWriteList(array $map)

    {

        $_map = array();

        foreach ($map AS $attributeName) {

            if ($this->owner->hasAttribute($attributeName)) {

                $_map[$attributeName] = $this->owner->getAttribute($attributeName);

            }

        }

        $this->_writeList = new \CMap($_map);

    }

}



Next, i’m attaching it to a AR model:




public function behaviors()

    {

        return array(

            'attributesMap' => array(

                'class' => '\somenamespace\me\app\components\behaviors\AttributesMap',

                'writeList' => array('first_name', 'last_name')

            ),

        );

    }



When the execution reaches:




if ($this->owner->hasAttribute($attributeName)) {...



It throws an error because $this->owner is null.

Any pointers ?

Thanks.

Are you using this behavior as




$this->setWriteList(...)



Where $this is the instance of the active record?

Hi twisted,

Probably setWriteList() is called in the constructor (or initialization process) of AttributesMap, before it is attached to the owner.

Hi there,

@le_top - nope, i don’t even reach the point where i access this behavior, the error happens when it is attached to the model.

@softwark - not quite, take a look, here is my entire class:




<?php


namespace somenamespace\me\app\components\behaviors;


class AttributesMap extends \CBehavior

{

    // writable attributes list

    protected $_writeList ;

    

    // readable attributes list

    protected $_readList;


    public function setWriteList(array $map)

    {

        $_map = array();

        foreach ($map AS $attributeName) {

            if ($this->owner->hasAttribute($attributeName)) {

                $_map[$attributeName] = $this->owner->getAttribute($attributeName);

            }

        }

        $this->_writeList = new \CMap($_map);

    }

    

    public function setReadList(array $map)

    {

        $_map = array();

        foreach ($map AS $attributeName) {

            if ($this->owner->hasAttribute($attributeName)) {

                $_map[$attributeName] = $this->owner->getAttribute($attributeName);

            }

        }

        $this->_readList = new \CMap($_map);

    }

    

    public function getWriteList()

    {

        if (!is_object($this->_writeList) || !($this->_writeList) instanceof \CMap) {

            $this->_writeList = new \CMap();

        }

        return $this->_writeList->toArray();

    }

    

    public function getReadList()

    {

        if (!is_object($this->_readList) || !($this->_readList) instanceof \CMap) {

            $this->_readList = new \CMap();

        }

        return $this->_readList->toArray();

    }

}



It’s nothing too fancy actually. The error is triggered when the behavior is attached to the model, i’m not doing anything else.

I think it is sometihng in your attach context which implies that setWriteList is called too early.

The thing is that set* and get* are setters and getters and i think this is the problem.

If i




public function getSomething()

    {

        return $this->getOwner();

    }



and call that manually with




$model->behavior->getSomething(); // or

$model->behavior->something;



They both work, but as you say, i think in my case it happens to early and the owner doesn’t get set at that point.

Don’t know if i should report this as a bug or not.

Well, there you have it, which is what I referred to in my first reply.

You should write:


$model->getSomething(); // or

$model->something;

The base class (CComponent) works out where to find the method.

BTW, I have an extension that provides dynamic getters and setters, so the issue is not with the method being getters or setters.

Yes i understood your point from the start, what i mean is that i never call the setter setWriteList for example nor the getters.

that setter is triggered by Yii itself when i pass the writeList attribute to the behavior. The behavior does not have a public property called writeList. Do you see my point ?

You can try and simulate that.

Have a behavior with a setter method, something like:




class SomeBehavior extends CBehavior{

  public function setSomething($str) {

     var_dump($this->owner->anyPropertyOfTheModel);

  }

}



Then attach that behavior to a model like:




$model->attachBehaviors(array(

   'someBehavior' => array(

      'class' => 'SomeBehavior',

      'something' => 'this will trigger the fancy error',

   )

));



And run it. It will trigger the error.

Hi

The issue is with the fact the the initializer is called before the behavior is attached.

Fix it like this (using another initializer, or adjust you rmethod to behave differently when there is no owner yet):




public $initialWriteList;

public function attach($owner) {

    parent::attach($owner);

    $this->writeList = $this->initialWriteList;

}



@ le_top - thanks for the hint, i’ll give it a try when I reach a computer (writing from my phone now) and i will let you know about how it goes.

Just an update. I gave up upon the idea, i think it’s way easier to just:




$attributes = array('first_name', 'last_name');

foreach ($user->getAttributes($attributes) as $attributeName => $attributeValue) {

[...]

}



than to define a class to do same stuff, no point in reinventing the wheel after all.

Hi

It all depends on the level of abstraction you want to introduce and what the eventual goal is.

In your case it looks as if you want to limit the attributes that are readable and writeable for the class to a limited list.

So, IMHO, you are stll ‘reinventing’ the wheel with the last implementation.

Let me explain:

The model rules let you define which attributes are ‘safe’.

The list if safe attributes can be retrieved through ‘getSafeAttributes’.

The default ‘setAttributes’ allows you to set safe attributes only.

Where you need to ‘extend’ your class is where you want to read only the safe attribute.

This is where a behavior can be usefull.

To maintain your list, use the rules and scenarios.