Easily create a revision history for any laravel model

Related tags

Laravel revisionable
Overview

Revisionable for Laravel

Latest Version Downloads License

Wouldn't it be nice to have a revision history for any model in your project, without having to do any work for it. By simply adding the RevisionableTrait Trait to your model, you can instantly have just that, and be able to display a history similar to this:

  • Chris changed title from 'Something' to 'Something else'
  • Chris changed category from 'News' to 'Breaking news'
  • Matt changed category from 'Breaking news' to 'News'

So not only can you see a history of what happened, but who did what, so there's accountability.

Revisionable is a laravel package that allows you to keep a revision history for your models without thinking. For some background and info, see this article

Working with 3rd party Auth / Eloquent extensions

Revisionable has support for Auth powered by

(Recommended) Revisionable can also now be used as a Trait, so your models can continue to extend Eloquent, or any other class that extends Eloquent (like Ardent).

Installation

Revisionable is installable via composer, the details are on packagist, here.

Add the following to the require section of your projects composer.json file:

"venturecraft/revisionable": "1.*",

Run composer update to download the package

php composer.phar update

Open config/app.php and register the required service provider (Laravel 5.x)

'providers' => [
	Venturecraft\Revisionable\RevisionableServiceProvider::class,
]

Publish the configuration and migrations (Laravel 5.x)

php artisan vendor:publish --provider="Venturecraft\Revisionable\RevisionableServiceProvider"

Finally, you'll also need to run migration on the package (Laravel 5.x)

php artisan migrate

For Laravel 4.x users:

php artisan migrate --package=venturecraft/revisionable

If you're going to be migrating up and down completely a lot (using migrate:refresh), one thing you can do instead is to copy the migration file from the package to your app/database folder, and change the classname from CreateRevisionsTable to something like CreateRevisionTable (without the 's', otherwise you'll get an error saying there's a duplicate class)

cp vendor/venturecraft/revisionable/src/migrations/2013_04_09_062329_create_revisions_table.php database/migrations/

Docs

Implementation

The new, Trait based implementation (recommended)

Traits require PHP >= 5.4

For any model that you want to keep a revision history for, include the VentureCraft\Revisionable namespace and use the RevisionableTrait in your model, e.g.,

namespace App;

use \Venturecraft\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    use RevisionableTrait;
}

Being a trait, Revisionable can now be used with the standard Eloquent model, or any class that extends Eloquent, such as Ardent.

Legacy class based implementation

The new trait based approach is backwards compatible with existing installations of Revisionable. You can still use the below installation instructions, which essentially is extending a wrapper for the trait.

For any model that you want to keep a revision history for, include the VentureCraft\Revisionable namespace and use the RevisionableTrait in your model, e.g.,

use Venturecraft\Revisionable\Revisionable;

namespace App;

class Article extends Revisionable { }

Note: This also works with namespaced models.

Implementation notes

If needed, you can disable the revisioning by setting $revisionEnabled to false in your Model. This can be handy if you want to temporarily disable revisioning, or if you want to create your own base Model that extends Revisionable, which all of your models extend, but you want to turn Revisionable off for certain models.

namespace App;

use \Venturecraft\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    protected $revisionEnabled = false;
}

You can also disable revisioning after X many revisions have been made by setting $historyLimit to the number of revisions you want to keep before stopping revisions.

namespace App;

use \Venturecraft\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    protected $revisionEnabled = true;
    protected $historyLimit = 500; //Stop tracking revisions after 500 changes have been made.
}

In order to maintain a limit on history, but instead of stopping tracking revisions if you want to remove old revisions, you can accommodate that feature by setting $revisionCleanup.

namespace App;

use \Venturecraft\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    protected $revisionEnabled = true;
    protected $revisionCleanup = true; //Remove old revisions (works only when used with $historyLimit)
    protected $historyLimit = 500; //Maintain a maximum of 500 changes at any point of time, while cleaning up old revisions.
}

Storing Soft Deletes

By default, if your model supports soft deletes, Revisionable will store this and any restores as updates on the model.

You can choose to ignore deletes and restores by adding deleted_at to your $dontKeepRevisionOf array.

To better format the output for deleted_at entries, you can use the isEmpty formatter (see Format output for an example of this.)

Storing Force Delete

By default the Force Delete of a model is not stored as a revision.

If you want to store the Force Delete as a revision you can override this behavior by setting revisionForceDeleteEnabled to true by adding the following to your model:

protected $revisionForceDeleteEnabled = true;

In which case, the created_at field will be stored as a key with the oldValue() value equal to the model creation date and the newValue() value equal to null.

Attention! Turn on this setting carefully! Since the model saved in the revision, now does not exist, so you will not be able to get its object or its relations.

Storing Creations

By default the creation of a new model is not stored as a revision. Only subsequent changes to a model is stored.

If you want to store the creation as a revision you can override this behavior by setting revisionCreationsEnabled to true by adding the following to your model:

protected $revisionCreationsEnabled = true;

More Control

No doubt, there'll be cases where you don't want to store a revision history only for certain fields of the model, this is supported in two different ways. In your model you can either specifiy which fields you explicitly want to track and all other fields are ignored:

protected $keepRevisionOf = ['title'];

Or, you can specify which fields you explicitly don't want to track. All other fields will be tracked.

protected $dontKeepRevisionOf = ['category_id'];

The $keepRevisionOf setting takes precedence over $dontKeepRevisionOf

Storing additional fields in revisions

In some cases, you'll want additional metadata from the models in each revision. An example of this might be if you have to keep track of accounts as well as users. Simply create your own new migration to add the fields you'd like to your revision model, add them to your config/revisionable.php in an array like so:

'additional_fields' => ['account_id', 'permissions_id', 'other_id'], 

If the column exists in the model, it will be included in the revision.

Make sure that if you can't guarantee the column in every model, you make that column nullable() in your migrations.

Events

Every time a model revision is created an event is fired. You can listen for revisionable.created,
revisionable.saved or revisionable.deleted.

// app/Providers/EventServiceProvider.php

public function boot()
{
    parent::boot();

    $events->listen('revisionable.*', function($model, $revisions) {
        // Do something with the revisions or the changed model. 
        dd($model, $revisions);
    });
}

Format output

You can continue (and are encouraged to) use Eloquent accessors in your model to set the output of your values, see the Laravel Documentation for more information on accessors The below documentation is therefor deprecated

In cases where you want to have control over the format of the output of the values, for example a boolean field, you can set them in the $revisionFormattedFields array in your model. e.g.,

protected $revisionFormattedFields = [
    'title'      => 'string:<strong>%s</strong>',
    'public'     => 'boolean:No|Yes',
    'modified'   => 'datetime:m/d/Y g:i A',
    'deleted_at' => 'isEmpty:Active|Deleted'
];

You can also override the field name output using the $revisionFormattedFieldNames array in your model, e.g.,

protected $revisionFormattedFieldNames = [
    'title'      => 'Title',
    'small_name' => 'Nickname',
    'deleted_at' => 'Deleted At'
];

This comes into play when you output the revision field name using $revision->fieldName()

String

To format a string, simply prefix the value with string: and be sure to include %s (this is where the actual value will appear in the formatted response), e.g.,

string:<strong>%s</strong>

Boolean

Booleans by default will display as a 0 or a 1, which is pretty bland and won't mean much to the end user, so this formatter can be used to output something a bit nicer. Prefix the value with boolean: and then add your false and true options separated by a pipe, e.g.,

boolean:No|Yes

Options

Analogous to "boolean", only any text or numeric values can act as a source value (often flags are stored in the database). The format allows you to specify different outputs depending on the value. Look at this as an associative array in which the key is separated from the value by a dot. Array elements are separated by a vertical line.

options: search.On the search|network.In networks

DateTime

DateTime by default will display as Y-m-d H:i:s. Prefix the value with datetime: and then add your datetime format, e.g.,

datetime:m/d/Y g:i A

Is Empty

This piggy backs off boolean, but instead of testing for a true or false value, it checks if the value is either null or an empty string.

isEmpty:No|Yes

This can also accept %s if you'd like to output the value, something like the following will display 'Nothing' if the value is empty, or the actual value if something exists:

isEmpty:Nothing|%s

Load revision history

To load the revision history for a given model, simply call the revisionHistory method on that model, e.g.,

$article = Article::find($id);
$history = $article->revisionHistory;

Displaying history

For the most part, the revision history will hold enough information to directly output a change history, however in the cases where a foreign key is updated we need to be able to do some mapping and display something nicer than plan_id changed from 3 to 1.

To help with this, there's a few helper methods to display more insightful information, so you can display something like Chris changed plan from bronze to gold.

The above would be the result from this:

@foreach($account->revisionHistory as $history )
    <li>{{ $history->userResponsible()->first_name }} changed {{ $history->fieldName() }} from {{ $history->oldValue() }} to {{ $history->newValue() }}</li>
@endforeach

If you have enabled revisions of creations as well you can display it like this:

@foreach($resource->revisionHistory as $history)
  @if($history->key == 'created_at' && !$history->old_value)
    <li>{{ $history->userResponsible()->first_name }} created this resource at {{ $history->newValue() }}</li>
  @else
    <li>{{ $history->userResponsible()->first_name }} changed {{ $history->fieldName() }} from {{ $history->oldValue() }} to {{ $history->newValue() }}</li>
  @endif
@endforeach

userResponsible()

Returns the User that was responsible for making the revision. A user model is returned, or null if there was no user recorded.

The user model that is loaded depends on what you have set in your config/auth.php file for the model variable.

fieldName()

Returns the name of the field that was updated, if the field that was updated was a foreign key (at this stage, it simply looks to see if the field has the suffix of _id) then the text before _id is returned. e.g., if the field was plan_id, then plan would be returned.

Remember from above, that you can override the output of a field name with the $revisionFormattedFieldNames array in your model.

identifiableName()

This is used when the value (old or new) is the id of a foreign key relationship.

By default, it simply returns the ID of the model that was updated. It is up to you to override this method in your own models to return something meaningful. e.g.,

use Venturecraft\Revisionable\Revisionable;

class Article extends Revisionable
{
    public function identifiableName()
    {
        return $this->title;
    }
}

oldValue() and newValue()

Get the value of the model before or after the update. If it was a foreign key, identifiableName() is called.

Unknown or invalid foreign keys as revisions

In cases where the old or new version of a value is a foreign key that no longer exists, or indeed was null, there are two variables that you can set in your model to control the output in these situations:

protected $revisionNullString = 'nothing';
protected $revisionUnknownString = 'unknown';

disableRevisionField()

Sometimes temporarily disabling a revisionable field can come in handy, if you want to be able to save an update however don't need to keep a record of the changes.

$object->disableRevisionField('title'); // Disables title

or:

$object->disableRevisionField(['title', 'content']); // Disables title and content

Contributing

Contributions are encouraged and welcome; to keep things organised, all bugs and requests should be opened in the GitHub issues tab for the main project, at venturecraft/revisionable/issues

All pull requests should be made to the develop branch, so they can be tested before being merged into the master branch.

Having troubles?

If you're having troubles with using this package, odds on someone else has already had the same problem. Two places you can look for common answers to your problems are:

If you do prefer posting your questions to the public on StackOverflow, please use the 'revisionable' tag.

Comments
  • Support inheriting from any other Eloquent implementation

    Support inheriting from any other Eloquent implementation

    If I'm using Ardent, which is derived from Eloquent, how could I also use Revisionable, since PHP doesn't support multiple inheritances.

    Would be great if we could set the class from which Revisionable 'inherits' from or is 'created from' internally, so we could use any other implementation of Eloquent with the package.

    enhancement 
    opened by antonioribeiro 34
  • Use Cartalyst Sentry user id

    Use Cartalyst Sentry user id

    We're using Sentry to handle our users. It would be great there was an option to use Sentry's user id instead of Laravel's built it one.

    I know what needs to be changed, but I'm just not sure how to make it easily configurable for others.

    Line #120 would be replaced with the following:

    'user_id' => (\Sentry::check() ? \Sentry::getUser()->id : null),
    

    Maybe a solution would be to have a package configuration file with an option like culpa. https://github.com/rmasters/culpa#changing-the-user-source

    opened by jveldboom 13
  • Revisionable and Mutators

    Revisionable and Mutators

    Hi,

    one of my revisionable models has some mutators like getPriceAttribute / setPriceAttribute.

    Revisionable's oldValue / newValue irgnores my getter and returns the raw price atrribute?

    enhancement confirmed 
    opened by mmodler 13
  • using Event::dispatch instead of deprecated Event::fire method

    using Event::dispatch instead of deprecated Event::fire method

    fixes issue #341

    In the Laravel 5.8 docs:

    The fire method (which was deprecated in Laravel 5.4) of the Illuminate/Events/Dispatcher class has been removed. You should use the dispatch method instead.

    opened by markacianfrani 12
  • Instructions not working.

    Instructions not working.

    This how the instructions says to use:

    namespace MyApp\Models;
    
    class Article extends Eloquent {
        use \Venturecraft\Revisionable\RevisionableTrait;
    
        public static function boot()
        {
            parent::boot();
        }
    }
    
    

    When I look in in my models, they extend Model, not Eloquent.

    When I try this code:

    class Record extends Model
    
    {
        use Venturecraft\Revisionable\RevisionableTrait;
    
    

    I get: Fatal error: Trait 'App\Venturecraft\Revisionable\RevisionableTrait' not found in /var/www/html/selection/app/Record.php on line 13

    What am I doing wrong?

    Mick

    opened by ArtisanTinkerer 11
  • Foreign key problem with namespaced Models

    Foreign key problem with namespaced Models

    Hi,

    first of all thanks for the revisionable package, saving me hours of work!

    I made most of my namspaced models "revisionable" and everything works fine. Bit if i try to get the oldValue() from a foreign key relation, Class "myrelatedmodel" can't be found. If i look in Revision.php line 72, i know why:

    $item  = $model::find($this->old_value);
    

    This will try to get myrelatedmodel, not App/Models/myrelatedmodel.

    I don't know how to solve the issue - un-namespacing all my models would work but is no option for me. Perhaps revisionable can extract the model namespace (if there is any) stored in revisionable_type like 'App\Models\Mymodel'? Ir is there a simple solution i can't think about?

    Thanks in advance!

    enhancement confirmed 
    opened by mmodler 11
  • Enable to choose db connection

    Enable to choose db connection

    Enable to choose db connection by creating simple config file: config/revisionable.php

    For example:

    return [ 'db_connection' => 'mysql-revision' ];

    opened by verox 10
  • get revisions for all models

    get revisions for all models

    How would i get a collection of revisions for ALL models? I want to have a revisions table on a page on my site that acts like a 'feed' that will show all changes made to anything on the website.. I currently am able to get the revisions but not able to use the methods such as getting the model that was edited because it can be any sort of model

    opened by z1haze 10
  • Error: Class 'app\models\Eloquent' not found

    Error: Class 'app\models\Eloquent' not found

    Hi, I don't know if this is the right place for this. Kindly pardon my ignorance if so.

    My problem is...I have installed the package per the instructions in my Laravel 4. I get the following error:

    Class 'app\models\Eloquent' not found
    

    when i use based on the new, trait based implementation like this:

    <?php
    use Illuminate\Auth\UserInterface;
     use Illuminate\Auth\Reminders\RemindableInterface;
    
    namespace MyApp\Models;
    
    class User extends Eloquent implements UserInterface, RemindableInterface {
    
    use Venturecraft\Revisionable\Revisionable;
    ...
    

    Please what could be the issue here?

    opened by tochie-daniels 9
  • Add support to JSON fields

    Add support to JSON fields

    I ended up with this solution in a fork of this project that I've worked on.

    If a json { "title": "foo", "description": "bar" } is updated to { "title": "foo", "description": "baz" } it will keep only the updated field on revisions:

    old_value: { "description": "bar" } new_value: { "description": "baz" }

    #337 #210

    opened by JimmyBastos 8
  • Package migration doesnt work L5.1.24

    Package migration doesnt work L5.1.24

    Finally, you'll also need to run migration on the package php artisan migrate --package=venturecraft/revisionable

    This wil throw an error: exception 'RuntimeException' with message 'The "--package" option does not exist. Apparantly the --package switch is not supported anymore as of L5.1.24

    Please change documentation from:

    Finally, you'll also need to run migration on the package php artisan migrate --package=venturecraft/revisionable

    To:

    Finally, you'll also need to run migration on the package php artisan migrate --path=vendor/venturecraft/revisionable/src/migrations

    As this will solve this problem.

    Regards, Rick

    opened by ricklwd 8
  • use in tables without integer primary key

    use in tables without integer primary key

    How to use revesionable package for tables without integer primary key? I have the following error:

    Invalid datetime format: 1366 Incorrect integer value: 'y' for column

    opened by javadzanguei 0
  • DateTime object to json serialization

    DateTime object to json serialization

    In mongodb when storing datetime object it stores empty object

    'created_at' => new \DateTime(), 'updated_at' => new \DateTime(),

    Using PHP8 with mongo 5.0 and MongoDB extension version => 1.13.0

    opened by kkarayat 0
  • so i created this fuction to return 'meta', anyone knows how to access [key] => date_achat / i want to display only the arrays where key == date_achat

    so i created this fuction to return 'meta', anyone knows how to access [key] => date_achat / i want to display only the arrays where key == date_achat

    public function gethistory($reference) { $Equipement = Equipement::where('reference', $reference)->first(); foreach ($Equipement->histories as $eq) { print_r($eq->meta);

    Screenshot 2022-04-24 114906 }

    opened by MohammadTabbaby 0
  • More documentation around identifiableName()

    More documentation around identifiableName()

    Can we get more documentation around identifiableName()

    In my revision_history it still shows the id's of my Model instead of the actual value even after adding.

    public function identifiableName()
        {
            return $this->distance;
        }
    

    Thanks

    opened by akc4 1
  • adding group_id to revisions table

    adding group_id to revisions table

    currently the package handles each field change as a revision , but what if we needed to detect a form ( or multiple field at same time ) change ? or ability to "group undo"? by adding a group field to revision this will be handled so easy.

    opened by phpust 0
Releases(1.39.0)
A laravel package to handle sanitize process of model data to create/update model records.

Laravel Model UUID A simple package to sanitize model data to create/update table records. Installation Require the package using composer: composer r

null 66 Sep 19, 2022
A Laravel package to monitor the status and history of jobs on the queue.

Monitored Jobs for Laravel Overview This package tracks the status and history of your queued jobs by hooking into the events that Laravel fires for i

Aryeo 9 Dec 9, 2022
🍪 Laravel cookie consent history

?? Laravel cookie consent history This packages aims to make it easy to store anonymous cookie consents in a database in order to comply with rather s

Dystopia 3 Nov 9, 2022
A laravel package to generate model hashid based on model id column.

Laravel Model Hashid A package to generate model hash id from the model auto increment id for laravel models Installation Require the package using co

Touhidur Rahman 13 Jan 20, 2022
A package to filter laravel model based on query params or retrieved model collection

Laravel Filterable A package to filter laravel model based on query params or retrived model collection. Installation Require/Install the package usin

Touhidur Rahman 17 Jan 20, 2022
Laravel-model-mapper - Map your model attributes to class properties with ease.

Laravel Model-Property Mapper This package provides functionality to map your model attributes to local class properties with the same names. The pack

Michael Rubel 15 Oct 29, 2022
A Laravel package that adds a simple image functionality to any Laravel model

Laraimage A Laravel package that adds a simple image functionality to any Laravel model Introduction Laraimage served four use cases when using images

Hussein Feras 52 Jul 17, 2022
Add settings to any Laravel model.

Laravel Property Bag Simple settings for Laravel apps. Easily give multiple resources settings Simple to add additional settings as your app grows Set

Zach Leigh 80 Aug 8, 2022
Turn any Eloquent model into a list!

Listify Turn any Eloquent model into a list! Description Listify provides the capabilities for sorting and reordering a number of objects in a list. T

Travis Vignon 138 Nov 28, 2022
This package provides a trait that will generate a unique uuid when saving any Eloquent model.

Generate slugs when saving Eloquent models This package provides a trait that will generate a unique uuid when saving any Eloquent model. $model = new

Abdul Kudus 2 Oct 14, 2021
A Laravel 8 Project Implement with GraphQL With Sanctum APIs Authentications Which utilized in Any Frontend or Any Mobile Application Programs.

A Laravel 8 Project Implement with GraphQL With Sanctum APIs Authentications Which utilized in Any Frontend or Any Mobile Application Programs.

Vikas Ukani 3 Jan 6, 2022
Laravel 2-Step Verification is a package to add 2-Step user authentication to any Laravel project easily.

Laravel 2-Step verification is a package to add 2-Step user authentication to any Laravel project easily. It is configurable and customizable. It uses notifications to send the user an email with a 4-digit verification code. Laravel 2-Step Authentication Verification for Laravel. Can be used in out the box with Laravel's authentication scaffolding or integrated into other projects.

Jeremy Kenedy 204 Dec 23, 2022
A Laravel Wrapper for the CoinDCX API. Now easily connect and consume the CoinDCX Public API in your Laravel apps without any hassle.

This package provides a Laravel Wrapper for the CoinDCX API and allows you to easily communicate with it. Important Note This package is in early deve

Moinuddin S. Khaja 2 Feb 16, 2022
Laravel package to create autonumber for Eloquent model

Laravel AutoNumber Laravel package to create autonumber for Eloquent model Installation You can install the package via composer: composer require gid

null 2 Jul 10, 2022
Need some filters? This package is based on the Repository Design Pattern to let you create specific queries easily.

DevMakerLab/Laravel-Filters Need some filters? This package is based on the Repository Design Pattern to let you create specific queries easily. Insta

DevMakerLab 19 Feb 20, 2022
This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

Jonas Staudenmeir 5 Jan 6, 2023
laravel-model-validator

laravel-model-validator This is a simple validator. The validator can be created by command. The validator has all common table column constraint, eg:

null 6 May 22, 2022
Automatic Laravel model migrations.

Laravel Automatic Migrations Automatic Laravel model migrations. Instead of having to create and manage migration files, this package allows you to sp

null 38 Nov 11, 2022
Update multiple Laravel Model records, each with it's own set of values, sending a single query to your database!

Laravel Mass Update Update multiple Laravel Model records, each with its own set of values, sending a single query to your database! Installation You

Jorge González 88 Dec 31, 2022