andrewdyer / metrics
A library for calculating and aggregating metrics from Eloquent queries
Requires
- php: ^8.3
- illuminate/database: ^11.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- phpunit/phpunit: ^10.5
This package is auto-updated.
Last update: 2026-06-08 19:22:29 UTC
README
A library for calculating and aggregating metrics from Eloquent queries.
Introduction
This library provides strongly typed metric calculations and aggregations from Eloquent queries, including Value, Trend, and Partition metrics that return structured result objects with JSON serialisation support. The package is built on top of andrewdyer/php-package-template.
Prerequisites
- PHP: Version 8.3 or higher is required.
- Composer: Dependency management tool for PHP.
- Eloquent ORM: An configured Eloquent connection is required.
Installation
composer require andrewdyer/metrics
Getting Started
1. Set up Eloquent
This library requires an Eloquent connection. In a Laravel application this is configured automatically. Outside of Laravel, bootstrap Eloquent using the Capsule manager:
use Illuminate\Database\Capsule\Manager as Capsule; $capsule = new Capsule(); $capsule->addConnection([ 'driver' => 'mysql', 'host' => '127.0.0.1', 'database' => 'my_database', 'username' => 'root', 'password' => '', 'charset' => 'utf8', 'prefix' => '', ]); $capsule->setAsGlobal(); $capsule->bootEloquent();
2. Create a metric
Extend Value and implement calculate() to define the query, and getStartDate() / getEndDate() to define the date range:
use AndrewDyer\Metrics\Value; use AndrewDyer\Metrics\Results\ValueResult; use DateTimeImmutable; class TotalNewUsers extends Value { public function calculate(): ValueResult { return $this->count(User::query()); } public function getStartDate(): DateTimeImmutable { return new DateTimeImmutable('first day of this month'); } public function getEndDate(): DateTimeImmutable { return new DateTimeImmutable('last day of this month'); } }
Usage
Calculating a metric
Call calculate() on any metric instance to run the query and return a typed result:
$metric = new TotalNewUsers(); $result = $metric->calculate(); $result->getResult(); // e.g. 42
JSON serialisation
All result objects implement JsonSerializable:
json_encode($metric->calculate()); // {"result": 42}
Customising the metric name and description
Override getName() and getDescription() on any metric to provide a human-readable label:
public function getName(): string { return 'Total New Users'; } public function getDescription(): string { return 'The total number of users who signed up this month.'; }
Rounding
Override getRoundingPrecision() and getRoundingMode() to control how aggregate values are rounded:
public function getRoundingPrecision(): int { return 2; } public function getRoundingMode(): int { return PHP_ROUND_HALF_UP; }
Metric Types
Value
A Value metric calculates a single aggregate over a date range. When the model uses timestamps, the date filter defaults to created_at. When the model does not use timestamps, no date filter is applied unless an explicit $dateColumn is passed.
Count
Returns the number of records within the date range:
$this->count(User::query());
To filter by a specific date column, pass it explicitly:
$this->count(User::query(), null, 'published_at');
On models without timestamps, no date filter is applied unless an explicit column is given:
$this->count(Product::query()); // no date filter — all records returned $this->count(Product::query(), null, 'released_at'); // filtered by released_at
Sum
Totals the values of a column across all matching records:
$this->sum(User::query(), 'revenue');
Average
Calculates the mean value of a column across all matching records:
$this->average(User::query(), 'revenue');
Minimum
Returns the smallest value of a column within the date range:
$this->min(User::query(), 'revenue');
Maximum
Returns the largest value of a column within the date range:
$this->max(User::query(), 'revenue');
Trend
A Trend metric calculates an aggregate grouped over time at a given frequency. Missing dates within the range are automatically filled with zero values.
When the model uses timestamps, the date column defaults to created_at. When the model does not use timestamps, an explicit $dateColumn must be passed or an InvalidArgumentException is thrown.
Extend Trend and implement calculate(), getStartDate(), getEndDate(), and getFrequency():
use AndrewDyer\Metrics\Trend; use AndrewDyer\Metrics\Enums\Frequency; use AndrewDyer\Metrics\Results\TrendResult; use DateTimeImmutable; class UserSignupsOverTime extends Trend { public function calculate(): TrendResult { return $this->count(User::query(), null, 'created_at'); } public function getFrequency(): Frequency { return Frequency::Monthly; } public function getStartDate(): DateTimeImmutable { return new DateTimeImmutable('first day of january this year'); } public function getEndDate(): DateTimeImmutable { return new DateTimeImmutable('last day of december this year'); } }
Calculating the metric returns a keyed array of labels to aggregate values:
$metric = new UserSignupsOverTime(); $result = $metric->calculate(); $result->getResult(); // ['January 2026' => 12, 'February 2026' => 9, ...]
Supported frequencies
Frequency::Daily Frequency::Weekly Frequency::Monthly
Count
Tracks how many records fall within each period of the date range:
$this->count(User::query(), null, 'created_at');
Sum
Totals a column's values for each period across the date range:
$this->sum(User::query(), 'revenue', 'created_at');
Average
Calculates the mean value of a column for each period in the range:
$this->average(User::query(), 'revenue', 'created_at');
Minimum
Returns the lowest value of a column recorded within each period:
$this->min(User::query(), 'revenue', 'created_at');
Maximum
Returns the highest value of a column recorded within each period:
$this->max(User::query(), 'revenue', 'created_at');
Partition
A Partition metric calculates an aggregate grouped by a categorical column, returning a key-value breakdown. Results are filtered to the configured date range.
When the model uses timestamps, the date filter defaults to created_at. When the model does not use timestamps, no date filter is applied unless an explicit $dateColumn is passed.
Extend Partition and implement calculate(), getStartDate(), and getEndDate():
use AndrewDyer\Metrics\Partition; use AndrewDyer\Metrics\Results\PartitionResult; use DateTimeImmutable; class UsersByCountry extends Partition { public function calculate(): PartitionResult { return $this->count(User::query(), 'country'); } public function getStartDate(): DateTimeImmutable { return new DateTimeImmutable('first day of this year'); } public function getEndDate(): DateTimeImmutable { return new DateTimeImmutable('today'); } }
Calculating the metric returns a keyed array of group labels to aggregate values:
$metric = new UsersByCountry(); $result = $metric->calculate(); $result->getResult(); // ['GB' => 42, 'US' => 31, 'DE' => 14]
Count
Groups records by a column and counts how many fall into each category:
$this->count(User::query(), 'country');
To filter by a specific date column, pass it explicitly:
$this->count(User::query(), 'country', null, 'registered_at');
Sum
Groups records by a column and totals a second column within each group:
$this->sum(User::query(), 'country', 'revenue');
Average
Groups records by a column and calculates the mean of a second column per group:
$this->average(User::query(), 'country', 'revenue');
Database Support
The following database drivers are supported:
| Driver | Minimum Version |
|---|---|
| MySQL | 5.7+ |
| MariaDB | 10.2+ |
| SQLite | 3.38.0+ |
| PostgreSQL | 9.4+ |
License
Licensed under the MIT licence and is free for private or commercial projects.