elegantly / laravel-kpi
Advanced KPI for your Laravel application
Fund package maintenance!
ElegantEngineeringTech
Requires
- php: ^8.2
- elegantly/laravel-money: ^2.0.1
- illuminate/contracts: ^11.0
- spatie/laravel-package-tools: ^1.16
- spatie/php-structure-discoverer: ^2.2
Requires (Dev)
- larastan/larastan: ^2.9
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1
- orchestra/testbench: ^9.0.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-arch: ^2.7
- pestphp/pest-plugin-laravel: ^2.3
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
README
This package provides an easy way to store KPIs from your application in your database and retrieve them in various formats. It's especially useful for tracking data related to your models, such as:
- Number of users
- Number of subscribed users
- Total revenue
- And more...
It's a perfect tool for building dashboards and displaying stats and charts.
Filament Plugin
Display your KPIs in a beatiful way with 1 line using our filament plugin: elegantly/filament-kpi
Installation
Install the package via Composer:
composer require elegantly/laravel-kpi
Publish and run the migrations with:
php artisan vendor:publish --tag="kpi-migrations"
php artisan migrate
Publish the configuration file with:
php artisan vendor:publish --tag="kpi-config"
Here is the content of the published config file:
return [ /* |-------------------------------------------------------------------------- | Discover Definitions |-------------------------------------------------------------------------- | | If 'enabled' is set to true, your KPI definitions will be automatically | discovered when taking snapshots. | Set the 'path' to specify the directory where your KPI definitions are stored. | Definitions will be discovered from this path and its subdirectories. | */ 'discover' => [ 'enabled' => true, /** * This path will be used with the `app_path` helper, like `app_path('Kpis')`. */ 'path' => 'Kpis', ], /* |-------------------------------------------------------------------------- | Registered Definitions |-------------------------------------------------------------------------- | | You can manually register your KPI definitions if you are not using | "discover" or if you want to add additional definitions located elsewhere. | */ 'definitions' => [], ];
Concepts
This package is not a query builder. Instead, it is based on a kpis
table where all KPIs are stored. This allows historical data (e.g., the number of users a year ago) to remain intact, even if models are permanently deleted.
Retrieving KPIs is also way more efficient when calculating complex values, such as "users who made a purchase last week."
A KPI can be simple or complex. Examples include:
- Number of registered users
- Monthly active users
- Total revenue invoiced to customers
- Number of recurring customers
- Average Order Value
- ...
KPIs can be either "absolute" or "relative":
- An absolute KPI represents a current state, such as the total number of users.
- A relative KPI represents a change, such as the number of new users each day.
Depending on the context, either an absolute or relative KPI may be more relevant. In most cases, relative KPIs can be derived from absolute ones, and vice versa. Therefore, it's often recommended to store KPIs as "absolute" and compute relative values when needed.
Usage
A KPI consists of two key components:
- A definition
- Its values
The definition is a class extending KpiDefinition
, where you configure the KPI.
Each KPI definition must have a unique name
, such as users:count
.
The values are stored in the kpis
table and represented by the Kpi
model.
A KPI value may contain:
- A value (float, string, Money, JSON)
- A description (optional)
- Tags (optional)
- Metadata (optional)
- A timestamp
1. Defining a KPI
Each KPI is represented by a single KpiDefinition
class. The package offers predefined classes for each data type:
KpiFloatDefinition
KpiStringDefinition
KpiMoneyDefinition
KpiJsonDefinition
You can also extend KpiDefinition
if you need custom behavior.
Example:
namespace App\Kpis\Users; use App\Models\User; use Elegantly\Kpi\Enums\KpiInterval; use Elegantly\Kpi\KpiFloatDefinition; class UsersCountKpi extends KpiFloatDefinition { public static function getName(): string { return 'users:count'; } /** * This KPI is intended to be snapshotted every day. */ public static function getSnapshotInterval(): KpiInterval { return KpiInterval::Day; } public function getValue(): float { return (float) User::query() ->when($this->date, fn ($query) => $query->where('created_at', '<=', $this->date)) ->toBase() ->count(); } /** * Description to store alongside the KPI value */ public function getDescription(): ?string { return null; } /** * Tags to store alongside the KPI value */ public function getTags(): ?array { return null; } /** * Metadata to store alongside the KPI value */ public function getMetadata(): ?array { return null; } }
As shown, the KpiDefinition
class has a date
property, representing the snapshot date. When possible, use date
in getValue
, this will allow you to seed your KPIs with past data.
2. Snapshotting KPIs
There are two ways to create KPI snapshots:
- Schedule the
kpis:snapshot
command - Manually create snapshots
Using the Command and Scheduler
To capture KPI data at regular intervals (e.g., hourly or daily), schedule the kpis:snapshot
command in your application's scheduler.
Example:
$schedule->command(SnapshotKpisCommand::class, [ 'interval' => KpiInterval::Hour, ])->everyHour(); $schedule->command(SnapshotKpisCommand::class, [ 'interval' => KpiInterval::Day, ])->daily();
Manual Snapshot
You can manually snapshot a KPI using the snapshot
method:
use App\Kpis\Users\UsersCountKpi; UsersCountKpi::snapshot( date: now() );
3. Seeding KPIs
Seeding with the Command
When adding KPIs to an existing project, you may want to seed past data. If your KpiDefinition
class supports the date
property, you can seed KPIs using the following command:
php artisan kpis:seed "one year ago" "now"
Manual Seeding
You can also seed KPIs manually using the seed
method:
use App\Kpis\Users\UsersCountKpi; UsersCountKpi::seed( from: now()->subYear(), to: now(), interval: KpiInterval::Day );
4. Querying KPIs
To visualize KPIs in charts or dashboards, the KpiDefinition
class provides several helper methods:
use App\Kpis\Users\UsersCountKpi; /** * Retrieve a collection of KPIs for a given period, keyed by date. */ UsersCountKpi::getPeriod( start: now()->subDays(6), end: now(), interval: KpiInterval::Day ); /** * Retrieve a collection of relative KPIs (i.e., the difference between consecutive snapshots). */ UsersCountKpi::getDiffPeriod( start: now()->subDays(6), end: now(), interval: KpiInterval::Day );
5. Aggregating KPIs
You can easily aggregate KPIs using the following methods:
/** * Retrieve the KPI with the maximum value for each month. */ UsersCountKpi::max( start: now()->subMonths(6), end: now(), interval: KpiInterval::Month ); UsersCountKpi::min(...); UsersCountKpi::avg(...); UsersCountKpi::sum(...); UsersCountKpi::count(...);
Testing
Run the tests with:
composer test
Changelog
For recent changes, see the CHANGELOG.
Contributing
For contribution guidelines, see CONTRIBUTING.
Security Vulnerabilities
For details on reporting security vulnerabilities, review our security policy.
Credits
License
The MIT License (MIT). Please see License File for more information.