PHPackages                             bjthecod3r/laravel-recordables - 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. bjthecod3r/laravel-recordables

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

bjthecod3r/laravel-recordables
==============================

Record snapshots of Eloquent model data over time.

0.1.0(3w ago)1138↑117.4%MITPHPPHP ^8.2CI passing

Since May 16Pushed 3w agoCompare

[ Source](https://github.com/BJTheCod3r/laravel-recordables)[ Packagist](https://packagist.org/packages/bjthecod3r/laravel-recordables)[ RSS](/packages/bjthecod3r-laravel-recordables/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

 [![Laravel Recordables](art/logo.svg)](art/logo.svg)

Laravel Recordables
===================

[](#laravel-recordables)

 A lightweight Laravel package for recording snapshots of Eloquent model data over time.

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

[](#installation)

```
composer require bjthecod3r/laravel-recordables
```

Publish the config and migration:

```
php artisan vendor:publish --tag=recordables-config
php artisan vendor:publish --tag=recordables-migrations
php artisan migrate
```

Quick start
-----------

[](#quick-start)

```
use BJTheCod3r\Recordables\Concerns\HasRecordings;
use BJTheCod3r\Recordables\Contracts\Recordable;
use Illuminate\Database\Eloquent\Model;

class Product extends Model implements Recordable
{
    use HasRecordings;

    protected array $recordable = [
        'price',
        'stock',
        'views',
    ];
}
```

```
$product = Product::create([...]);  // creates a recording
$product->update(['price' => 200]);    // creates a recording
$product->record();                        // manual snapshot
```

For computed/derived snapshots, override `toRecording()` instead of declaring `$recordable`:

```
public function toRecording(): array
{
    return [
        'price' => $this->price,
        'profit_margin' => $this->calculateProfitMargin(),
    ];
}
```

Recording API
-------------

[](#recording-api)

```
$product->record(
    data: ['price' => 200],
    changeType: 'synced',
    changeSource: 'cms_sync',
    causer: auth()->user(),
    recordedAt: now()->subDay(),
);

$product->recordIfChanged();
$product->recordSilently();
$product->recordOnQueue();
```

Retrieval
---------

[](#retrieval)

```
$product->recordings;                      // MorphMany relation
$product->latestRecording();
$product->firstRecording();
$product->recordingAt(now()->subWeek());
$product->recordingsBetween($start, $end);
```

Analytics
---------

[](#analytics)

All analytics helpers return immutable, JSON-serializable value objects.

```
$history = $product->recordingHistory('price');           // History
$growth  = $product->recordingGrowth('price');            // Growth
$delta   = $product->recordingDelta('price');             // ?Delta
$minmax  = $product->recordingMinMax('price');            // ?MinMax
$trend   = $product->recordingTrend('price');             // Trend enum
$average = $product->recordingAverage('price');           // float
$chart   = $product->recordingChartData('price', 'day');  // ChartData

$growth->isPositive();
$growth->percentage;   // 50.0
$growth->toArray();    // JSON-friendly
```

Pass a `period` to switch from all-time growth to period-over-period:

```
$product->recordingGrowth('price', period: 'month'); // this month vs last
$product->recordingGrowth('views', period: 'week');
$product->recordingGrowth('stock', period: 'day');
```

Boundaries are calendar-aligned via Carbon (`startOfWeek`, `startOfMonth`, …), so `'week'` is "this week vs last week", not "last 7 days vs the 7 days before that." Supported periods: `hour`, `day`, `week`, `month`, `year`. Each side resolves to the **latest recording within its window** that carries the metric.

Non-throwing variant for both modes:

```
$product->recordingGrowthOrNull('price');
$product->recordingGrowthOrNull('price', period: 'month');
```

Events
------

[](#events)

- `RecordingCreating` — cancellable; call `$event->cancel()` from a listener to skip persistence.
- `RecordingCreated` — fired after persistence.

When a recording is skipped (disabled, unchanged, or cancelled), `record()`and `recordIfChanged()` return `null` — check the return value rather than listening for a separate event.

Testing
-------

[](#testing)

```
use BJTheCod3r\Recordables\Facades\Recordables;

Recordables::fake();

$product->update(['price' => 100]);

Recordables::assertRecorded($product);
Recordables::assertRecordedTimes($product, 1);
Recordables::assertRecordedWith($product, fn ($data) => $data['price'] === 100);
Recordables::assertNotRecorded($otherProduct);
Recordables::assertNothingRecorded();
```

Exceptions
----------

[](#exceptions)

All package exceptions extend `RecordablesException`:

- `MissingRecordableDefinitionException` — no `$recordable` property and no `toRecording()` override.
- `InvalidRecordableMetricException` — analytics asked for a metric absent from every recording.
- `InsufficientRecordingsException` — fewer than two comparable points (e.g., growth needs two).
- `NonNumericMetricException` — analytics ran against a metric stored as a non-numeric value (string, bool, array). Carries `metric`, `recordableClass`, and `actualType`.
- `RecordingFailedException` — persistence error wrapper used inside queued jobs.

Metrics stored as `null` are silently skipped (treated as "no sample this time") so missing values from integrations don't break analytics.

Pruning
-------

[](#pruning)

```
php artisan recordables:prune --days=90
php artisan recordables:prune --keep=100 --model="App\Models\Product"
```

Schedule it:

```
Schedule::command('recordables:prune')->daily();
```

Per-model overrides
-------------------

[](#per-model-overrides)

```
class Product extends Model implements Recordable
{
    use HasRecordings;

    protected array $recordable = ['price', 'stock'];

    protected bool $recordOnCreate = true;
    protected bool $recordOnUpdate = true;
    protected bool $recordOnlyOnChange = true;
    protected ?int $keepRecordingsForDays = 90;
    protected ?int $keepRecordingsCount = null;
}
```

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance95

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

 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

Unknown

Total

1

Last Release

24d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c9d0cfc944007a872649f1e04921e82d5f0b5bc18de0bc8866a1e00b0db970e?d=identicon)[bjthecod3r](/maintainers/bjthecod3r)

---

Top Contributors

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

---

Tags

laraveleloquentMetricshistoryanalyticssnapshots

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/bjthecod3r-laravel-recordables/health.svg)

```
[![Health](https://phpackages.com/badges/bjthecod3r-laravel-recordables/health.svg)](https://phpackages.com/packages/bjthecod3r-laravel-recordables)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[watson/validating

Eloquent model validating trait.

9743.4M53](/packages/watson-validating)[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.0M84](/packages/mongodb-laravel-mongodb)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M120](/packages/laravel-pulse)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[reedware/laravel-relation-joins

Adds the ability to join on a relationship by name.

2121.2M16](/packages/reedware-laravel-relation-joins)

PHPackages © 2026

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