bjthecod3r/laravel-recordables 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

bjthecod3r/laravel-recordables

Composer 安装命令:

composer require bjthecod3r/laravel-recordables

包简介

Record snapshots of Eloquent model data over time.

README 文档

README

Laravel Recordables

Laravel Recordables

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

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

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

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

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

Retrieval

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

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

  • 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

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

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

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

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

MIT

统计信息

  • 总下载量: 278
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-16

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固