Dynamically add attributes to model

Hello, developers!

This is my first post, so be nice :slight_smile:

I’ve checked the forum and did not really find any problem that was like the one I have.

My question follows: does Yii support dynamically loading attributes to a model?

Meaning, instead of creating a virtual attribute for a model, can I use some kind of dynamic loading?

I need to add attributes to the model so that Form builder can display the fields correctly.

Now I just get an error that the given attribute is not defined in the model.

The attribute depends on a type that a user has to choose. Meaning that if user chooses a Post type, the model should get attributes like Title and Body, but if the user chose a Blog type, the model should get attributes like Site name and Url, or something like that.

I tried to use CActiveRecord::instantiate($attributes), but it really did not work as wanted, because the Form builder still gives me the same error, that the attribute for the model is not defined.

I tried to be as clear as I could on this, so hopefully you understand the problem I have.

Thanks in advance!

What you probably want is something like this.

$rows = $db->createCommand($sql)->queryAll();

foreach($rows as $row)

{

echo $row[‘col_name’]; // col_name is name of column. e.g. id

}

Where $db is a CDbConnection.

Not really understanding what you’re doing. A model is usually based off a database, but if it’s not then you can add anything you want. But it sounds like your post and blog are in the same model? So do they go to the same database or are the posting somewhere else? It would help knowing what your data model is too (database design of post and blog). Or do you just want to dynamically change the visual form? That doesn’t require changing the model.

Hi!

You came quite close there. The blog and post was a bad example I guess.

I have a column in db named data, that is a serialized array of key=>value pairs. I need to build a form with different kinds of fields, depending on what type of controller a user chooses. I have created a controller for each type that a user CAN choose, but the problem here is, that I need to use the same model for them all.

Because the only attribute that exists in db is data, I have to create attributes on the fly for each controller.

So, if I have a controller named Test that needs to implement attributes like title, body and date, and another controller that is named Testing that needs to implement attributes like age, gender and date of birth. These use the same model, TestModel, that only has two columns in database, id and data.

I have to create a form that uses this model, but has these different types of input fields. These fields are serialized as an array in db-column data like so:

title=>‘test controller’, body=>‘Test controller body’, date=>‘2010-03-31’

I can not create a form for the Test controller, because the TestModel only has attribute data. There comes an error when trying to create the form with form builder: TestModel.title is not defined.

I don’t even know if it is possible to do that, but that’s why I asked. One solution would be to create a model for each controller, but that’s not an option for now.

Hi,

i for myself think i would do it the "old-fashioned" way by overriding the magic methods __get and __set (well and maybe __isset).

Something like this




public function __get($name)

{

  $arr = unserialize($this->data);

  if(isset($arr[$name]))

         $value = $arr[$name];

  else

         $value = parent::__get($name);


        return $value;

}



So you can get the ‘body’ in your serialized data-field by using $yourModel->body

But you should put the serialize and unserialize inside a behavior to avoid that everything is serialized/unserialized for every single get or set of an attribute.

Regard,

yoshi

Hi!

Thanks for the answer, gotta try that one out. I will report back if it works!

Hi for the last time for this post :)

Thank you yoshi for pointing out the behaviors! The overriding of __get() didn’t work, but we looked into the behavior system. So, by creating a class that extended CBehavior, with the wanted “non existent” attributes, we got the thing working.




class MyBehavior extends CBehavior

{

    public $name;

    public $type;

    public $url;

}


class MyModel extends CActiveRecord

{

    public function rules()

    {

        // Dynamically add safe attributes from the other controller

        return array('data'=>'safe');

    }

    public function behaviors()

    {

         return array('MyBehavior'=>array('class'=>'MyBehavior'));

    }

}



Edit:

 Removed the overloaded __construct because I found that public function behaviors() works the same way <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />

This worked for me, so I thank you all for the help!

Where you fill-in these attributes??

I’ve tried to fill it with a model method then call this method in afterCreate event, but It doesn’t work when I tried to fill CGridView.

The value displayed in the CGridView is the value I used to initialize the attribute in the class defination, not the one calculated from my method, I think that afterCreate not worked as I expect.