spatie/laravel-stats
Lightweight Laravel package to track and summarize database stat changes over time. Create a stats class, call increase/decrease on events, then query totals and deltas over ranges grouped by day/week/month for easy reporting.
Installation
composer require spatie/laravel-stats
php artisan vendor:publish --provider="Spatie\Stats\StatsServiceProvider" --tag="stats-migrations"
php artisan migrate
Create a Stats Class
Define a class extending Spatie\Stats\BaseStats:
// app/Stats/SubscriptionStats.php
namespace App\Stats;
use Spatie\Stats\BaseStats;
class SubscriptionStats extends BaseStats {}
First Use Case Track subscriptions/cancellations in a controller or event:
// When a user subscribes
SubscriptionStats::increase();
// When a user cancels
SubscriptionStats::decrease();
Query Stats Fetch historical data (e.g., weekly stats for the last 2 months):
$stats = SubscriptionStats::query()
->start(now()->subMonths(2))
->end(now())
->groupByWeek()
->get();
Event-Driven Tracking
increase()/decrease() in:
SubscriptionCreated event).ProcessPaymentJob after successful payment).// app/Events/SubscriptionCreated.php
public function handle() {
SubscriptionStats::increase();
}
Batch Updates
set():
$count = $api->fetchSubscriptionCount();
SubscriptionStats::set($count);
Time-Adjusted Tracking
SubscriptionStats::increase(1, $subscription->created_at);
Query Patterns
start()/end() (e.g., monthly reports).minute, hour, day, week, month, or year.value, increments, decrements, and difference per period.Model-Backed Stats
StatsWriter for Eloquent models (e.g., tenant-specific stats):
// Track orders per tenant
StatsWriter::for($tenant->orders)->increase(1);
StatsQuery::for($tenant->orders)
->groupByMonth()
->get();
Custom Attributes
StatsWriter::for('subscription', ['type' => 'premium'])->increase(1);
StatsQuery::for('subscription', ['type' => 'premium'])
->groupByWeek()
->get();
Direct Writer Access
writer() for bulk operations:
SubscriptionStats::writer()->set(100); // Override current value
Integration with Analytics
$stats = SubscriptionStats::query()->get();
$analyticsService->send($stats);
Unit Tests
BaseStats methods:
$stats = Mockery::mock(SubscriptionStats::class);
$stats->shouldReceive('increase')->once();
Database Tests
$this->assertDatabaseHas('stats', [
'name' => 'subscription',
'value' => 100,
]);
Query Validation
$stats = SubscriptionStats::query()->groupByWeek()->get();
$this->assertCount(4, $stats); // 4 weeks of data
Migration Conflicts
--force:
php artisan migrate:fresh --env=testing
Timestamp Precision
groupByMinute() may return empty results for sparse data.groupByHour() or pad data with set() calls.Relationship Queries
StatsQuery::for($model->relationship()) fails if the relationship is lazy-loaded.$tenant = Tenant::with('orders')->find(1);
StatsQuery::for($tenant->orders)->get();
Concurrency Issues
increase()/decrease().DB::transaction(function () {
SubscriptionStats::increase();
});
Query Logging
\DB::enableQueryLog();
SubscriptionStats::query()->get();
\DB::getQueryLog(); // Inspect generated queries
DataPoint Inspection
DataPoint objects to debug:
$stats = SubscriptionStats::query()->get();
dd($stats->first()->toArray());
Migration Rollback
php artisan migrate:fresh --env=testing
Custom Storage
getTable() in BaseStats for non-default tables:
public function getTable(): string
{
return 'custom_stats';
}
Event Hooks
BaseStats to trigger events:
protected static function afterIncrease()
{
event(new StatIncreased(self::class));
}
Period Customization
StatsQuery:
public function groupByQuarter()
{
return $this->groupBy(fn () => 'quarter');
}
API Integration
// app/Facades/StatsFacade.php
public static function syncFromApi()
{
$count = ApiClient::getSubscriptionCount();
SubscriptionStats::set($count);
}
Batch Writes
set() for bulk updates instead of incremental calls:
// Instead of:
foreach ($subscriptions as $sub) {
SubscriptionStats::increase();
}
// Do:
SubscriptionStats::set(count($subscriptions));
Query Caching
$stats = Cache::remember('subscription_stats_weekly', now()->addWeek(), function () {
return SubscriptionStats::query()->groupByWeek()->get();
});
Indexing
stats table for large datasets:
Schema::table('stats', function (Blueprint $table) {
$table->index(['name', 'created_at']);
});
How can I help you explore Laravel packages today?