PHPackages                             finller/laravel-kpi - 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. finller/laravel-kpi

AbandonedArchivedLibrary

finller/laravel-kpi
===================

This is my package laravel-kpi

v2.0.15(2y ago)87.8k3MITPHPPHP ^8.2

Since Dec 10Pushed 1y agoCompare

[ Source](https://github.com/finller/laravel-kpi)[ Packagist](https://packagist.org/packages/finller/laravel-kpi)[ Docs](https://github.com/finller/laravel-kpi)[ RSS](/packages/finller-laravel-kpi/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (12)Versions (37)Used By (0)

Store, analyse and retrieve KPI over time in your Laravel App
=============================================================

[](#store-analyse-and-retrieve-kpi-over-time-in-your-laravel-app)

[![Latest Version on Packagist](https://camo.githubusercontent.com/f5605bde6bbda94b313bcaaeca87f40f51d912905c8cd5049b338a916916d9bd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f66696e6c6c65722f6c61726176656c2d6b70692e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/finller/laravel-kpi)[![GitHub Tests Action Status](https://camo.githubusercontent.com/314559eaa77deaaaa700fd9729345fb80d8426e61556c245265bcef11fd8017c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f66696e6c6c65722f6c61726176656c2d6b70692f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/finller/laravel-kpi/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/757196af4c38b362678ae6eac4d4037188b5abfe6011ce7a07f0d2d1a444a785/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f66696e6c6c65722f6c61726176656c2d6b70692f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/finller/laravel-kpi/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/f2b52d5d795f3f3293b4b81a60d69896372d5edd5dc64d11687fe5a8838c1edd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f66696e6c6c65722f6c61726176656c2d6b70692e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/finller/laravel-kpi)

Important

This package has been completely rethought and rewritten. The new and improved version is now available at [this repository](https://github.com/ElegantEngineeringTech/laravel-kpi).

This package provides a way to store kpis from your app in your database and then retreive them easily in different ways. It is espacially usefull to tracks things related to your models like:

- the number of users
- the number of subscribed users
- the total revenue
- ...

It's a perfect tool for building dashboard ans display stats/charts.

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

[](#installation)

You can install the package via composer:

```
composer require finller/laravel-kpi
```

You have to publish and run the migrations with:

```
php artisan vendor:publish --tag="kpi-migrations"
php artisan migrate
```

Usage
-----

[](#usage)

This package is **not a query builder**, it's based on a kpi table where you will store all your kpis. With this approach, your kpis from the past (like the number of users you had a year ago) will not be altered if you permanently delete a model.

Retreiving kpis will also be much more efficient when asking for computed values that often require join like "users who have purchased last week" for example.

### Step 1: Store kpis in you database

[](#step-1-store-kpis-in-you-database)

As said above, you will have to store the kpis you need in the database. Kpis are grouped by `keys` and support different kind of values:

- number (float) under `number_value` column
- string under `string_value` column
- json or array under `json_value` column
- money under `money_value` and `money_currency` column

In most cases, you would store numbers, like the number of users for example. You are free to choose the key but you could do it like that:

```
Kpi::create([
    'key' => 'users:count',
    'number_value' => User::count(),
]);
```

#### Define KPIs for each model

[](#define-kpis-for-each-model)

Generally kpis are related to models, that's why we provid a trait `HasKpi` with a standardized way to name your kpi key `{namespace}:{key}`. For the User model, it would store your key in the `users` namespace like `users:{key}`.

By default the trait only define 1 KPI: `Model::count()`

You can define your own KPIs freely with the method `registerKpis`. Here is an example:

```
namespace App\Models;

use Finller\Kpi\HasKpi;

class User extends Model
{
    use HasKpi;

    /*
     * The date represent the date of the KPI
     * It is usefull when capturing a Kpi from the past
     */
    public static function registerKpis(Carbon $date = null): Collection
    {
        $query = static::query()
            ->when($date, fn (Builder $q) => $q->whereDate('created_at', 'put('active:count', new Kpi([
                'number_value' => $query->clone()->active()->count(),
                'created_at' => $date->clone(),
            ]))
            // You can also manually define the key
            ->push( new Kpi([
                'key' =>
                'number_value' => $query->clone()->subscribed()->count(),
                'created_at' => $date->clone(),
            ]));
    }
}
```

Each item of the collection can either have a key that represent the Kpi key, or define directly the Kpi key.

Notice that, the method accept a `$date` parameter. This allow you to take KPIs snapshot "in the past". This is usefull for already existing project, or simply when you add a new KPI to the list.

### Step 2: Capture the Kpis with a command

[](#step-2-capture-the-kpis-with-a-command)

After registering the kpis you have to save them in the database with the `snapshotKpis` method.

A standard way to save your kpi values would be in a command that runs every day.

Here is an example:

```
namespace App\Console\Commands;

use App\Models\User;

class SnapshotKpisCommand extends Command
{
    protected $signature = 'kpis:snapshot';
    protected $description = 'Snapshot KPI';

    public function handle()
    {
        User::snapshotKpis();
    }
}
```

Then add it to `Kernel`

```
namespace App\Console;

use App\Console\Commands\SnapshotKpiCommand;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{

    protected function schedule(Schedule $schedule)
    {
        $schedule->command(SnapshotKpiCommand::class)->dailyAt('00:00');
    }
}
```

You are free to store as much kpis as needed, even multiple times in a day, so you can get more recent data.

### Step 3: Retreive your kpis

[](#step-3-retreive-your-kpis)

You can retreive kpis by using usefull scopes and the native eloquent Builder methods.

For example, if you want to query kpis under `users:count` key, you could use:

```
// With Kpi model
use Finller\Kpi\Kpi;
Kpi::where('key', "users:count")->get();

// With HasKpi trait
use App\Models\User;
User::kpi('count')->get();

// With KpiBuilder
use Finller\Kpi\KpiBuilder;
KpiBuilder::query("users:count")->get();
KpiBuilder::query(Kpi::query()->where("key", "users:count"))->get();
```

#### Query by date

[](#query-by-date)

In most cases, you would like to have thoses kpis grouped by date (day, month, year, ...).

For example, to get the number of users grouped by day between 2 dates (usefull to draw a chart), you could do:

```
User::kpi('count')
    ->between(start: now(), end: now()->subDays(7))
    ->perDay()
    ->get();
```

As we are grouping by date/period, you could have more than 1 snapshot of the same key for a date/period. In this situation, this package will give you only the most recent snaphot of each date/period.

In the previous example, I would get the most recent count of users for each day. This is also true for other kind of supported intervals.

#### Supported intervals:

[](#supported-intervals)

The supported intervals are: perDay, perWeek, perMonth and perYear.

```
Kpi::query()->perDay()->get();
Kpi::query()->perWeek()->get();
Kpi::query()->perMonth()->get();
Kpi::query()->perYear()->get();
```

#### Fill gaps between dates

[](#fill-gaps-between-dates)

In some cases, you could have missed a snapshot. Let's say that your snapshot kpi command failed or your server was down.

To fill the gaps let by missing values, you can use the `fillGaps` method available on `KpiBuilder` or `KpiCollection`. By default the placeholders will be a copy of their previous kpi.

For convenience the `KpiBuilder` is the best option as it will give you better typed values and shares parameters between `fillGaps` and `between`.

```
use Finller\Kpi\Enums\KpiInterval;

Kpi::query()
    ->where('key', 'users:blocked:count')
    ->between(now()->subWeek(), now())
    ->perDay()
    ->get()
    ->fillGaps( // optional parameters
        start: now()->subWeek(),
        end: now(),
        interval: KpiInterval::Day,
        default: ['number_value' => 0]
    );

KpiBuilder::query('users:blocked:count')
    ->perDay()
    ->between(now()->subWeek(), now())
    ->fillGaps()
    ->get();

Kpi::query()
    ->where('key', 'users:blocked:count')
    ->between(now()->subWeek(), now())
    ->perDay()
    ->get()
    ->fillGaps(); // if you do not specify anything when using KpiCollection, the start, end and the interval values will be guessed from your dataset
```

### Missing Kpis and fill values from the past

[](#missing-kpis-and-fill-values-from-the-past)

#### Find gaps in Kpis

[](#find-gaps-in-kpis)

You can also find gaps in a KpiCollection using `findGaps` method. It can be usefull to snapshot these missing kpi later with something like:

```
$gaps = Model::kpi('count')
    ->perDay()
    ->between(now()->subMonth())
    ->get()
    ->findGaps(interval: KpiInterval::Day, start: now()->subMonth(), end: now());

$gaps->each(function(Carbon $date){
    Model::snapshotKpis($date);
});
```

#### Fill KPIs on a period for existing projects or when adding new KPIs

[](#fill-kpis-on-a-period-for-existing-projects-or-when-adding-new-kpis)

For existing project or when ading a new KPI, you may want to fill your KPIs with data from the past.

The `HasKpi` trait allow you to do it like so:

```
Model::backfillKpis(
    start: now()->subYear(),
    end: now(),
    interval: KpiInterval::Day
);
```

### Transforming Kpi Values

[](#transforming-kpi-values)

#### Get relative values

[](#get-relative-values)

Some Kpis are only relevant when taken relatively to others or to a period.

For example, the number of new registered users by month can be obtained without registering a specific KPIs, but by taking the relative values of the default `User::count()` KPI.

You can easily get your Kpis in a relative format by using different methods:

To go from:

JanFebMar10100500To:

JanFebMar090400```
KpiBuilder::query('users:count')
    ->relative()
    ->perMonth()
    ->between(now()->subYear(), now())
    ->get();

Kpi::query()
    ->where('key', 'users:blocked:count')
    ->between(now()->subYear(), now())
    ->perMonth()
    ->get()
    ->toRelative(); // when using KpiCollection
```

#### Combine multiple Kpis together

[](#combine-multiple-kpis-together)

When a KPI is a direct result of a combination of 2 other KPIs, it might not be worth to save it in the database. You can combine KPIs together to create a new one simply by using the `combineWith` method from the `KpiCollection`.

Here is an example:

```
$usersCount = User::kpi('count')->perDay()->get();

$subscribedUsersCount = User::kpi('subscribed:count')->perDay()->get();

$usersCount->combineWith(
    $subscribedUsersCount,
    fn(Kpi $userCount, Kpi $subscribedUsersCount) => new Kpi([
        ...$userCount->toArray,
        'number_value' => $subscribedUsersCount->number_value / $userCount->number_value
    ])
);
```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Quentin Gabriele](https://github.com/QuentinGab)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance29

Infrequent updates — may be unmaintained

Popularity26

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity70

Established project with proven stability

 Bus Factor1

Top contributor holds 72.3% 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

Every ~13 days

Recently: every ~25 days

Total

35

Last Release

785d ago

Major Versions

v0.2.0 → v1.0.02023-02-11

v1.1.5 → v2.0.02023-12-01

PHP version history (2 changes)v0.1.0PHP ^8.1

v2.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/d769563728757858e249f266ac2555f28db5640124df92821ed17d93ee76fc7b?d=identicon)[QuentinGab](/maintainers/QuentinGab)

---

Top Contributors

[![QuentinGab](https://avatars.githubusercontent.com/u/40128136?v=4)](https://github.com/QuentinGab "QuentinGab (81 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (20 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (10 commits)")[![ImSeaWorld](https://avatars.githubusercontent.com/u/20188588?v=4)](https://github.com/ImSeaWorld "ImSeaWorld (1 commits)")

---

Tags

chartsdatakpilaravelpackagephpstatslaravelfinllerlaravel-kpi

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/finller-laravel-kpi/health.svg)

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

###  Alternatives

[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[spatie/laravel-prometheus

Export Laravel metrics to Prometheus

2651.3M6](/packages/spatie-laravel-prometheus)[hydrat/filament-table-layout-toggle

Filament plugin adding a toggle button to tables, allowing user to switch between Grid and Table layouts.

6292.3k1](/packages/hydrat-filament-table-layout-toggle)[scalar/laravel

Render your OpenAPI-based API reference

6183.9k2](/packages/scalar-laravel)[ralphjsmit/laravel-helpers

A package containing handy helpers for your Laravel-application.

13704.6k2](/packages/ralphjsmit-laravel-helpers)[musahmusah/laravel-multipayment-gateways

A Laravel Package that makes implementation of multiple payment Gateways endpoints and webhooks seamless

852.2k1](/packages/musahmusah-laravel-multipayment-gateways)

PHPackages © 2026

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