Preferred way to set dynamic default values in a CActiveRecord model?

I understand that I can set initial (default) values in a CActiveRecord like this:


class SimpleObject extends CActiveRecord

{

  public $direction = 'left';


  ...



So, assigning values to properties which are named like one of the attributes sets the default value for this attribute. Fine.

However, this does not work if I need dynamically calculated values, for example the following does not work:


class SimpleObject extends CActiveRecord

{

  public $user = Yii::app()->getUser()->getName();


  ...



It results in a syntax error. Now how should one set such a default value?

The init() method

First possibility: init() method. The CActiveRecord provides an (empty) init() method which can be overridden just for the purpose of populating your model with default values. This works:


class SimpleObject extends CActiveRecord

{

  public function init()

  {

     $this->user = Yii::app()->getUser()->getName();

  }

  

  ...



Is this "the real way"? I am asking because I discovered 2 other promising facts:

The CDefaultValueValidator

the documentation says:

Isn’t this exactly what I want? However, I could not make it work. I tried this:


class SimpleObject extends CActiveRecord

{

  public function rules()

  {

    return array(

      array('user', 'default', 'value'=>Yii::app()->getUser()->getName()),

    );

  }


  ...



But the input form (_form.php) in the “create” scenario of CRUD doesn’t show the default value, it was empty. Have I done something wrong?

The property CActiveRecordMetaData->attributeDefaults

As far as I have understood, this meta data is just filled automatically by reading the database metadata and cannot be modified manually in a useful way. So this is not the right place for setting dynamic default values. Am I right?

To sum up, these are my questions:

[list=1]

[*]How to use the CDefaultValuesValidator properly and which purpose does it serve exactly?

[*]Am I right that CActiveRecordMetaData->attributeDefaults is of no interest with respect to setting dynamic default values?

[/list]

Use the init() method ;)

Why would you create a new property named like existing attribute?

If your module has an attribute eg. name in the desired action like actionCreate() after the


$model=new Model; 

you can use


$model->name="whatever you need";

Edit:

the default validator gives default values to an attribute but it’s run as an validator… it means that the attribute will get the default value when the form is validated… not before opening the form…

It can be used for example to set an attribute to desired value if it’s empty (if nothing entered in the form)

I am sure I saw it somewhere - damn I just don’t remember where. But it was either in the Blob demo app or from the Yii book (“Agile Web Application Development with Yii1.1 and PHP5”), so I took it for granted.

Is it considered bad style to set the default value like this? Just to be consistent, maybe you should always use the init() method, is this what you are proposing?

Aaaahhh … silly me, of course. Thanks for pointing this out!

@lgoss007 a concise and helpful answer, too ;)


class SimpleObject extends CActiveRecord

{

  public function rules()

  {

    return array(

      array('user', 'default', 'value'=>Yii::app()->getUser()->getName()), 'on' => 'initform',

    );

  }


  ...

Maybe this solutions is usefull


  public function initEmptyForm(){

    $field = array();

    foreach($this->rules() as $chunk){

      if ($chunk[1] === 'default'){

        $params = array_slice($chunk, 2);

        $validator = CValidator::createValidator($chunk[1], self::model(), $chunk[0], $params );

        $validator->validate($this);

      }

    }

  }

Usign this code, when create form:




$form = new SimpleObject('initform');

$form->initEmptyForm();



I have the same question but my situation is more tricky.

I have a model A and has two children tables. Table B and table C

As you already know you create a unique key in table B and table C which also play the role of the foreign key to table A

So properly populated the id of a row of table A exists in table B or exists on table C

As you imagine Yii has created automagically (using gii) relations for this situation

So model A "HAS_ONE" model B

also model A "HAS_ONE" model C

BUT inside init() function when I try to access the relations. I mean when I try to execute something like modelA->modelB or modelA->modelC then the result is ALWAYS null

But after initialization is completed, the (for example) modelA->modelB variable contains a real modelB object !!

Is this the normal situation for init() function or should I use another "initialization" function which I cannot find …

I guess the answer is something obvious but could you please share it? :)

Yes, in the init() function nothing in the class is initialized, this is the very first thing to be called. It depends on when you are trying your initialization and what you are trying to do.

init() is good for setting initial values for the class that don’t have anything to do with the database.

afterFind() is good for setting values after a query has been performed.

getModelA() is good for pulling the data when needed (this would be a custom property which you create).

It really depends on why you are filling two separate models related to A and what is best for performance.

Igoss007 thank you for pointing out the afterFind() method :)

Although I believe I made some progress to answer my own question.

I discovered yii has a cool feature: If you implement a getter and a setter for a variable that does not exist then this, let’s say dynamic, variable is available for use. Meaning that the getMyvar() and setMyvar($value) inside a model automagically creates the property $model->myvar to be handled in the same way as a common property

In fact I did not even have to declare the variable anywhere. I started filling the rules and attributeLabels public functions and I could use it as a common property! Even create a form element for this to get a value (the setter method here plays its role)

Please let me know if you found it useful or if you suggest a different approach (:

Yes that’s the custom properties I was talking about… I use it quite a bit. And yes that can be a good approach and sounds like (from what I can gather) it might be a good approach for you.

I took this approach, which allows me to tune the details just before its validated or saved.

The benefit is you can set a value based on the input of another value.

The down side is you have to ensure not to run costly processes twice.


class ActiveRecord extends CActiveRecord

{

    public function setDefaultAttributes() {

    }

    protected function beforeValidate() {

        $this->setDefaultAttributes();

        return parent::beforeValidate();

    }

    protected function beforeSave() {

        $this->setDefaultAttributes();

        return parent::beforeSave();

    }

}

Then in your model do this:


class MyModel extends ActiveRecord{

    public function setDefaultAttributes() {

        // if the name is not set

        if (!$this->name) {

            $this->name = $this->type.'-'.date('Y-m-d'); // set it to something

        }

        // if the date is the default

        if (!$this->start_date == '0000-00-00') {

            $this->start_date = null; // set it null so it saves cleaner

        }

    }

}

Hi all,

I wanted to initialize the model before the form is rendered, using the model::rules() definitions, because they are the centralized solution for data defaulting.

Here is the way I found, inspired from getValidators() method in framework/base/CModel.php :




public function actionCreate() { // (the same in update and duplicate actions)

	$model = new Mymodel;


	// Defaulting fields before the form is output

	foreach($model->getValidators() as $validator) { // for each model::rules

		if ($validator instanceof CDefaultValueValidator) { // if 'default' validator

//			$model->addError('id',print_r($validator,1)); // see them for debug purpose

			$validator->validate($model, $validator->attributes); // do the validation (so, the initialisation) against relevant attributes

		}

	}


	etc ...