Right way of adding additional attributes to an AR model?

Hi,

I am working on a user handling system. In order for the administrator to assign roles to the users. I want to include an array in the users AR model containing the assigned roles for the the current user. I get the roles for the current user from the auth system by doing this:



<?php


   $roles=Yii::app()->authManager->getRoles($this->id);


    foreach($roles AS $role)


    {


      $this->rolesArray[] = $role->getName(); 


    }


?>


In order to reach the rolesArray from the view I had to create a public attribute in the model called $rolesArray.

I am using this code in the view:



<?php echo CHtml::activeLabel($user,'Roles'); ?>


<?php echo CHtml::activeCheckBoxList($user,'rolesArray',$user->getRolesList()); ?>


Where getRolesList in the model returns an array of all available roles assigned in RBAC.

This all works fine except for that the attribute rolesArray is not added to the attribute array of the User model. So when the new selection of roles are posted the rolesArray is available in the _POST array but not in the attributes array of the user.

Am I approaching this in the right way? What is best practise if you want to add attributes to an AR model object(I don't really like to put the attribute rolesArray as public)?

ps: The next step is to assign or revoke the roles dependng on if they are ticked or not.

/John

Why you don't like declaring rolesArray as a public member of your AR class?

If you do however set rolesArray as an attribute of the user model, it works, right?

And why don't you want to make it an attribute of the user model anyways?  Please explain.

Quote

If you do however set rolesArray as an attribute of the user model, it works, right?

Yes it works fine if I declare the roles array like this:

public $rolesArray;

Quote

And why don't you want to make it an attribute of the user model anyways?  Please explain.

I cannot set the roles array as an attribute. At the moment I do this in afterFind in the model. I have tried to use this code:

$this->setAttributes('rolesArray', $this->rolesArray);

But that gives me the error message:

Indirect modification of overloaded property User::$attributes has no effect

Maybe there is another way to add the rolesArray to the attribute array.

Quote

Why you don't like declaring rolesArray as a public member of your AR class?

Well it might be an inheritance from my Java background. I would set the attribute as private and create public getters and setters for it. Since this is already done using magic getters and setters in the CActiveRecord file my problem would be solved if I found a way to add the rolesArray to the attributes array.

Any more thoughts on this?

It should be setAttribute(), not setAttributes().

Oh, of course it should be setAttribute()  :-[

I have tried it and it seems to work. I also tried it setting rolesArray to to be private. That didn’t work, I get this message

User does not have attribute “rolesArray”.
, but I  guess that is by design.

I was running Xdebug in Eclipse and I was watching the attributes array. That confused me because I couldn't see rolesArray be added to the attributes array. I tried to use getAttribute and it returned what I expected so it is all fine now.

I also tried to use getAttributes after using setAttribute. Get attributes did not return the attribute rolesArray. The description for getAttributes is:

Quote

names of attributes whose value needs to be returned. If this is true (default), then all attribute values will be returned, including those that are not loaded from DB (null will be returned for those attributes). If this is null, all attributes except those that are not loaded from DB will be returned.

Since I called without parameters I am expecting to get all the attributes back including rolesArray. Is this a bug?

Thanks for all the help

The attributes array only stores data for the table columns.

I don't know why you want to call this following. What are you trying to accomplish here?

$this->setAttribute('rolesArray', $this->rolesArray);

I want to make the rolesArray be a part of the model. So I don't have to treat it separately when posting the data.

I want to be able to do this:



<?php


  public function actionUpdate()


  {


    $user=$this->loadUser();


    if(isset($_POST['User']))


    {


      $user->attributes=$_POST['User'];


?>


Without having to treat rolesArray separately.

Like I have to do now since the rolesArray is not a part of the attributes array:



<?php


$user->setAttribute('rolesArray', $_POST['User']['rolesArray']);


?>


Is there another way of achieving this?

Did you declare 'rolesArray' in safeAttributes()?

No, will that give me the wanted functionality?

Yes, because the default implementation simply returns all table columns except the primary key. Since rolesArray is not one of them, you need to manually declare it. (this is for security reason)

more information here: http://php-thoughts…bute-scenarios/

Thanks Jonah!

That was an excellent post. I will definitely use the scenario based validation.

Quote

Yes, because the default implementation simply returns all table columns except the primary key. Since rolesArray is not one of them, you need to manually declare it. (this is for security reason)

This worked perfectly fine. This in combination with the scenario based validation will give me exactly what I was looking for.

This is what the safeAttributes method looks like now:



  public function safeAttributes()


  {


    $customSafeAttrArray = array('rolesArray');


    $currentSafeAttributes = parent::safeAttributes();


    return array_merge($customSafeAttrArray, $currentSafeAttributes);


  }


Thanks guys Yii is looking more promising by the minute.

no problem.  Good luck!