Much of the work done with models involves using the methods defined within the model classes. These methods, such as rules()
and relations()
, are created by the code generator Gii. You’ll also add your own methods to the code generated for you. But, thanks to inheritance, there are lots of methods common to Yii models that you’ll frequently use. In this post, I want to specifically look at:
afterConstruct()
afterDelete()
afterFind()
afterSave()
afterValidate()
beforeDelete()
beforeFind()
beforeSave()
beforeValidate()
These methods are used to handle model-related events. Before looking at the usage of these methods, let’s first look at event handling in Yii in general.
The CComponent Class
Something I thought about discussing in Chapter 3, “A Manual for Your Yii Site,” but later changed my mind about, is the concept of components. Discussion of components can get a bit complex (which is why I removed it from Chapter 3), but components are an important subject, and it’s time they were introduced to you.
Unlike the application components configured in Chapter 4 (such as the database component, the “urlManager” component, and so forth), I’m talking about generic components here. Components are the key building block in the Yii framework. It all starts with Yii’s CComponent
class. Most of the classes used in Yii are descendants of the base CComponent
class. For example, the application object will be of type CWebApplication
. That class is derived from CComponent
(although there are other classes in between). Controllers are of type CController
, which inherits from CBaseController
, which inherits from CComponent
. CActiveRecord
inherits from CModel
, which inherits from CComponent
, and the same inheritance path apply to CFormModel.
Knowing that the component is the basic building block is important to your use of Yii. Because of the nature of inheritance in OOP, functionality defined in CComponent
will be present in every derived class, which is to say most of the classes in the framework.
The CComponent
class provides three main tools:
- The ability to get and set attributes
- Event handling
- Behaviors
Of these three, I want to discuss events now. This coverage will be specific to models, but understand that any class that inherits from CComponent
supports events (which is to say most classes).
Event Handling in Yii
Event programming isn’t necessarily familiar territory to PHP developers, as PHP does not have true events the way, say, JavaScript does. In PHP, the only real event is handling the request of a PHP script (through a direct link or a form submission). The result of that event occurrence is that the PHP code in that script is executed. Conversely, in JavaScript, which continues to run so long as the browser window is open, you can have your code watch for, and respond to, all sorts of events (e.g., a form’s submission, the movement of the cursor, and so forth). Thanks to the CComponent
class, Yii adds additional event functionality to PHP-based Web site.
Event handling in any language starts by declaring “when this event happens with this thing, call this function”. In Yii, you can create your own events, but models have their own predefined events you can watch for: before a model is saved, after a model is saved, before a model is validated, after a model is validated, and so forth (the available events will depend upon the model type). Yes: each of the methods previously mentioned correspond to an event that Yii will watch for with your models.
In many situations, you’ll want to make use of events when something that happens with an instance of model A should also cause a reaction in model B. You’ll see examples of this in time. But watching for events can be a good way to take some extra steps within a single model, too.
For example, you might want to do something special before a model instance is saved. To do so, just create a beforeSave()
method within the model:
# protected/models/SomeModel.php protected function beforeSave() { // Do whatever. return parent::beforeSave(); }
As you can see in that minimal example, it’s a best practice to call the parent class’s same event handler (here, beforeSave()
) just before the end of the method. Doing so allows the parent class’s event handler to also take any actions it needs to, just in case. (If you don’t do this, then any default behavior in the parent class method won’t be executed.)
As a real-world example of using an event with a model, the page.user_id
value needs to be set to the current user’s ID when a new page record is created. One way to do that is to create a beforeValidate()
event handler that sets the attribute’s value:
# protected/models/Page.php protected function beforeValidate() { if(empty($this->user_id)) { // Set to current user: $this->user_id = Yii::app()->user->id; } return parent::beforeValidate(); }
This does assume that the current user’s ID is available through user->id
, but other than that, it will work fine. And because this method checks for an empty user_id
attribute first, the event handler will not have an impact on the attribute’s value when a page is being updated.
The same concept can be applied to the user_id
and page_id
attributes in Comment
and the user_id
attribute in File
. See the downloadable code for examples of all of these.
As another example, earlier in the chapter you saw how to set the two date/time column values via scenarios:
# protected/models/AnyModel.php::rules() array('date_entered', 'default', 'value'=>new CDbExpression('NOW()'), 'on'=>'insert'), array('date_updated', 'default', 'value'=>new CDbExpression('NOW()'), 'on'=>'update'),
An alternative solution would be to use the beforeSave()
method and set the values within it. To test whether this is an insertion of a new record or an update of an existing one, the code can check the isNewRecord
property of the model:
# protected/models/AnyModel.php public function beforeSave() { if ($this->isNewRecord) { $this->created = new CDbExpression('NOW()'); } else { $this->modified = new CDbExpression('NOW()'); } return parent::beforeSave(); }
Which approach you take for setting values–validation scenarios or events–is largely a matter of preference, as both will can the trick. The argument for using event handling is that you are moving more of the logic out of the rules and into new methods, which can make for cleaner code.
As a final note on this concept, if the event that’s about to take place shouldn’t occur–for example, the model should not be saved for some reason, just return false in the event handler method.