Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Chronos Laravel Package

cino/laravel-chronos

cino/laravel-chronos is a lightweight Laravel package for working with time in your app—track temporal state, schedule recurring work, and query time-based records through a clean, Eloquent-friendly API that fits Laravel conventions.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require cino/laravel-chronos
php artisan vendor:publish --tag=chronos-config   # check the README for the exact tag
php artisan migrate

Look at these three things first, in this order:

  1. The base model / trait source — this is where the versioning behaviour actually lives. Read the boot* method and the save/delete overrides, because that is the entire mechanism.
  2. The published migration — it tells you the version table's columns (model type, model id, payload, causer, timestamps) and therefore what you can query later.
  3. The config file — table name, connection, and any on/off switch. Confirm versioning is enabled for the environments you care about (usually you want it off in local/test until you deliberately test it).

First use case: pick one model whose history has real business value — an Order, Invoice, or a Page — not User. Wire it up, then create and update a record in Tinker and confirm a row lands in the versions table. If that works, the rest of the package is just querying and restoring.

The one decision to make up front (new in v1.1.0):

  • class Order extends ChronosModel — simplest, works when your models extend plain Illuminate\Database\Eloquent\Model.
  • class Order extends Model { use Chronos; } — required when you already extend something else: a tenant-aware base model, Spatie\Permission, a UUID/ULID base class, or your own AbstractModel. Before v1.1.0 this was a blocker; now it isn't.

Implementation Patterns

Retrofitting an existing codebase (the v1.1.0 headline).

The trait is the migration path off the base class. The mechanical change is:

// before
class Order extends ChronosModel {}

// after
use Cino\Chronos\Concerns\Chronos;   // verify namespace in the package source

class Order extends TenantModel
{
    use Chronos;
}

Do this per-model, not in a big-bang commit. Behaviour can differ subtly between the two paths — most importantly the base class may set $connection, $guarded, $casts, or a custom newInstance() that the trait does not. Diff the base class against your new parent before assuming parity.

Pick one style per codebase. Mixing extends ChronosModel in half your models and the trait in the other half is legal but makes it hard to reason about which models are versioned. Convention: trait everywhere if any model needs a custom base; base class everywhere only if all models are plain.

Restore/replay should be append-only. When you restore an old version, the restore itself is a write — it should produce a new version rather than deleting history. Wrap restore in a transaction:

DB::transaction(function () use ($order, $version) {
    $order->restoreFrom($version);   // confirm actual method name in README
});

If the package's restore deletes rather than appends, wrap it yourself with an explicit "restored from version X" record so the audit trail survives.

Version-aware side effects. Don't hook notifications or search-index syncs to saved — they'll fire on every write including restores. Hook them to the version-created event if one is emitted; otherwise check $model->wasChanged() inside the observer and compare against your versioning intent.

History UI. Paginate versions rather than loading them all, and eager-load the causer relation if the package records an authenticated user — otherwise an admin history screen becomes an N+1 disaster at ~20 versions per record.

Testing. RefreshDatabase plus one assertion is enough to catch the silent-failure mode:

$order = Order::factory()->create();
$order->update(['status' => 'shipped']);
$this->assertDatabaseCount('chronos_versions', 2);

Keep this test next to any model you version. It's the cheapest guard against a trait/boot regression.

Gotchas and Tips

Trait method collisions are the main risk of v1.1.0. Boot traits are resolved as boot{TraitName}, and if you override save() or delete() in your own base model and the trait does too, PHP picks the class method and the trait's versioning silently never runs. Resolve explicitly:

use Chronos, SoftDeletes {
    Chronos::save insteadof TenantModel;
}

If you can't express the conflict cleanly, alias the trait method and call it from your override.

Silent no-op after converting to the trait. No exception, no log line — versions just stop being written. Usual causes: the boot method isn't being invoked (trait not actually used in the final class), a parent class's boot() doesn't call parent::boot(), or the model is saved via a path that bypasses Eloquent. The assertDatabaseCount test above is your detector.

Bulk writes bypass everything. Order::where(...)->update([...]), insert(), and raw SQL create no versions. Neither does saveQuietly() or anything inside Model::withoutEvents(). If audit coverage is a compliance requirement, wrap bulk operations in chunked ->each->save() or accept and document the gap.

Version tables grow without bound. Add a composite index on (model_type, model_id, created_at) before you have a million rows, not after. Plan a retention policy early — a scheduled prune that keeps the last N versions per model, or archives to a cold table.

Transactions and audit semantics. A version written inside your transaction disappears on rollback. Usually that's correct ("no order, no history"), but if your auditors want attempted-and-failed writes, version outside the transaction — and accept that you'll then need to reconcile orphan versions.

Payload size. Versions are typically serialised JSON. A model with a large body or description column multiplies storage fast. Exclude heavy columns if the package supports an attribute allow/deny list; otherwise consider a $chronosVersioned array on the model.

Debugging checklist, in order: is the trait/base actually applied (class_uses_recursive($model)), did the boot run (Chronos::isVersioned() or equivalent), is the config table name correct, is the connection the same as the model's. Nine times out of ten it's the first or fourth.

Extension points worth knowing: swap the version model or repository via a container binding in a service provider if you need custom diffing or a different table; add casts to the version model for typed payloads; emit your own domain event from a version-created listener rather than modifying the package.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console