Monday, April 29, 2013

Yii Active Record Events

I am skipping ahead a bit, but I just worked out an important detail that I want to capture. CActiveRecord pre-defines some events that you can use to save yourself some time and effort. The documentation tells that they are there, but doesn't really tell how to use them. These events are:

onBeforeValidation - in CModel - executes just before the validation rules are processed - can be used to stop save
onAfterValidation - in CModel - executes just after the validation rules are processed - can be used to stop save, but it is harder.
onBeforeSave - executes after validation but before the record is saved - can be used to stop save
onAfterSave - executes after the record is saved
onBeforeDelete - executes before the record is deleted, can be used to stop deletion
onAfterDelete - executes after the record is deleted
onBeforeFind - executes before the find command is executed
onAfterFind - executes after each found record is instantiated
onAfterConstruct - executes after a new model is constructed

I'm not going to go through all  these to tell you what they are useful for. I will leave that to you. But I will show you how to enable one of them (the rest will work the same way), and give you a bit of code that I am using it for.

I was asked to put some audit fields on each of my files to track create date and time, create user, last changed date and time, and last change user. Ordinarily it would take the same bit of code in each of my models to populate these audit fields. That code isn't all that complex. In fact here it is:

   if ($this->isNewRecord) {
      $this->create_user = Yii::app()->user->name;
      $this->create_time = new CDbExpression('current_timestamp');
   } else {
      $this->change_user = Yii::app()->user->name;
      $this->change_time = new CDbExpression('current_timestamp');
   }

This is not a large piece of code, but if things change, and they decide that they want the name of the function that called it or something else, I have to go back and change a lot of code. Instead, I can wrap this in an event handler and call it when the BeforeSave event is raised. So, I created a class in Components named Audit which has a single static method:

class Audit
{
   public static function beforeSaveHandler($event)
   {
      $model = $event->sender;
      if ($model->isNewRecord) {
         $model->create_user = Yii::app()->user->name;
         $model->create_time = new CDbExpression('current_timestamp');
      } else {
         $model->change_user = Yii::app()->user->name;
         $model->change_time = new CDbExpression('current_timestamp');
      }
   }
}

I still have to attach the handler to each model, but that is easy. It is attached in the init() method of the model. If you don't have one, just create it. It will override the init() method from CActiveRecord.

   public function init()
   { 
      parent::init();
      $this->onBeforeSave = array(new Audit, 'beforeSaveHandler');
   }

You do not really have to call the CActiveRecord::init() function because it does nothing, but I do it anyway just in case the developers of Yii put something there in the future.

So there you have it, a simple beforeSave event handler that populates audit fields in a table. You can add as many event handlers as you want to that init() function. I'm not sure what order they will be executed in, I suspect in the order that they are added. But, I wouldn't count on that as it could change. I also wouldn't write dependencies into the handlers as this will likely add unnecessary complexity to your application.

Let me know what you think.

Yii - Not a bad PHP Framework

I've been working on a little PHP project lately, and to make things a bit easier on myself I decided to use an MVC framework.  In the past I have worked a little with Zend Framework, and it is certainly a full featured framework, backed by Zend, the PHP company.  But in my experience it has a very steep learning curve.  In addition the many parts have great documentation if used in isolation, but the documentation of how the parts work together within an MVC framework is a bit lacking, and in addition I never did figure out how to get dates from the database to a form and back without jumping through a whole lot of hoops.  I am sure that there is an easy way to do it, but I spent hours searching the internet, and found nothing.

I have also looked at Code Igniter.  As much as Zend Framework is an industrial strength framework, Code Igniter is on the opposite side of the MVC framework ledger. About as light as a framework can be yet still be called a framework.  I want a framework to provide at least some assistance.  I don't want to have to completely define my own way to do things.

So I heard about Yii and decided to try it.  Yii occupies the space somewhere between Code Igniter and Zend Framework.  It is fairly responsive, yet contains database classes, form classes and several helpers, validators and filters built in.  It also features a slick code generator called Gii which you can configure with your own templates.  So far in my project I have had several things I wanted to do that were a bit unclear, or uncovered in the documentation.  However, I found that the community around Yii is extremely helpful, and most of my questions already had answers either in the Yii forums or on StackOverflow.

Over the next couple of weeks I will post more of my findings about Yii here on my blog.