How to log changes of ActiveRecords?

You are viewing revision #3 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version.

next (#4) »

If you want to know what the users are doing within your application, you need to log the activities releated to database modifications. Normally you would like to know whenever a record was inserted, changed or deleted and also when and by which user this was done. For a [CActiveRecord] Model you could use a behavior for this purpose. So you will be able to add log functionality to ActiveRecords very easily.

First of all you have to create a table for the loglines in the database. Here is an example (MySQL):

CREATE TABLE ActiveRecordLog (
  idActiveRecordLog INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  description VARCHAR(255) NULL,
  action VARCHAR(20) NULL,
  model VARCHAR(45) NULL,
  idModel INTEGER UNSIGNED NULL,
  field VARCHAR(45) NULL,
  creationdate TIMESTAMP NOT NULL,
  userid VARCHAR(45) NULL,
  PRIMARY KEY(idActiveRecordLog)
)
TYPE=InnoDB;

The next step would be to create the corresponding Model using the YII Shell Tool:

model ActiveRecordLog

If you would like to have basic crud functionality created, you should also type:

crud ActiveRecordLog

To be able to log the changes we will use a behavior class. So you have to create one (i.e. ActiveRecordLogableBehavior) and store it somewhere in your application directory (i.e. \protected\behaviors). The behavior class should extend [CActiveRecordBehavior] as we want to work with ActiveRecords here.

class ActiveRecordLogableBehavior extends CActiveRecordBehavior
{
private $_oldattributes = array();
public function afterSave($event)
{

	if (!$this->Owner->isNewRecord) {

		// new attributes
		$newattributes = $this->Owner->getAttributes();
		$oldattributes = $this->getOldAttributes();

		// compare old and new
        		foreach ($newattributes as $name => $value) {
			if (!empty($oldattributes)) {
				$old = $oldattributes[$name];
			} else {
				$old = '';
			}
			if ($value != $old) {
				//$changes = $name . ' ('.$old.') => ('.$value.'), ';

				$log=new ActiveRecordLog;
				$log->description=	'User ' . Yii::app()->user->Name . ' changed ' . $name . ' for ' . get_class($this->Owner) . '[' . $this->Owner->getPrimaryKey() .'].';
				$log->action=		'CHANGE';
				$log->model=		get_class($this->Owner);
				$log->idModel=		$this->Owner->getPrimaryKey();
				$log->field=		$name;
				$log->creationdate=	date("Y-m-d H:i:s", time());
				$log->userid=		Yii::app()->user->Name;
				$log->save();
			}
        		}
	} else {
		$log=new ActiveRecordLog;
		$log->description=	'User ' . Yii::app()->user->Name . ' created ' . get_class($this->Owner) . '[' . $this->Owner->getPrimaryKey() .'].';
		$log->action=		'CREATE';
		$log->model=		get_class($this->Owner);
		$log->idModel=		$this->Owner->getPrimaryKey();
		$log->field=		'';
		$log->creationdate=	date("Y-m-d H:i:s", time());
		$log->userid=		Yii::app()->user->Name;
		$log->save();
	}
}
public function afterDelete($event)
{
	$log=new ActiveRecordLog;
	$log->description=	'User ' . Yii::app()->user->Name . ' deleted ' . get_class($this->Owner) . '[' . $this->Owner->getPrimaryKey() .'].';
	$log->action=		'DELETE';
	$log->model=		get_class($this->Owner);
	$log->idModel=		$this->Owner->getPrimaryKey();
	$log->field=		'';
	$log->creationdate=	date("Y-m-d H:i:s", time());
	$log->userid=		Yii::app()->user->Name;
	$log->save();
}
public function afterFind($event)
{
	// Save old values
	$this->setOldAttributes($this->Owner->getAttributes());
}

public function getOldAttributes()
{
	return $this->_oldattributes;
}

public function setOldAttributes($value)
{
	$this->_oldattributes=$value;
}

}

The behavior class uses the ActiveRecordLog Model to store the log lines into the database. It will log a line each time a record is inserted or deleted. It will also log a line for each field which is changed.

In order to make an ActiveRecord Model use this behavior, you have to add the following code to the Model class:

public function behaviors()
{
    return array(
    	// Classname => path to Class
	    'ActiveRecordLogableBehavior'=>
            'application.behaviors.ActiveRecordLogableBehavior',
    );
} 

Of course this simple example could be enhanced:

  • support for mult-column primary keys
  • savethe attributeLabels instead of the field names
  • make description customizable
  • and so on...
31 1
47 followers
Viewed: 72 564 times
Version: Unknown (update)
Category: Tutorials
Tags: Logging
Written by: pfth
Last updated by: Yang He
Created on: Feb 13, 2009
Last updated: 11 years ago
Update Article

Revisions

View all history

Related Articles