---
title: "Extending — Metrics for Laravel | Roundly"
description: "Add trend support for another database with a QueryExpression, and feed metrics from any package’s domain events."
url: https://roundly-consulting.com/open-source/docs/metrics-for-laravel/extending
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Metrics for Laravel](https://roundly-consulting.com/open-source/docs/metrics-for-laravel.md)

# Extending

Trend bucketing SQL is produced per database driver by a QueryExpression:

```php
namespace RoundlyConsulting\Metrics\Types\Trend\QueryExpressions;

interface QueryExpression
{
    // SQL that formats $column into the unit's bucket key
    public function toSql(string $unit, string $column): string;

    // SQL that moves the datetime $column by $minutes — re-reads it on the reporting clock
    public function addMinutes(string $column, int $minutes): string;
}
```

toSql() formats a column into the unit’s bucket key; addMinutes() shifts a column onto the reporting clock (see Timezones). To support another driver, implement both and map the class in config('metrics.trend\_drivers') under the connection’s driver name — at runtime, config(\['metrics.trend\_drivers.oracle' => OracleExpression::class\]). The config map is the only driver registry, used by class-based trends and Metrics::trend() alike:

```php
namespace App\Metrics;

use RoundlyConsulting\Metrics\Exceptions\InvalidUnitException;
use RoundlyConsulting\Metrics\Types\Trend\QueryExpressions\QueryExpression;

final class SqlServerExpression implements QueryExpression
{
    public function toSql(string $unit, string $column): string
    {
        return match ($unit) {
            'DAY' => "CONVERT(char(10), {$column}, 23)",   // Y-m-d
            'MONTH' => "CONVERT(char(7), {$column}, 23)",  // Y-m
            'YEAR' => "CONVERT(char(4), {$column}, 23)",   // Y
            default => throw InvalidUnitException::for($unit),
        };
    }

    public function addMinutes(string $column, int $minutes): string
    {
        return "DATEADD(minute, {$minutes}, {$column})";
    }
}
```

```php
// config/metrics.php
'trend_drivers' => [
    'mysql' => Mysql::class,
    'mariadb' => Mysql::class,
    'pgsql' => Postgres::class,
    'sqlite' => Sqlite::class,
    'sqlsrv' => App\Metrics\SqlServerExpression::class,
],
```

$unit is the Unit value (MINUTE … YEAR) and $column the qualified date column, already quoted by the connection’s query grammar ("orders"."created\_at") — or, when the reporting timezone differs from the app timezone, an expression built from your addMinutes() over it. The keys your SQL emits must match the package’s PHP-side bucket keys character for character (Y-m-d for days, Y-m for months, o-W for ISO weeks — see Trends): gap filling looks each generated bucket up in the query result, and a mismatch silently reports 0. Throw InvalidUnitException for units you don’t support, as the built-in expressions do.

## Custom windows

The built-in periods resolve through the Period enum. For a bespoke window, use a CUSTOM range with an explicit start and end: range(Period::Custom, $start, $end).

## Recipe: an event → metric sink

Metrics read from your existing tables, so any other package’s domain events can feed a metric with no extra dependency — this is host wiring, not a package require. Point a metric at the model the event writes, or record a lightweight counter and count that:

```php
use App\Models\Order;
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Metrics\Facades\Metrics;

// Register a metric over whatever table the events already populate.
Metrics::register('orders_today', fn () => Metrics::value()->count(Order::query())->range('TODAY'));

// Or fan a package's event into your own metrics table, then build a metric over it.
Event::listen(OrderPlaced::class, function (OrderPlaced $event): void {
    MetricEvent::create(['name' => 'order_placed', 'occurred_at' => now()]);
});

Metrics::register('orders_placed', fn () => Metrics::trend()
    ->count(MetricEvent::query()->where('name', 'order_placed'), 'occurred_at')
    ->daily());
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/metrics-for-laravel.md)
