LogableBehavior

By Alexander Morland (alkemann)
This behavior is created to be a plug-and-play database changes log that will work out of the box as using the created and modified fields does in cake core. It is NOT version control, undo or meant to be used as part of the public application. It's intent is to easily let you (the developer) log users activities that relates to database modifications (ie, add, edit and delete). If you just want to see what your users are doing or need to be able to say "That is not a bug, I can see from my log that you deleted the post yesterday." and don't want to spend more time that it takes to do "var $actsAs = array('Logable');" then this behavior is for you.

What

The intent of this behavior is to create a row in a log table every time a model's data (or all the model that the behavior is applied to) is created, edited or deleted. The developer can set this log table up to include as much detail as is required, and that is all the configuration that is needed.


How

Requirements

  • The behavior found on page 2
  • A Log model( empty but for a order variable [created DESC]
  • A "logs" table with these fields required :
    • id (int)
    • title (string) automagically filled with the display field of the model that was modified.
    • created (date/datetime) filled by cake in normal way
  • actsAs = array("Logable"); on models that should be logged

Optional configurations

Optional extra table fields for the "logs" table


  • description (string) Fill with a descriptive text of what, who and to which model/row
    • Example :Contact "John Smith"(34) added by User "Administrator"(1).

or if u want more detail, add any combination of the following


  • model (string) automagically filled with the class name of the model that generated the activity.
  • model_id (int) automagically filled with the primary key of the model that was modified.
  • action (string) automagically filled with what action is made (add/edit/delete)
  • user_id (int) populated with the supplied user info. (May be renamed. See bellow.)
  • change (string) depending on setting either
    • full: [name (alek) => (Alek), age (28) => (29)] or list: [name, age]
  • version_id (int) cooperates with VersionBehavior to link the the shadow table (thus linking to old data)

NB! VersionBehavior cooperation not implemented this version.


Optionally register what user was responisble for the activity

Supply configuration only if defaults are wrong. Example given with defaults:

Model Class:

Download code <?php 
class Apple extends AppModel {
    var 
$name 'Apple';
    var 
$actsAs = array('Logable' => array(
        
'userModel' => 'User'
        
'userKey' => 'user_id'
        
'change' => 'list'// options are 'list' or 'full'
        
'description_ids' => TRUE // options are TRUE or FALSE
    
));
 [..]
?>

The change fields modifies what will be automagic filled in the change field if the log table has it. The description_ids option sets whether the description field will include the ids of the mode and the user. (See example above)


Usage


If you are using the user feature of the behavior, the models needs to know the id of the active user. This is most easily set in app controller in this way, but note that you may use the Logable::setUserData() method manually should you so desire.


Controller Class:

Download code <?php 
// In AppController (or single controller if only needed once) add these lines to beforeFilter : 

if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) {
    
$this->{$this->modelClass}->setUserData($this->activeUser);
}
?>

Where "$activeUser" should be an array in the standard format for the User model used :[/]
Download code
<? $activeUser = array( $UserModel->alias => array( $UserModel->primaryKey => 123, $UserModel->displayField => 'Alexander')); ?>

any other key is just ignored by this behaviour.


Get the logs out


I don't supply a helper or views for this, I am sure you can manage, but I suggest you make one view and use the controllers viewpath or the controllers render method to only render a single view for all models that use the behavior.


To extract the logs, no matter of you want all events, or just for one model or one user, or even one user's activity on one model, you can ask any model that the behavior is enabled on. There are two methods


  • findLog($params)
  • findUserActions($user_id, $params)

You can off course query the Log model in the normal way.


findLog

This is the main function for retrieving the logged activities. It will by default (when called with no parameters) return all activities for the model it is called from, but it can also be used for any or all models from any model. The available options are listed bellow.


  • model (string)
  • action (string) (add/edit/delte) defaults to NULL (ie. all)
  • fields (array)
  • order (string) defaults to 'created DESC'
  • conditions (array) add custom conditions
  • model_id (int) ForeignKey for a single instance of logged model
  • user_id (int) defaults to NULL (all users).

Remember to user your own foreignKey if you did not use 'user_id'


Download code <?php // examples
 // All acitivities on current model
 
$data $this->Apple->findLog();
 
// All acitivities on current model instance
 
$data $this->Apple->findLog('model_id'=>32);
 
// I am in apple controller, but i want acitivities for the user on a specific Logo isntance
 
$data $this->Apple->findLog(array('user_id'=>66,'model'=>'Logo','model_id'=>123));
?>

findUserActions

The first parameter is compulsory and is the ID of the user (foreignKey). The second is an array of options. The available options are listed bellow. Model and fields does the expected things, while events will create a description on the fly. This function is intended to be improved in the next version to be translatable / customizable.

  • model (string)
  • events (boolean)
  • fields (array)

Download code <?php // examples
    // note we are asking for a different model
 
$data $this->User->findUserActions(301,array('model' => 'BookTest')); 
 
$data $this->Apple->findUserActions(301,array('events' => true));
 
$data $this->Model->findUserActions(301,array('fields' => array('id','model'),'model' => 'BookTest');
?>

The code (or download link) can be found on the next page.

Page 2: The Code

Comments 793

CakePHP Team Comments Author Comments
 

Comment

1 Great work!

it works! I was going to built sth exactly like this, but not so fancy!.

Are you still working on linking it to the Revision Behaviour? it would be really great.

I´ll have a look and see what i can do with it.

Thank you!

Posted Dec 29, 2008 by Ignacio
 

Comment

2 Version 2

Logable has hit version 2, there has been some changes to the api, so i'll leave this code un-updated for now, but you can grab it from my google code at http://code.google.com/p/alkemann/downloads

Where you can also download it's big brother, RevisionBehavior (that also just hit version 2) and it's cousin, MultilingualBehavior.
Posted Jan 15, 2009 by Alexander Morland
 

Comment

3 Great!

Great behaviour; however, running into an oddity ... getting a seg fault when I tried just having `$actsAs = array('Logable');` on the global AppModel. Still trying to track it down.
Just curious why a new User is being instantiated on line 98 if we're passing it info in setUserData??
Posted Jan 27, 2009 by Brenton
 

Comment

4 Me too!

Great behaviour; however, running into an oddity ... getting a seg fault when I tried just having `$actsAs = array('Logable');` on the global AppModel. Still trying to track it down.
Same here. Anyone got a fix for this?
Posted Apr 28, 2009 by Chris
 

Comment

5 User data

Great work!

I had some troubles figuring out what I should pass to setUserData in beforeFilter. These lines seems to do the job:
    if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) {
      $this->{$this->modelClass}->setUserData($this->Session->read('Auth'));
    }

Regards

Jonathan
Posted May 9, 2009 by Jonathan Stein
 

Comment

6 Alternative way to pass User Data?

I am having a bit of trouble retrieving User Data from beforeFilter... I use the DarkAuth component to manage the Authentication as seen here :(http://bakery.cakephp.org/articles/view/darkauth-another-way). And I usually retrieve user data like so: $this->DarkAuth->current_user['User']. which is unavailable in the before filter function. Is there another way to pass UserData to the behavior?

Fixed:

$this->DarkAuth->getUserInfo()
Posted Jun 11, 2009 by Francis Gagne