Skip to content

ModelEvent ​

Unusualify\Modularous\Events\ModelEvent

File: src/Events/ModelEvent.php

Abstract base class for all model-level events. Extend this class to create events that fire when a model is created, updated, or deleted. It wires up broadcasting support via Laravel Reverb and automatically populates contextual data through four traits.

Class Signature ​

php
abstract class ModelEvent
{
    use EventUrls, EventChanges, EventStateable, EventUser;

    public string $modelType;
    public string $broadcastService = 'reverb';

    public function __construct(public $model, public $serializedData = null)
}

Properties ​

PropertyTypeDescription
$modelmixedThe Eloquent model instance that triggered the event
$serializedDatamixed|nullOptional pre-serialized payload (used by notification classes)
$modelTypestringFully-qualified class name of $model
$broadcastServicestringBroadcast driver; defaults to 'reverb'

Constructor ​

php
new OrderShipped($order);                    // model only
new OrderShipped($order, $serializedData);   // with pre-serialized payload

The constructor:

  1. Captures modelType = get_class($model).
  2. Runs the four setup*() methods from the composed traits (user, URLs, changes, stateable).
  3. If the event uses InteractsWithBroadcasting, calls $this->broadcastVia($this->broadcastService).

Defaults You Get For Free ​

MethodDefault returnOverride when
broadcastOn()[PrivateChannel('models.{id}'), Channel('model')]You need custom/per-user channels
broadcastAs()'modularous.' . snake-dot class nameYou want a stable wire name
broadcastWhen()trueYou want conditional broadcasting
$broadcastService'reverb'You want Pusher/Ably per event

Broadcasting ​

ModelEvent implements Laravel's ShouldBroadcast contract via InteractsWithBroadcasting. When the using class includes InteractsWithBroadcasting, the driver is set to $broadcastService during construction.

Channels ​

ChannelTypePattern
models.{model_id}PrivatePer-model updates
modelPublicAll model updates

Event Name ​

The broadcast event name follows the convention:

modularous.{snake_case_event_without_event_suffix}

For example, a class named UserCreatedEvent broadcasts as modularous.user.created.

This is resolved by:

php
public function broadcastAs(): string
{
    return 'modularous.' . Str::replace('_', '.', 
        Str::replace('_event', '', Str::snake(get_class_short_name($this)))
    );
}

Overriding broadcastWhen ​

php
class OrderShipped extends ModelEvent implements ShouldBroadcast
{
    use InteractsWithBroadcasting;

    public function broadcastWhen(): bool
    {
        return $this->wasChanged('status') && $this->model->status === 'shipped';
    }
}

Event Traits ​

Each trait is set up inside the constructor before broadcasting is configured. See the individual trait pages for full property/method references and usage examples.

TraitWhat it capturesPage
EventUserAuthenticated user at fire timeEventUser →
EventUrlsCurrent and previous HTTP URLsEventUrls →
EventChangesDirty attributes and changed relationshipsEventChanges →
EventStateableState machine transition detailsEventStateable →

Extending ModelEvent ​

php
use Unusualify\Modularous\Events\ModelEvent;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Broadcasting\InteractsWithBroadcasting;

class OrderShipped extends ModelEvent implements ShouldBroadcast
{
    use InteractsWithBroadcasting;
}

Dispatching the event:

php
event(new OrderShipped($order));

// With serialized payload for notification classes:
event(new OrderShipped($order, $serializedData));

Listening in your module's EventServiceProvider:

php
protected $listen = [
    OrderShipped::class => [
        OrderShippedListener::class,
    ],
];

Checking Changes in a Listener ​

php
public function handle(OrderShipped $event): void
{
    // Attribute / relationship change (EventChanges)
    if ($event->wasChanged('status')) {
        // status changed on this save
    }

    // State machine transition (EventStateable)
    if ($event->hasStateable && $event->stateableChanged) {
        $from = $event->previousStateableState;
        $to   = $event->currentStateableState;
    }

    // Who triggered it (EventUser)
    if ($event->hasUser()) {
        $actor = $event->getUser();
    }
}

See Event Traits for the full reference on each trait.

Broadcast Payload Shape ​

Because all four trait properties are public, they are automatically serialized into the broadcast payload. Clients listening on .modularous.* events receive an object with:

jsonc
{
  "model":        { /* full model, per broadcastWith() or default */ },
  "modelType":    "App\\Models\\Order",
  "user":         { /* auth user or null */ },
  "recentUrl":    "https://app.example.com/admin/orders/42",
  "previousUrl":  "https://app.example.com/admin/orders",
  "changedAttributes":    { "status": "shipped" },
  "changedRelationships": {},
  "hasStateable":           true,
  "stateableChanged":       true,
  "previousStateableState": "paid",
  "currentStateableState":  "shipped"
}

Use broadcastWith() on your subclass to trim or reshape the payload.

php
public function broadcastWith(): array
{
    return [
        'id'       => $this->model->id,
        'status'   => $this->currentStateableState,
        'actor'    => $this->user?->name,
    ];
}

Subclassing Checklist ​

When creating a new broadcast event:

  • [ ] Extend Unusualify\Modularous\Events\ModelEvent
  • [ ] Implement Illuminate\Contracts\Broadcasting\ShouldBroadcast
  • [ ] Use Illuminate\Broadcasting\InteractsWithBroadcasting
  • [ ] (Optional) Override broadcastOn() for custom channels
  • [ ] (Optional) Override broadcastWhen() for conditional dispatch
  • [ ] (Optional) Override broadcastWith() to shape the payload
  • [ ] Register a channel authorization in routes/channels.php for private channels