Attaching Behavior To Many (All) Models

Hi,

I’m just getting started on Yii2 (have done a couple of projects with Yii 1.1 earlier, both for personal use and professional), and it sure looks promising!

But I’ve run into a problem. I want to add some of behaviours to bascially all of my models, but from what I can understand that means that I have to copy & paste the same behaviour code in every model (50+).

I need autotimestamp for created_at / updated_at fields (when saving).

Also I would like to format my DATETIME fields to TIMESTAMP, when reading data from the database (and reversing from TIMESTAMP to DATETIME when saving).

And the third thing I’d like to do, is to add a created_by / updated_by field (user_id) when saving.

Isn’t there some way that I can attach these behaviours on all models without copying the same code into every model? Or is there another way of achieving this that I haven’t thought of?

The only conclusion I’ve come up with is to create models/BaseModel.php, have it extend the ActiveRecord and the make sure that all the desired models extends this BaseModel, but it feels like I’m “doing it all wrong” somehow…

Everything is fine, creating a base class to put common code in is good practice and often used.

Ok… so know I have the base model working as inteded!

95% of all tables have created_at/by

80% of all tables have created_at/by & updated_at/by

5% of all tables have neither

I’m trying to get the base model to be able to tell if created / updated fields exist for a given table, since it will throw an error complaining othervise… I’ve tried different solutions, but they all end up being very “hackish” - and I would prefer to avoid having different base classes for different models, if the table has “created”, “created” & “updated” or none of those fields.

Does Yii2 come with any suggestions on how to handle these scenarios?

Many ways to do this (no single right way). Suggesting a possible option…

You may keep an additional attribute in your BaseModel to track this. For example:




class BaseModel extends ActiveRecord

{

    /**

     * @var boolean whether to validate Timestamp behavior

     */

    protected $validateTimestamp = true;


    public function behaviors()

    {

        if ($this->validateTimestamp) {

            return [

                'timestamp' => [

                    'class' => TimestampBehavior::className(),

                    'attributes' => [

                        ActiveRecord::EVENT_BEFORE_INSERT => 'created_at',

                        ActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at',

                    ]

                ],

            ];

        }

    }

}



For models where you do not want timestamp behavior validation:




class YourModel extends BaseModel

{

    /**

     * @var boolean whether to validate Timestamp behavior

     */

    protected $validateTimestamp = false;


    // other code

}



Thanks, good suggestion, had forgotten about that one… I think however that this is how I will do it:




	public function behaviors()

	{

		return [

			// auto timestamp created_at / updated_at

			'timestamp' => [

				'class' => 'yii\behaviors\TimestampBehavior',

				'attributes' => $this->getWhenWhoWriteAttributes('at'),

				'value' => new Expression('NOW()'),

			],

			// store user id in created_by / updated_by

			[

				'class' => BlameableBehavior::className(),

				'attributes' => $this->getWhenWhoWriteAttributes('by'),

			],

		];

	}


	private function getWhenWhoWriteAttributes($type) {

		/*

		  Returns created / updated field

		  if / when they exist in table

		*/

		$attributes = [];

		$scenarios = [

			ActiveRecord::EVENT_BEFORE_INSERT => 'created',

			ActiveRecord::EVENT_BEFORE_UPDATE => 'updated'

		];

		foreach ($scenarios as $name => $field) {

			if ( array_key_exists($field . '_' . $type, $this->getAttributes()) ) {

				$attributes = array_merge( $attributes, [

					$name => [$field . '_' . $type]

				]);

			}

		}

		return $attributes;

	}



instead of this


array_key_exists($field . '_' . $type, $this->getAttributes()

you can use $this->hasAttribute($field . ‘_’ . $type)

http://www.yiiframework.com/doc-2.0/yii-db-baseactiverecord.html#hasAttribute()-detail