PHPackages                             multek/laravel-business-metrics - 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. multek/laravel-business-metrics

ActiveLibrary

multek/laravel-business-metrics
===============================

Server-side business event tracking with flexible reports and analytics schema for Laravel applications.

v1.1.0(1mo ago)00MITPHPPHP ^8.2CI passing

Since Feb 10Pushed 1mo agoCompare

[ Source](https://github.com/Multek-Company/laravel-business-metrics)[ Packagist](https://packagist.org/packages/multek/laravel-business-metrics)[ RSS](/packages/multek-laravel-business-metrics/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (10)Versions (5)Used By (0)

Laravel Business Metrics
========================

[](#laravel-business-metrics)

Server-side business event tracking with flexible report generation for Laravel + PostgreSQL. Get real-time dashboards in Grafana and exploratory BI in Metabase — without streaming infrastructure.

Architecture
------------

[](#architecture)

```
App (Laravel) → public.business_events (append-only, transactional)
     ↓
Scheduler (every minute)
     └── for each report whose cron matches:
           dispatch(ProcessReportJob) → Queue Worker
                                         ├── runs report SQL (INSERT ... ON CONFLICT)
                                         ├── writes to analytics.* tables
                                         ├── ShouldBeUnique (no duplicate runs)
                                         └── retries on failure (3 attempts)
     ↓
Grafana/Metabase → reads analytics.* (read-only user)

```

**Design principles:**

- `public` schema = transactional writes (app)
- `analytics` schema = read-only aggregations (dashboards)
- Events are append-only (auditable, no data loss)
- Reports use `INSERT ... ON CONFLICT DO UPDATE` (incremental upsert, idempotent)
- Each report is a PHP class with full SQL control — no rigid schema
- Reports run as queued jobs — visible in Horizon, with automatic retries

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11 or 12
- PostgreSQL 14+

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

[](#installation)

```
composer require multek/laravel-business-metrics
```

Publish config and enum stub:

```
php artisan vendor:publish --tag=business-metrics-config
php artisan vendor:publish --tag=business-metrics-enum
```

Run migrations and create analytics schema:

```
php artisan migrate
php artisan business-metrics:create-schema
```

Configuration
-------------

[](#configuration)

Edit `config/business-metrics.php`:

### 1. Register your events

[](#1-register-your-events)

**Option A — Enum (recommended):**

Edit `app/Enums/BusinessEventType.php` and uncomment/add your events:

```
enum BusinessEventType: string
{
    case UserSignedUp = 'user_signed_up';
    case RfqCreated = 'rfq_created';
    case OrderPaid = 'order_paid';
    // ...
}
```

Then point config to it:

```
'events' => \App\Enums\BusinessEventType::class,
```

**Option B — Array:**

```
'events' => [
    'user_signed_up',
    'rfq_created',
    'order_paid',
],
```

### 2. Create reports

[](#2-create-reports)

Each report is a PHP class that defines its own SQL, output table, and schedule. You get full SQL control — compute activation rates, cohort retention, revenue breakdowns, or anything that needs custom joins and data from any table.

```
php artisan make:report ActivationRate
```

This generates `app/Reports/ActivationRateReport.php`:

```
use Multek\BusinessMetrics\Reports\BusinessReport;

class ActivationRateReport extends BusinessReport
{
    public function table(): string
    {
        return 'analytics.activation_rate';
    }

    public function schema(): string
    {
        return  [
    \App\Reports\ActivationRateReport::class,
],
```

### 4. Create tables

[](#4-create-tables)

```
php artisan business-metrics:create-schema   # creates analytics.* tables from each report's schema()
```

### 5. How scheduling works (zero-config)

[](#5-how-scheduling-works-zero-config)

**You don't need to register anything in your scheduler.** The package handles it automatically.

When you register a report in config and define its `schedule()` cron expression, the package's `ServiceProvider` automatically registers it with Laravel's scheduler:

```
// This happens inside the package — you don't write this code:
$schedule->job(new ProcessReportJob($reportClass))
    ->cron($report->schedule())      // uses YOUR cron from schedule()
    ->withoutOverlapping();
```

So the full flow is:

1. You create a report class with `schedule()` returning a cron expression (e.g. `'0 */6 * * *'`)
2. You register it in `config/business-metrics.php` → `'reports'` array
3. The package reads all reports on boot, and for each one registers a scheduled job with that cron
4. Laravel's scheduler (`php artisan schedule:run`, which your server runs every minute via crontab) checks the cron and dispatches `ProcessReportJob` to the queue when it matches
5. Your queue worker picks up the job, runs the report SQL, done

**The only thing you need on your server** is the standard Laravel crontab entry (you probably already have this):

```
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

```

And a queue worker running:

```
php artisan queue:work
```

### 6. Queue configuration (optional)

[](#6-queue-configuration-optional)

Configure the queue name for report jobs in `.env`:

```
BUSINESS_METRICS_REPORTS_QUEUE=analytics

```

Or leave it unset to use the default queue. Report jobs are visible in Horizon, retry automatically (3 attempts, 60s backoff), and won't run concurrently for the same report (`ShouldBeUnique`).

### 7. Run reports manually

[](#7-run-reports-manually)

For debugging or one-off runs:

```
php artisan business-metrics:reports --sync                              # run all now (sync)
php artisan business-metrics:reports --report=ActivationRateReport --sync # run one (sync)
php artisan business-metrics:reports                                      # dispatch all to queue
php artisan business-metrics:reports --report=ActivationRateReport        # dispatch one to queue
```

Usage
-----

[](#usage)

### Logging events

[](#logging-events)

**Via Facade:**

```
use Multek\BusinessMetrics\Facades\BusinessEvent;

// Simple event
BusinessEvent::log('user_signed_up', actorUserId: $user->id);

// Event with context
BusinessEvent::log(
    eventName: 'order_paid',
    properties: ['value' => 15000.00, 'currency' => 'BRL', 'payment_method' => 'pix'],
    actorUserId: auth()->id(),
    companyId: $order->company_id,
    entityType: 'order',
    entityId: $order->id,
);

// Within a DB transaction (guarantees consistency)
DB::transaction(function () use ($order) {
    $order->update(['status' => 'paid']);

    BusinessEvent::logInTransaction(
        eventName: 'order_paid',
        properties: ['value' => $order->total],
        companyId: $order->company_id,
        entityType: 'order',
        entityId: $order->id,
    );
});
```

### Via Trait on Models

[](#via-trait-on-models)

```
use Multek\BusinessMetrics\Traits\HasBusinessEvents;

class Order extends Model
{
    use HasBusinessEvents;

    protected string $businessEntityType = 'order';

    protected function businessEventProperties(): array
    {
        return [
            'value' => $this->total,
            'currency' => 'BRL',
        ];
    }
}

// Then anywhere in your code:
$order->emitBusinessEvent('order_created');
$order->emitBusinessEvent('order_paid', ['payment_method' => 'pix']);

// Within a transaction:
$order->emitBusinessEventInTransaction('order_paid');
```

### Async events (queue)

[](#async-events-queue)

Set in `.env`:

```
BUSINESS_METRICS_QUEUE=analytics

```

Events will be dispatched to the queue instead of writing synchronously. Use sync (`null`) for critical events like payments.

Artisan Commands
----------------

[](#artisan-commands)

```
# Create analytics schema and report tables
php artisan business-metrics:create-schema

# Generate a new report class
php artisan make:report ActivationRate

# Dispatch all reports to queue
php artisan business-metrics:reports

# Dispatch a specific report to queue
php artisan business-metrics:reports --report=ActivationRateReport

# Run all reports synchronously (useful for debugging)
php artisan business-metrics:reports --sync

# Run a specific report synchronously
php artisan business-metrics:reports --report=ActivationRateReport --sync

# List registered events
php artisan business-metrics:events

# Prune old events
php artisan business-metrics:prune
php artisan business-metrics:prune --days=90
```

BusinessReport API
------------------

[](#businessreport-api)

Each report extends `Multek\BusinessMetrics\Reports\BusinessReport`:

MethodRequiredDescription`table(): string`YesTarget table name (e.g. `analytics.activation_rate`)`schema(): string`Yes`CREATE TABLE IF NOT EXISTS` SQL`query(): string`Yes`INSERT INTO ... SELECT ... ON CONFLICT` SQL`schedule(): string`YesCron expression for scheduling`retentionDays(): ?int`NoDays to keep rows (`null` = keep forever)`retentionColumn(): string`NoColumn for pruning (default: `updated_at`)`connection(): ?string`NoDB connection (default: package connection)Grafana Queries (Examples)
--------------------------

[](#grafana-queries-examples)

Since reports produce custom tables, your Grafana queries match your report schema:

```
-- Activation rate over time
SELECT cohort_week AS time, activation_rate
FROM analytics.activation_rate
ORDER BY cohort_week DESC
LIMIT 12
```

Connecting Dashboards
---------------------

[](#connecting-dashboards)

### Grafana

[](#grafana)

1. Add PostgreSQL data source pointing to your DB (use read-only user)
2. Grant permissions:

```
CREATE USER grafana_ro WITH PASSWORD 'secure_password';
GRANT USAGE ON SCHEMA analytics TO grafana_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO grafana_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics GRANT SELECT ON TABLES TO grafana_ro;
```

3. Create dashboards querying `analytics.*` tables

### Metabase

[](#metabase)

Same read-only user. Point Metabase to the same DB, it will discover both `public` and `analytics` schemas for ad-hoc exploration.

Scaling Roadmap
---------------

[](#scaling-roadmap)

PhaseTriggerAction**Now**Starting outPostgres + analytics schema + Grafana**Phase 1**Dashboard queries slow down primaryAdd read replica, point dashboards there**Phase 2**Complex joins, multiple sourcesIntroduce BigQuery + dbt**Phase 3**Real-time automation needsAdd Pub/Sub or KafkaThe package is designed so that when you move to a warehouse, you replicate the same `business_events` table and report structure — no redesign needed.

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance91

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~50 days

Total

2

Last Release

42d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/532197fb4915f98f7958d544b920ef85339270b9010632f3704ecee763310724?d=identicon)[rodrigocoliveira](/maintainers/rodrigocoliveira)

---

Top Contributors

[![rodrigocoliveira](https://avatars.githubusercontent.com/u/8976719?v=4)](https://github.com/rodrigocoliveira "rodrigocoliveira (7 commits)")

---

Tags

laravelanalyticsBIreportsmetabasegrafanabusiness-events

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/multek-laravel-business-metrics/health.svg)

```
[![Health](https://phpackages.com/badges/multek-laravel-business-metrics/health.svg)](https://phpackages.com/packages/multek-laravel-business-metrics)
```

###  Alternatives

[barryvdh/laravel-ide-helper

Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.

14.9k123.0M687](/packages/barryvdh-laravel-ide-helper)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[spatie/laravel-health

Monitor the health of a Laravel application

85810.0M83](/packages/spatie-laravel-health)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

423715.4k1](/packages/clickbar-laravel-magellan)[yadahan/laravel-authentication-log

Laravel Authentication Log provides authentication logger and notification for Laravel.

416632.8k5](/packages/yadahan-laravel-authentication-log)[fumeapp/modeltyper

Generate TypeScript interfaces from Laravel Models

196277.9k](/packages/fumeapp-modeltyper)

PHPackages © 2026

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