---
title: "Metric classes — Metrics for Laravel | Roundly"
description: "Define reusable Value, Trend, Progress and Partition classes with calculate() and setup(), plus the fluent API every metric shares."
url: https://roundly-consulting.com/open-source/docs/metrics-for-laravel/metric-classes
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)

# Metric classes

For metrics you reuse across a dashboard, extend a base type and implement calculate(), which returns the matching result object:

| Type | Extend | calculate() returns | Good for |
| --- | --- | --- | --- |
| Value | `RoundlyConsulting\Metrics\Types\Value\Value` | `ValueResult` | A single number, optionally vs. the previous period |
| Trend | `RoundlyConsulting\Metrics\Types\Trend\Trend` | `TrendResult` | A time series (line/area chart) |
| Progress | `RoundlyConsulting\Metrics\Types\Progress\Progress` | `ProgressResult` | Current value vs. a target (progress bar) |
| Partition | `RoundlyConsulting\Metrics\Types\Partition\Partition` | `PartitionResult` | A grouped breakdown (pie/bar chart) |

## Value

```php
use App\Models\User;
use RoundlyConsulting\Metrics\Types\Result;
use RoundlyConsulting\Metrics\Types\Value\Value;

final class RegisteredUsers extends Value
{
    protected function calculate(): Result
    {
        return $this->count(User::query());
    }
}

RegisteredUsers::make()->range('TODAY')->toArray();
```

## Trend

```php
use App\Models\User;
use RoundlyConsulting\Metrics\Types\Result;
use RoundlyConsulting\Metrics\Types\Trend\Trend;

final class RegisteredUsersTrend extends Trend
{
    protected function calculate(): Result
    {
        return $this->count(User::query(), 'created_at');
    }
}

RegisteredUsersTrend::make()->hourly()->range('TODAY')->toArray();
```

## Progress

A Progress metric compares the current value to a target (default 100). Configure its defaults once in setup():

```php
use App\Models\Subscription;
use RoundlyConsulting\Metrics\Types\Progress\Progress;
use RoundlyConsulting\Metrics\Types\Result;

final class MonthlyRevenueGoal extends Progress
{
    protected function setup(): void
    {
        $this->target(10000)->range('MTD');
    }

    protected function calculate(): Result
    {
        return $this->sum(Subscription::query(), 'amount');
    }
}
```

Use shouldBeAvoided() when the target is a ceiling to stay under — an error budget or a spend cap — rather than a goal to reach. It surfaces as avoid in the result so the UI can colour it accordingly:

```php
use App\Models\Incident;
use RoundlyConsulting\Metrics\Types\Progress\Progress;
use RoundlyConsulting\Metrics\Types\Result;

final class ErrorBudget extends Progress
{
    protected function setup(): void
    {
        $this->target(50)->shouldBeAvoided()->range('MTD');   // stay under 50 this month
    }

    protected function calculate(): Result
    {
        return $this->count(Incident::query());
    }
}

// result => [..., 'target' => 50.0, 'avoid' => true, ...]
```

## Partition

```php
use App\Models\User;
use RoundlyConsulting\Metrics\Types\Partition\Partition;
use RoundlyConsulting\Metrics\Types\Result;

final class UsersByPlan extends Partition
{
    protected function calculate(): Result
    {
        // count of users grouped by the `plan` column
        return $this->count(User::query(), 'plan');
    }
}
```

## Defaults in setup()

setup() runs in the constructor, after the config defaults. Configure presentation and behaviour there once instead of at every call site — fluent calls on the instance still win:

```php
use App\Models\Order;
use RoundlyConsulting\Metrics\Enums\Period;
use RoundlyConsulting\Metrics\Types\Result;
use RoundlyConsulting\Metrics\Types\Value\Value;

final class Revenue extends Value
{
    protected function setup(): void
    {
        $this->withChangeAgainstPreviousPeriod();

        $this->name('Revenue')
            ->description('Paid orders in the selected period')
            ->prefix('$')
            ->precision(2)
            ->range(Period::MonthToDate);
    }

    protected function calculate(): Result
    {
        return $this->sum(Order::query()->where('status', 'paid'), 'total');
    }
}

// setup() runs in the constructor, so call-site fluent calls still win:
Revenue::make()->range(Period::LastMonth)->toArray();
```

## Shared fluent API

Every metric — class-based or inline — extends the abstract RoundlyConsulting\\Metrics\\Metric class and exposes:

| Method | Effect |
| --- | --- |
| `name(string $name)` | Display name; defaults to the humanized class name. |
| `description(string $description)` | Free-text description in the envelope. |
| `prefix(string $prefix) / suffix(string $suffix)` | Display prefix and suffix, e.g. a currency sign or a unit. |
| `precision(int $precision = 0, RoundingMode $mode = RoundingMode::HalfAwayFromZero)` | Rounding precision and mode for every value. |
| `range(Period\|string $range, ?string $customRangeStart = null, ?string $customRangeEnd = null)` | Date range — see Date ranges. |
| `timezone(?string $timezone)` | Resolve this metric’s ranges and label its trend buckets in an explicit timezone — see Date ranges. |
| `ranges(): array` | The selectable range keys and labels. |
| `formatUsing(Closure $formatter)` | Add a formatted value to the result — see Number formatting. |
| `cache(), cacheFor(), cacheKey(), dontCache()` | Per-metric caching — see Caching. |
| `key(): ?string` | The registry key it was resolved under, or null for ad-hoc metrics. |
| `make(mixed ...$arguments): static` | Static constructor. |
| `result(): Result` | The typed result object — calculated, or restored from the result cache. |
| `toArray(): array` | Resolve and return the envelope. |
| `toResponse($request): JsonResponse` | The envelope as a JSON response (Responsable). |

precision() takes PHP’s native RoundingMode enum, e.g. ->precision(2, RoundingMode::HalfEven). By default a metric’s name is the humanized class name (RegisteredUsers → “Registered Users”).

## 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)
