FleetbaseFleetbase

Expansions & Observers

React to model lifecycle events with observers, and add methods to core models or Laravel facades with expansions — both auto-loaded by your service provider.

Expansions & Observers

Two patterns let your extension hook into other code without owning it: observers for model lifecycle events, and expansions for adding methods to a class you don't own.

Observers

Observers run extension code in response to Eloquent lifecycle events on a model. Use them to enforce invariants, dispatch jobs, send notifications, or sync data with external services.

Defining an Observer

<?php

namespace MyOrg\MyExtension\Observers;

use MyOrg\MyExtension\Models\Widget;
use MyOrg\MyExtension\Notifications\WidgetCreated;

class WidgetObserver
{
    public function creating(Widget $widget): void
    {
        // Last-minute defaults — fires before INSERT
        if (!$widget->status) {
            $widget->status = 'active';
        }
    }

    public function created(Widget $widget): void
    {
        $widget->company->notify(new WidgetCreated($widget));
    }

    public function updating(Widget $widget): void
    {
        if ($widget->isDirty('status')) {
            // …
        }
    }

    public function deleted(Widget $widget): void
    {
        // …
    }
}

Standard Eloquent events: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored, forceDeleted. Each method receives the model instance.

Real example: storefront/server/src/Observers/OrderObserver.php.

Registering Observers

CoreServiceProvider reads the $observers array on your service provider and calls Model::observe() for each pair when you call $this->registerObservers():

// server/src/Providers/MyExtensionServiceProvider.php
public $observers = [
    \MyOrg\MyExtension\Models\Widget::class       => \MyOrg\MyExtension\Observers\WidgetObserver::class,

    // You can also observe core models — fires on the same model used by other extensions
    \Fleetbase\Models\User::class                 => \MyOrg\MyExtension\Observers\UserObserver::class,
];

public function boot()
{
    parent::boot();
    $this->registerObservers();
}

The implementation in CoreServiceProvider::registerObservers() skips entries whose model class doesn't exist, so it's safe to declare observers for optional dependencies.

Observing Core Models

You can observe Fleetbase\Models\User, Company, Order, etc. — multiple extensions stack their observers on the same model and all fire. Be defensive: don't assume you're the only listener, and don't throw from non-creating events unless you genuinely want to abort the save.

Expansions

Expansions add methods to a class you don't own — Laravel facades (Route, Str, Arr, Builder, Carbon, Request, Response), or any model that supports Macroable. The Route::fleetbaseRoutes() macro you saw in Routes & Controllers is itself an expansion shipped by core-api.

Defining an Expansion

<?php

namespace MyOrg\MyExtension\Expansions;

use Fleetbase\Build\Expansion;

class StrExpansion implements Expansion
{
    /**
     * The class this expansion targets.
     */
    public static function target()
    {
        return \Illuminate\Support\Str::class;
    }

    /**
     * Each public static method becomes a macro on the target.
     */
    public static function dasherizeUuid()
    {
        return function (string $uuid): string {
            return strtolower(str_replace('_', '-', $uuid));
        };
    }
}

Now Str::dasherizeUuid('ABC_123') works anywhere in the app.

Targeting a Model

To attach behaviour to a core model, target the model class. Each expansion method's returned closure is bound to a model instance — $this inside it is the model:

<?php

namespace MyOrg\MyExtension\Expansions;

use MyOrg\MyExtension\Models\Widget;
use Fleetbase\Build\Expansion;

class UserExpansion implements Expansion
{
    public static function target()
    {
        return \Fleetbase\Models\User::class;
    }

    public static function widgets()
    {
        return function () {
            /** @var \Fleetbase\Models\User $this */
            return $this->hasMany(Widget::class, 'created_by_uuid', 'uuid');
        };
    }
}

After this expansion loads, any User instance has $user->widgets as a real Eloquent relationship — usable in queries, eager loads, and with().

Real example: fleetops/server/src/Expansions/UserExpansion.php — adds driver(), customer(), contact(), and other relationships to the core User model.

Auto-loading Expansions

Drop expansions into server/src/Expansions/ and call registerExpansionsFrom from boot():

// server/src/Providers/MyExtensionServiceProvider.php
public function boot()
{
    parent::boot();
    $this->registerExpansionsFrom(__DIR__ . '/../Expansions');
}

The provider scans the directory, resolves each class through your namespace's Expansions\\, Macros\\, or Mixins\\ folders, and calls Target::expand($expansion) (falling back to Target::mixin($expansion) if the target is a model). Source: CoreServiceProvider::registerExpansionsFrom().

Observers vs. Hooks vs. Webhooks

These three reaction mechanisms work at different layers:

MechanismRuns inUse for
Backend observerPHP — same request as the model saveServer-side invariants, side effects on save (notifications, jobs, syncing)
Frontend hookBrowser — Ember runtimeUI reactions during route transitions, login, etc. See Hook Service
WebhookExternal — HTTP POST after the request commitsThird-party services reacting to platform events. Configured via the Developer Console

Pick the layer that matches the side effect's owner.

When to Reach for Each

  • Need to enforce a rule whenever a model saves? Observer.
  • Need to add a method to a Laravel facade or a core model? Expansion.
  • Need a third party (or your own backend job) to react asynchronously? Webhook + Listener — define a Laravel EventServiceProvider (sibling to your main provider) and register listeners there.

For event service providers, mirror the fleetops/server/src/Providers/EventServiceProvider.php pattern.

Source

FileDescription
src/Build/Expansion.phpThe Expansion interface
src/Providers/CoreServiceProvider.php$observers, registerObservers(), registerExpansionsFrom()
src/Expansions/Route.phpReference expansion — adds fleetbaseRoutes() to Route
storefront/server/src/Observers/OrderObserver.phpReference observer
fleetops/server/src/Expansions/UserExpansion.phpReference model expansion
Expansions & Observers | Fleetbase