If you're asking, "What's Yii?" check out my earlier tutorial: Introduction to the Yii Framework, which reviews the benefits of Yii and includes an overview of what's new in Yii 2.0, released in October 2014.
In this Programming With Yii2 series, I'm guiding readers in use of the newly upgraded Yii2 Framework for PHP. In this tutorial, we'll explore Timestamp Behaviors, which reduce the amount of code you need to write with each new model for the common operation of creating timestamps for inserts and updates. We'll also dive into the Yii2 source code, examining how a behavior is implemented.
For the examples in this tutorial, we'll continue to imagine we're building a framework for posting simple status updates, e.g. our own mini-Twitter.
Just a reminder, I do participate in the comment threads below. I'm especially interested if you have different approaches, additional ideas, or want to suggest topics for future tutorials.
What Is a Behavior?
Yii2 Behaviors are essentially mixins. Wikipedia describes mixins as "a class that contains a combination of methods from other classes. How such a combination is done depends on the language, but it is not by inheritance."
Yii describes them this way:
Attaching a behavior to a component "injects" the behavior's methods and properties into the component, making those methods and properties accessible as if they were defined in the component class itself.
Yii2 offers several built-in behaviors, most of which we'll be documenting, including sluggable, blameable, and timestamp. Behaviors are an easy way to reuse common code across many of your data models without having to repeat the code in many places. Injecting a behavior into a model can often be done with just two lines of code. As the number of models in your application increases, behaviors become more and more useful.
What Is the Timestamp Behavior?
The Timestamp Behavior makes it easy for us to implement the frequently needed task of assigning the current date and time to inserts and updates in an ActiveRecord model, automatically setting the properties for created_at
and updated_at
.
Earlier in this series, we implemented timestamp behavior manually. Whenever Status models were posted by the user submitting a form, we assigned the current Unix timestamp to both fields:
public function actionCreate() { $model = new Status(); if ($model->load(Yii::$app->request->post())) { $model->created_at = time(); $model->updated_at = time(); if ($model->save()) { return $this->redirect(['view', 'id' => $model->id]); } else { var_dump ($model->getErrors()); die(); } }
Implementing the Timestamp behavior will do this automatically for us and can be easily added to all of the ActiveRecord models in a web application.
Implementing the Timestamp Behavior in the Status Model
Almost every model I create in Yii has a created_at
and updated_at
field. It's good practice. Thus, the Timestamp behavior is useful in nearly every model.
Adding the Timestamp Behavior to the Status Model
In models/Status.php
we add the TimestampBehavior
after Sluggable
and Blameable
:
public function behaviors() { return [ [ 'class' => SluggableBehavior::className(), 'attribute' => 'message', 'immutable' => true, 'ensureUnique'=>true, ], [ 'class' => BlameableBehavior::className(), 'createdByAttribute' => 'created_by', 'updatedByAttribute' => 'updated_by', ], 'timestamp' => [ 'class' => 'yii\behaviors\TimestampBehavior', 'attributes' => [ ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], ], ], ]; }
We also have to include the ActiveRecord class at the top of our model (I always forget this part):
<?php namespace app\models; use Yii; use yii\db\ActiveRecord; use yii\behaviors\SluggableBehavior; use yii\behaviors\BlameableBehavior;
Then, we remove the required rule for created_at
and updated_at
in the model rules:
public function rules() { return [ [['message', 'created_at', 'updated_at'], 'required'], [['message'], 'string'], [['permissions', 'created_at', 'updated_at','created_by'], 'integer'] ]; }
Like this:
public function rules() { return [ [['message'], 'required'], [['message'], 'string'], [['permissions', 'created_at', 'updated_at','created_by'], 'integer'] ]; }
This allows the validation to succeed and continue on to the behaviors.
We also need to remove the StatusController's created_at
and updated_at
assignments in the create action:
public function actionCreate() { $model = new Status(); if ($model->load(Yii::$app->request->post())) { if ($model->save()) { return $this->redirect(['view', 'id' => $model->id]); } else { var_dump ($model->getErrors()); die(); } } return $this->render('create', [ 'model' => $model, ]); }
Once all these changes are complete, we can write a new Status post:
And, the resulting view shows the created_at
and updated_at
settings made by the Timestamp behavior.
The Touch Method
The Timestamp behavior also provides a method named touch()
that allows you to assign the current timestamp to the specified attribute(s) and save them to the database.
$model->touch('updated_at');
For example, if you have a background cron job that does some processing on the status table, you might have a last_processed_at
timestamp which you attach the behavior to. Whenever the cron job runs, you would touch that field:
$model->touch('last_processed_at');
The Timestamp Behavior Source Code
Since Yii2 now supports the PSR-4 naming conventions, it's easier to dive right into the framework code to see how it works. Let's take a look at the TimestampBehavior
code to understand how it's implemented.
The code is linked to GitHub from the documentation page:
class TimestampBehavior extends AttributeBehavior { /** * @var string the attribute that will receive timestamp value * Set this property to false if you do not want to record the creation time. */ public $createdAtAttribute = 'created_at'; /** * @var string the attribute that will receive timestamp value. * Set this property to false if you do not want to record the update time. */ public $updatedAtAttribute = 'updated_at'; /** * @var callable|Expression The expression that will be used for generating the timestamp. * This can be either an anonymous function that returns the timestamp value, * or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`). * If not set, it will use the value of `time()` to set the attributes. */ public $value; /** * @inheritdoc */ public function init() { parent::init(); if (empty($this->attributes)) { $this->attributes = [ BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdAtAttribute, $this->updatedAtAttribute], BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedAtAttribute, ]; } } /** * @inheritdoc */ protected function getValue($event) { if ($this->value instanceof Expression) { return $this->value; } else { return $this->value !== null ? call_user_func($this->value, $event) : time(); } } /** * Updates a timestamp attribute to the current timestamp. * * ```php * $model->touch('lastVisit'); * ``` * @param string $attribute the name of the attribute to update. */ public function touch($attribute) { $this->owner->updateAttributes(array_fill_keys((array) $attribute, $this->getValue(null))); } }
The default attributes are defined here, and they can be customized in our models:
public $createdAtAttribute = 'created_at'; public $updatedAtAttribute = 'updated_at';
On initialization, the behavior defines which events trigger timestamp updates to the specified attributes:
public function init() { parent::init(); if (empty($this->attributes)) { $this->attributes = [ BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdAtAttribute, $this->updatedAtAttribute], BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedAtAttribute, ]; } }
You can read more about Yii2 ActiveRecord Events here.
The getValue
method returns the current timestamp for the attribute if it's undefined:
protected function getValue($event) { if ($this->value instanceof Expression) { return $this->value; } else { return $this->value !== null ? call_user_func($this->value, $event) : time(); } }
By default, TimestampBehavior
will fill the created_at
and updated_at
attributes with the current timestamp when the associated object is being inserted. It will fill the updated_at
attribute with the timestamp when the object is being updated. If there's no custom function assigned, then it uses the PHP time()
function, which returns the current Unix timestamp.
It also implements the touch
method for the defined attributes:
/** * Updates a timestamp attribute to the current timestamp. * * ```php * $model->touch('lastVisit'); * ``` * @param string $attribute the name of the attribute to update. */ public function touch($attribute) { $this->owner->updateAttributes(array_fill_keys((array) $attribute, $this->getValue(null))); }
Hopefully, this gives you an idea of how to implement your own model behavior. If you do create something new, post a link to the code in the comments so others can check it out.
What's Next?
I hope you've enjoyed learning about Yii2 Timestamp behaviors and exploring the Yii2 source code.
Watch for upcoming tutorials in my Programming With Yii2 series as I continue diving into different aspects of the framework. You may also want to check out my Building Your Startup With PHP series, which is using Yii2's advanced template as I build a real world application.
I welcome feature and topic requests. You can post them in the comments below or email me at my Lookahead Consulting website.
If you'd like to know when the next Yii2 tutorial arrives, follow me @reifman on Twitter or check my instructor page. My instructor page will include all the articles from this series as soon as they are published.
Related Links
-
The Yii2 Definitive Guide: Behaviors
- Yii2 Documentation: Timestamp Behavior
- Yii2 Developer Exchange, my Yii2 resource site
Comments