PHPackages                             richardhulbert/revisions-model - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. richardhulbert/revisions-model

ActiveLibrary[Database &amp; ORM](/categories/database)

richardhulbert/revisions-model
==============================

Append-only, branch-aware revision models for Laravel Eloquent. Records are never updated in place - every change is a new row, so history is permanent and any revision can be rolled back.

0.2.0(3d ago)02MITPHPPHP ^8.4

Since Jul 7Pushed 3d agoCompare

[ Source](https://github.com/richardhulbert/RevisionsModel)[ Packagist](https://packagist.org/packages/richardhulbert/revisions-model)[ RSS](/packages/richardhulbert-revisions-model/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (4)Versions (3)Used By (0)

Revisions Model for Laravel
===========================

[](#revisions-model-for-laravel)

Append-only, branch-aware revision models for Eloquent. A record is never updated in place: `update()` clones the row with the new values, so every change stacks up as a new revision and any change can be permanently rolled back.

The pattern
-----------

[](#the-pattern)

Every table using this pattern has three extra columns:

ColumnMeaning`prime`The id of the first record in the chain. All revisions share it.`branch_id`The branch the revision was made on. Branch `1` is public/published by default.`user_id`The user who made the revision.A "record" as the rest of your app sees it is identified by its `prime`; the individual rows are its revisions. The newest row per branch is the current state of that record on that branch.

Installation
------------

[](#installation)

```
composer require richardhulbert/revisions-model
```

The service provider is auto-discovered. Publish the config if you need to change any defaults:

```
php artisan vendor:publish --tag=revisions-config
```

```
// config/revisions.php
return [
    'branch_model'          => \App\Models\Branch::class, // your branch model
    'user_model'            => null,      // null = default auth provider model
    'public_branch_id'      => 1,         // the published/public branch
    'user_branch_attribute' => 'branch',  // attribute on the user holding their current branch id
    'default_user_id'       => 1,         // author recorded when nobody is logged in
];
```

Migrations
----------

[](#migrations)

A `revisions()` Blueprint macro adds the three columns:

```
Schema::create('pages', function (Blueprint $table) {
    $table->id();
    $table->revisions();          // prime, user_id, branch_id
    $table->string('title');
    // ...
    $table->softDeletes();
    $table->timestamps();
});
```

Usage
-----

[](#usage)

Extend `RevisionsModel` instead of `Model`. The revision columns are merged into `$fillable` automatically — no constructor boilerplate needed:

```
use RichardHulbert\Revisions\RevisionsModel;

class Page extends RevisionsModel
{
    protected $fillable = ['title', 'slug', 'blocks'];
}
```

### Creating and updating

[](#creating-and-updating)

```
$page = Page::new(['title' => 'Home']);       // starts a chain: prime = id

$draft = $page->update(['title' => 'Home!']); // NEW row on the user's branch
$live  = $page->update(['title' => 'Home!', 'branch_id' => 1]); // explicit branch
```

`update()` returns the new revision; the row you called it on is untouched. `branch_id` defaults to the logged-in user's branch, `user_id` to the logged-in user.

### Reading

[](#reading)

```
$page->lastRevision()->first();          // user's branch if it has revisions, else public
$page->lastRevisionWithBranch(3)->first();
$page->lastPublicRevision()->first();

$page->revisions();      // history: latest revision per branch per day
$page->revisions(3);     // history on one branch

Page::allLatest()->get();   // newest revision of every record (public branch)
Page::allLatest(3)->get();  // ... on branch 3
```

### Relations

[](#relations)

```
$revision->prime;    // the first record of the chain
$revision->branch;   // the branch (even if soft-deleted)
$revision->owner;    // the author (even if soft-deleted)
```

To point at another revisioned model, store its `prime` in a column and use `hasOneRevision()` — an eager-loadable hasOne that resolves to the latest revision on a branch:

```
class Page extends RevisionsModel
{
    public function templateOnBranch(): HasOne
    {
        return $this->hasOneRevision(Template::class, 'template_id');      // user's branch
    }

    public function templatePublic(): HasOne
    {
        return $this->hasOneRevision(Template::class, 'template_id', 1);   // public branch
    }
}

Page::with('templateOnBranch')->get();  // works with with()/load()
```

### Deleting

[](#deleting)

Deleting any revision deletes the **prime** record of the chain (with soft deletes, this marks the whole record as deleted without losing history):

```
$revision->delete();
```

Testing
-------

[](#testing)

```
composer install
composer test
```

License
-------

[](#license)

MIT — see [LICENSE.md](LICENSE.md).

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance99

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

3d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/356980815dfb4115484f40b8816eb00eb480c5cab07a128bb04dabf0bd13cab2?d=identicon)[richardhulbert](/maintainers/richardhulbert)

---

Top Contributors

[![richardhulbert](https://avatars.githubusercontent.com/u/12337002?v=4)](https://github.com/richardhulbert "richardhulbert (3 commits)")

---

Tags

append-onlylaravelphplaraveleloquentversioningAuditRevisionsbranches

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/richardhulbert-revisions-model/health.svg)

```
[![Health](https://phpackages.com/badges/richardhulbert-revisions-model/health.svg)](https://phpackages.com/packages/richardhulbert-revisions-model)
```

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.4M96](/packages/mongodb-laravel-mongodb)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k32.6M46](/packages/kirschbaum-development-eloquent-power-joins)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8793.2M25](/packages/yajra-laravel-oci8)[glushkovds/phpclickhouse-laravel

Adapter of the most popular library https://github.com/smi2/phpClickHouse to Laravel

2051.5M2](/packages/glushkovds-phpclickhouse-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
