Please use the Github project home for this extension: HERE since the download here is out of date. Github is the officially supported platform for me to dish out updates. ¶
This is basically a modification of a previous extension made by MadSkillsTisdale at http://www.yiiframework.com/extension/audittrail.
I have basically cleaned up some of the code and made a few additions to the behaviour bundled within this extension.
Installing the extension ¶
The method of installation has changed. I have removed the need to install a module since:
- It only provided global configuration variables for the audit log widget
- It was extra bloat that didn't justify the needs
- I found that in a real system you wouldn't want a page showing all audit log entries since the audit logs
- The audit log is quite easy to add to a page using
CGridView
As such, for these reasons, the module itself has been deleted.
Composer ¶
This extension is listed on packagist.
Step 1 ¶
To install you must first choose a folder in which to place this repository. I have chosen:
/root/backend/extensions/modules
Since this seems most right to me. Clone this repository to that location.
Step 2 ¶
Time to install the table. You can use the migration file provided by the original author of this extension or you can use the SQL file bundled within the migrations folder. Simply run it on your DB server (using PHPMyAdmin or something) and watch the magic unfold.
Step 3 ¶
Reference the AuditTrail
model within your configuration:
'import'=>array(
'site.backend.extensions.modules.auditTrail.models.AuditTrail',
),
Note You can move AuditTrail
to your models
folder preventing you from having to link it like this.
Step 4 ¶
Simply use the behaviour within a model like:
'LoggableBehavior'=> array(
'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
)
Epilogue ¶
If your user class is not User
then you may (depending on your setup) need to change the relation within the AuditLog
model to suite your needs.
API ¶
Please note that the below snippets are snippets only and are not tested in a real environment. It is recommend that you use this section as reference and documentation only, do not copy and paste from here.
Custom User Attributes ¶
Some people don't actually have defined users but do have an attribute of the auditable model that would define a unique identification of who edited it. For this end you can use:
'LoggableBehavior'=> array(
'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
'userAttribute' => 'name'
)
Storing Timestamps ¶
The date of the audit log can be changed to used timestamps instead using:
'LoggableBehavior'=> array(
'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
'storeTimestamp' => true
)
Changing the date format ¶
You can adjust the date format using the dateFormat
property of the behaviour:
'LoggableBehavior'=> array(
'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
'dateFormat' => 'Y-m-d H:i:s'
)
Ignoring and allowing specific fields ¶
There is one interesting addition to this version. You can now specify an allowed
set of fields and a ignored
set of fields...or both.
To do this include the behaviour in your models like you normally would:
'LoggableBehavior'=> 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior'
But then add either an ignored
or allowed
(or both) list of fields to the behaviour like so:
'LoggableBehavior'=> array(
'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
'allowed' => array( 'version', 'ns_purchase_description' ), 'ignored' => array( 'ns_purchase_description', 'ns_display_name', 'update_time' )
)
The names put into the allowed
and ignored
parameters of the behaviour represent field names.
As you will notice I allow the ns_purchase_description
field but also ignore it. When you use the fields in this way ignored
will replace the allowed
and this field will be omitted.
Ignoring a whole class ¶
This is useful if you put the behaviour in a class that extends CActiveRecord which all your own models extend from. This is useful in cases where you want ALL your classes to share the same core (like this behaviour) without having to specify it in every model you create.
'LoggableBehavior'=> array(
'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
'ignored_class' => array(
'ErrorLog', // I use this to log error messages to MYSQL, no need to keep a log of this
),
)
Printing out the audit log ¶
Since this no longer uses a module to do its work there is no global configuration for the previously inbuilt audit log to work from. Instead you can insert an audit log like (as an example only, showing an audit of changes to a book title and it's products on a book title page):
$model_ids = array(array($model->id, 'Title'));
foreach($model->products as $id => $product){
$model_ids[] = array($product->id, 'Product');
}
$criteria=new CDbCriteria(array(
'order'=>'stamp DESC',
'with'=>array('user'),
));
$param_id = 0;
foreach( $model_ids as $id_pair ) {
$criteria->addCondition( '( model_id = :id' . $param_id . ' AND model = :model' . $param_id . ' )', 'OR' );
$criteria->params[ ':id' . $param_id ] = $id_pair[0];
$criteria->params[ ':model' . $param_id ] = $id_pair[1];
$param_id++;
}
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'title-grid',
'dataProvider'=>new CActiveDataProvider('AuditTrail', array(
'criteria'=>$criteria,
'pagination'=>array(
'pageSize'=>100,
)
)),
'columns'=>array(
array(
'name' => 'Author',
'value' => '$data->user ? $data->user->email : ""'
),
'model',
'model_id',
'action',
array(
'name' => 'field',
'value' => '$data->getParent()->getAttributeLabel($data->field)'
),
'old_value',
'new_value',
array(
'name' => 'Date Changed',
'value' => 'date("d-m-Y H:i:s", strtotime($data->stamp))'
)
),
));
model reference
I did notice, that you just store primary key of model in your audit trail (looking at $criteria in last snippet). It is rather wrong, because you cannoc assign this behavior to more than one model, or its keys will collide. To be more clear:
Lets say you have two models: User, Company. Both have attached your behavior. If I change User with id=1 AuditTrail record is created with model_id=1. When I change Company with id=1 - same thing (audittrail is created with model_id=1), but both have same foreign key reference and you cannot say whoch is for User trail and witch for Company...
I did write very similiar behavior for my project, but I store both class name of model and it's primary key. In fact there are also cases when primary key is an array (multiple column primary key) and in such case your behavior will fail as well...
RE: model reference
Indeed normally you cannot differentiate the model however this too stores the model name.
So it stores:
model_id: 1 model: Company
model_id: 1 model: User
If you check the AuditTrail model and the function getParent() within that you will see this is how I get the parent model (The line for it here: https://github.com/Sammaye/audittrail/blob/master/behaviors/LoggableBehavior.php#L98).
The field name is a little confusing and could probably do with changing. I originally made this to work on the same schema as the previous extension.
The last snippet was just an example of getting all audit logs by a specific primary key. Probably a bad one since I too realised this key confliction and have since changed the code I show here to go on the "model" field as well. I might also allow extra descriptive fields as a future addition since I realised to get a group of audit logs for a specific context you might need to know more about the subobjects of a parent. However I am unsure how this will be done atm.
When the Primary is an array it should JSON encode the primary key (https://github.com/Sammaye/audittrail/blob/master/behaviors/LoggableBehavior.php#L122), I am unsure as to whether that is the right thing to do since I personally have never had an array based primary, in Yii yet.
Hope it helps,
re: RE: model reference
aa, ok. I did not check source codes, just slightly misunderstood 'printing out' snippet, because you are preparing there criteria with just model id's and without model names...
Further RE:
Yea it is quite bad example but the alternative is a really complex PDO statement with lots of
OR
s which is bad. I may proposed something like:/** * This function is not in use yet, the field is not applied to the general schema so don't use it * @example serializedSearch(array(array(1, 'User'), array(1, 'Company'), array('{"d": 1, "e": 2}', 'Other'))) */ function serializedSearch($serializedIds = array()){ $crtieria = new CDbCriteria(array( 'order'=>'stamp DESC', 'with'=>array('user'), )); $in = array(); foreach($serializedIds as $k => $val){ if(isset($val[0]) && isset($val[1])) $in[] = json_encode(array($val[0], $v[1])); } $criteria->addInCondition('serialized_id', $in); return new CActiveDataProvider('AuditTrail', array( 'criteria'=>$criteria, 'pagination'=> array( 'pageSize'=>100, ) ) ); }
Anyway let's see where it goes :).
some bugs
There are some issues with audittrails2, for example is unable to record create and delete trails because model_id and field attributes in model AuditTrail are required so save fails every time.
Another bug is found in LoggableBehavior in line 101, when using variable $time() instead of function time(), by default it would not give error because the default is to not save timestamp.
thanks for your extension, very usefull to me!
RE: some bugs
Strange I use delete and what not and it works fine for me. It should place "Create" (or "Delete") and the field attribute, as for the model_id: that is indeed a required attribute. Is there a reason you are using this without activeRecord since all active record models should have an id (primary key).
I shall look into that time thing. I must admit I had not tested that functionality, however
$time()
is not a valid variable. Can you give me an example code which causes the bugable behaviour?Edit: I see what you meant now by $time(), lol. I will change that asap.
RE: some bugs
Fixed time bug.
Would be awesome if you can give an example of the Delete thing you said about :)
create delete trail
you have this in your rules for model AuditTrail
array('action, model, field, stamp, model_id', 'required'),
but when you create a trail for create (there another issue here I think) or delete only pass variable $action and save fails.
another thing I changed is that you use afterSave to save the trail and you use isNewRecord, I'm not sure right now cause I can't check it right away but that will always be false, because the model is already saved so you have to check that flag in beforeSave and ask latter in the afterSave.
Hope it helps sorry if I'm wrong :D
RE: create delete trail
I will look into that
getIsNewRecord
one though it does work on my system.Ah I see, it is running the validation rules. Strange I don't see that behaviour in my system but you are right that is a bug. I have fixed it now :).
Old Value
I cant seem to get Old value to be recorded, its seems this is tied to
$this->Owner->getAttributes()
Any pointers, thanks again, the allowed and ignored functionality is really awesome
RE: Old Value
Strange indeed.
Can you post a code snippet that can replicate this behaviour?
Maybe it is how it is being used in the model as a behaviour in the parent model.
Also can you just tell me if
getAttributes()
actually returns anything in this case?It might not be recording old values because those values are either '' or
null
, these values I omit to save database space; that option can be turned off however.Old Value
@Sammaye It seems to get the old value on some models but on others nothing is stored, let me investigate, here are dropbox link to the two different models,does not work on this one
Example One
Works on this one Example Two . I have also tried playing with
public $skipNulls = true;
nothing much, I am using the github version
RE: Old Value
Yeah, that is very weird.
I would try and see what getAttributes returns and if it returns fields that are not empty I would iterate through
auditAttributes
to see why it ditches them.I can't see anything obvious from the classes.
RE RE: Old Value
Can you open a forum topic for this extension, or if one exist post the link?
RE: Old Value
I have opened a new toic here: http://www.yiiframework.com/forum/index.php/topic/40416-extension-audittrail-2/
You can also post an issue on github: https://github.com/Sammaye/audittrail/issues?state=open
RE: Old Value
How do you declare your primaryKey? I should be bringing it back as a json string
Added the ability to ignore a whole class, including added comments to the readme
I submitted a pull request to github, hopefully you update the article above :)
Inserting behaviour and creating indexes issue
public function behaviors()
{ return array( // Classname => path to Class 'LoggableBehavior'=> array( 'class' => 'ext.audittrail.behaviors.LoggableBehavior', ) ); }
Re: Inserting behaviour and creating indexes issue
Thanks for the comment.
About point 2: what indexes exactly? There are about 7 normally.
2 indices
1.$this->createIndex( 'idx_audit_trail_old_value', 'tbl_audit_trail', 'old_value');
2.$this->createIndex( 'idx_audit_trail_new_value', 'tbl_audit_trail', 'new_value');
Re: 2 indices
Weird that should work, definitely a bug if it does not. What was the exact error you got?
Error
create index idx_audit_trail_old_value on tbl_audit_trail (old_value) ...exception 'CDbException' with message 'CDbCommand failed to execute the SQL statement: SQLSTATE[42000]: Syntax error or access violation: 1170 BLOB/TEXT column 'old_value' used in key specification without a key length. The SQL statement executed was: CREATE INDEX
idx_audit_trail_old_value
ONtbl_audit_trail
(old_value
)' in /usr/share/php/yii-1.1.12.b600af/framework/db/CDbCommand.php:354Stack trace:
Re: Error
Fixed thanks
Saving with mysql transaction
Hi,
I am using this extension and it is pretty good, but we encountered an issue due to our transactional data saving.
Scenario:
In a single save, I need to save the values to 2 different fields. It was successful on the first table but encountered an error on the second table. Now the problem is, the audit trail recorded the first action. But since mysql has rollback function, the record in audit trail will be different from the actual record.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.