Yii 2 Behaviors Blameable and Timestamp

One of the things that really made me fall in love with the Yii 2 framework is the intuitiveness of the behaviors method. Behaviors in Yii 2 is a very flexible concept that works on both models and controllers.

On the models, I’ve used two behaviors, timestamp and blameable. Both add a lot of efficiency to the model and prevent code duplication. I’ve talked about timestamp behavior before, I also cover it in my book, so I’ll just cover the basics.

Many times where you create or update a record in the database, you want to keep track of when you did it. This need is so common, that I’m almost tempted to push the behaviors method one layer down by creating my own class extending ActiveRecord and then by having all my models extend from that class. for example:


class MyActiveRecord extends ActiveRecord
{

 public function behaviors()
    {
        return [
            'timestamp' => [
                'class' => 'yii\behaviors\TimestampBehavior',
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                ],
                'value' => new Expression('NOW()'),
            ],
        ];
     }

}

So now, if I built all my models to extend MyActiveRecord instead of ActiveRecord, I get my created_at and updated_at fields updated automatically, assuming I have those fields in my DB table. Of course you would have to include a namespace and the appropriate use statements for it to work.

I generally tend not to use this approach, it’s too much for me because not every model that uses ActiveRecord I create will need it, and I prefer to do things the same way every time when I can, even if that is slightly more procedural. For example, I could extend MyActiveRecord when I want timestamp behavior and extend ActiveRecord when I don’t need the behavior. But then I have to keep track of it. Things like that really depend on the size of the project.

If I find there is a lot of code duplication with the behavior at the end of a project, I can optimize, and put the behavior in its own class as in the example. I would rather do it then because while I’m developing, it’s easier for me to get into the flow of the code by seeing the behavior in the same class as the model.

I’m not saying my way is a best practice, just sharing my particular method of doing it. At least now you are aware of some options and can decide for yourself.

Another pre-built behavior that yii 2 has made for you is the blameable behavior, which, when used in combination with the timestamp behavior, looks like this:


public function behaviors()
    {
        return [
            'timestamp' => [
                'class' => 'yii\behaviors\TimestampBehavior',
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                ],
                'value' => new Expression('NOW()'),
            ],
            'blameable' => [
                'class' => BlameableBehavior::className(),
                'createdByAttribute' => 'created_by',
                'updatedByAttribute' => 'updated_by',
                ],
            
        ];
     }

What blameable does is insert the current user id into the fields created_by and updated_by. This is a super-convient way of doing things. Every time a model gets created or updated, we know who to blame. Don’t you just love the name of the class?

This works out really well for large systems, where multiple users are doing admin and you need to keep track of who is doing what. You can also use this for frontend implementations, for example if you had a posts table and you wanted to use this method to keep track of the author. Alternatively, you could set that in the controller, but the behavior relieves you of having to write any additional code. This is extremely efficient, easy to implement, and easy to understand the benefit.

Now if I were writing a lazy tutorial, I would stop there. However, it’s worth noting that to extract the blameable username and display it, we need 4 additional methods on our model and the Yii 2 guide on behaviors does not mention them. Since this is not from the guide, please keep in mind that this is my way to do it, not necessarily official, and there may be a more concise way to do it that I’m not aware of.

However, until the docs are more complete, I had to come up with my own solution, which I’m pretty happy with at this point. I’m a big fan of these one line methods, which the Yii 2 guide does suggest using for relationships, so that’s how I approached this.

I simply created a get method to map the id field of user to the created_by field of the current model. Then in the next method, getCreateUserName(), we can access the username using the magic get syntax in a ternary statement:


public function getCreateUser()
{
    return $this->hasOne(User::className(), ['id' => 'created_by']);
}

       /**
       * @getCreateUserName
       * 
       */

public function getCreateUserName() 
{
    return $this->createUser ? $this->createUser->username : '- no user -';
}

I put in a condition for – no user – even though the blameable behavior will fire everytime and update to the current user. It doesn’t hurt to code in the possibility that there is no id stored, and in such a case, we return the string ‘- no user -‘. It would help you troubleshoot at the least.

Of course we had to do the same methods for updated_by, so that required 2 more methods.


public function getUpdateUser()
{
   return $this->hasOne(User::className(), ['id' => 'updated_by']);
}
       /**
       * @getUpdateUserName
       * 
       */

public function getUpdateUserName() 
{
    return $this->createUser ? $this->updateUser->username : '- no user -';
} 

I also had to add the following to my attribute labels method on my model:


 'createUserName' => Yii::t('app', 'Created By'),
 'updateUserName' => Yii::t('app', 'Updated By'),

And once you have those labels in place, you can use them in a view like so:


 <?= DetailView::widget([
        'model' => $model,
        'attributes' => [
            'id',
            'question',
            'answer',
            'faqCategory.faq_category_name',
            'faq_value',
            ['attribute'=>'createUserName', 'format'=>'raw'],
            ['attribute'=>'updateUserName', 'format'=>'raw'],
            'created_at',
            'updated_at',
        ],
    ]) ?>

Don’t worry about the rest of the example, that is my Faq model in the DetailView::widget. Ther relevant lines are:


 ['attribute'=>'createUserName', 'format'=>'raw'],
 ['attribute'=>'updateUserName', 'format'=>'raw'],
  

Those labels reference our get methods, using magic syntax, which means the get is implied, not explicit and you have to make the first word lower case. So now, instead of getting an id number displayed, we get the actual username. Cool stuff.

You can see that when we talk about implementing things, we have to jump around a bit. We went from behaviors on the model to calling a get method on the view, so we can return the username instead of user id.

Controllers can also implement the behaviors method, and this is incredibly useful for access control. I did a complete tutorial on RBAC using behaviors on the controllers, you should check that out if you want to continue on to see how you can use behaviors that way. I made implementing RBAC pretty simple, boiling it down to six easy steps.

I also cover this topic more extensively in my book, Yii 2 For Beginners.

Well, that’s it for this tutorial. Thanks for reading and thanks for your support. Feel free to add your comments and thoughts, it’s appreciated, as are links and reviews. Thanks again.

Advertisements

3 thoughts on “Yii 2 Behaviors Blameable and Timestamp”

  1. Well thanks for the article and your efforts to post your approach to the community. However, I’m a bit frustrated and perplexed. Here’s why. Both create and updated functions can be handled easily in the database without a lick of php or yii code. When a new record is inserted to the database, both columns will get the date, after that successive updates will cause the update column to receive a new date. And these columns will be dates which means that all sorts of analysis can be done on them in the database such as aggregate queries, etc. Again, the db (MySQL) can be set up to handle this automatically just by column definitions, no triggers. The solution you present is fine, if it must be done in Yii.

    1. Thanks for your feedback James. It’s common in php frameworks to have a function that sets created_at and updated_at with DateTime, even though as you point out, there are other alternatives.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s