thc1967's avatar

Building A History of Data Changes

I'd like to build a history feature into my application such that when a user updates one of my objects / records, I track that as an event that also stores the original values on the object and the updated values on the object.

I'd like to be able to come back around to look at that using the objects themselves, so I think that means I would need to store the data in a way I can read it back.

I'd like to do this without too much variance from the way Laravel and Eloquent manage the Model - Table relationship, so I think that precludes doing something like storing multiple copies of the record in the model's table.

I thought about creating a history table of sorts and storing the pre-edit and post-edit versions of the Eloquent model object as JSON (or something else?) in two different fields, creating a new record each time there was an update. I got about that far and thought it would be cool to do that but I couldn't figure out how to reconstitute an Eloquent Model object from JSON sitting in a record in a table.

What techniques have you guys used to do this with Laravel?

Thanks!

0 likes
6 replies
jlrdw's avatar

Use a second database, laravel can have multiple database connections. I do accounting that way, I have a backup table for older ledger entries. That json thing nah.

2 likes
michaeldrennen's avatar

I do something similar to @jlrdw

In my audit/accounting tables, I keep it pretty atomic.

Each row has:

  • The type of model name
  • primary key to find that record.
  • field that was updated
  • old value
  • new value

If you ever need to roll back, or produce an audit report on changes to a model, you have the data you need.

Whether or not you keep that audit table in the same database as your models is up to you.

1 like
Cronix's avatar

This is easily accomplished using a database trigger. Laravel doesn't have it built in, but you can easily create a trigger using raw sql, and then have a model to work with the "history" table. Triggers work at the database level and don't involve any php at all. You basically tell it "when this table is updated, copy it's data to this other table". It's really fast since it's internal and happens automatically behind the scenes at the database level.

You can have triggers for INSERT, as well as UPDATE functions. You can have conditionals, etc, like "if x field changes on an update, then copy to history table"

https://dev.mysql.com/doc/refman/5.7/en/trigger-syntax.html

I use this for tracking changes in real estate data for properties, so anytime a field on a property gets updated, it creates an entry in the history table so I can track all changes to properties over time. You just need to create a regular model to retrieve the data from the table and have a one-to-many relationship to the history table (one property can have many histories). The "listings" table holds the current listing data, and the "listing_history" table contains all previously changed data.

3 likes
jlrdw's avatar

@thc1967 I have a simple pet database I use just for test data, here is what a simple trigger looks like

DELIMITER $$

DROP TRIGGER /*!50032 IF EXISTS */ `pbackdate`.`au_petname`$$

CREATE
    /*!50017 DEFINER = 'root'@'localhost' */
    TRIGGER `pbackdate`.`au_petname` AFTER UPDATE ON `pbackdate`.`pets` 
    FOR EACH ROW BEGIN
INSERT INTO `nameback` SET `petnamebak` = OLD.petname;
END;
$$

DELIMITER ;

Just quick example, change a name, and old name is backed up. But remember, that backed up data grows fast with triggers, every edit. Have to figure out a purge technique.

1 like
thc1967's avatar

I would prefer to manage the record as a whole instead of creating the equivalent of a row per field. If I have new and old together, I can show them side-by-side and do cool things like filter to only populated values or only changed values.

I don't think I'll be high enough volume that I need to worry about purging or storage. The system that I'm replacing has been in place for over 15 years, it tracks history as 1 record per 1 field change, and it has under 500,000 records in its history table.

So let's change the question...

If I have a JSON representation of an Eloquent model inside a field in a table, how would I reconstitute that into the right Eloquent model object in PHP / Laravel? I feel like I should be looking into how polymorphic relations work...

1 like
topvillas's avatar
Level 46

If your JSON is a straight mapping of the data model or contains a straight mapping. You could just instantiate a new model with the attributes.

$attributes = json_decode($json);

$model = new Model($attributes);

You could even write a helper to map over a collection of your stored objects and return a collection of real models.

3 likes

Please or to participate in this conversation.