Installation
composer require jpkleemans/attribute-events
No publisher or service provider needed—just add the trait to your models.
First Use Case Define attribute-based events in your model:
use Jpkleemans\AttributeEvents\AttributeEvents;
class Order extends Model
{
use AttributeEvents;
protected $dispatchesEvents = [
'status:shipped' => OrderShipped::class,
'note:*' => OrderNoteChanged::class,
];
}
attribute:new_value triggers when the attribute changes to new_value.*) allow catching any change to an attribute (e.g., note:*).Trigger Events
$order = new Order();
$order->status = 'shipped'; // Dispatches `OrderShipped` event
$order->save();
Define Events
Use $dispatchesEvents to map attribute changes to event classes:
protected $dispatchesEvents = [
'status:pending' => OrderStatusPending::class,
'status:cancelled' => OrderCancelled::class,
'priority:high' => HighPriorityOrder::class,
];
status changes) for clarity.Listen to Events
Register listeners in EventServiceProvider:
protected $listen = [
OrderShipped::class => [
SendShipmentNotification::class,
],
OrderNoteChanged::class => [
LogNoteChange::class,
],
];
Access Old/New Values
Events receive $model, oldValue, and newValue:
public function handle(OrderNoteChanged $event)
{
Log::info("Note changed from {$event->oldValue} to {$event->newValue}");
}
Dynamic Event Mapping
Override getDispatchesEvents() to conditionally define events:
public function getDispatchesEvents()
{
if ($this->isPremium()) {
return array_merge(parent::getDispatchesEvents(), [
'status:premium' => PremiumOrderStatusChanged::class,
]);
}
return parent::getDispatchesEvents();
}
Mass Assignment Handling
Use fill() or update() carefully—events fire after assignment but before saving:
$order->fill(['status' => 'shipped']); // Event fires before `save()`
$order->save();
Event Payload Customization Extend events to include additional data:
class OrderShipped extends Event
{
public function __construct(
public Order $order,
public string $oldStatus,
public string $newStatus,
public ?User $shippedBy
) {}
}
Bulk Operations Disable events during bulk updates:
Order::where('status', 'pending')->update(['status' => 'shipped']);
// No events fired. Use manual dispatch if needed:
Order::where('status', 'pending')->get()->each(fn ($order) => $order->dispatchEvents());
Event Timing
saving or updating model events instead.Wildcard Overlap
note:* will catch all changes to note, including those from note:urgent. Order rules by specificity (e.g., note:urgent before note:*).Case Sensitivity
// Fails: 'Status:SHIPPED' ≠'status:shipped'
protected $dispatchesEvents = ['Status:SHIPPED' => ...];
Circular Dependencies
status:shipped → updates shipped_at → triggers another event).Testing Quirks
shouldDispatchEvent():
$order->shouldDispatchEvent(OrderShipped::class, false);
Log Events Temporarily add a listener to debug:
Event::listen(OrderShipped::class, function ($event) {
Log::debug('Event triggered', ['old' => $event->oldValue, 'new' => $event->newValue]);
});
Check Dispatch Order
Use dd() in event handlers to inspect the call stack:
public function handle(OrderShipped $event) {
dd(debug_backtrace()); // Trace where the event was fired
}
Custom Event Resolvers
Override resolveEventClass() to dynamically resolve event classes:
protected function resolveEventClass(string $eventName): string
{
if (str_contains($eventName, 'priority:')) {
return PriorityChanged::class;
}
return parent::resolveEventClass($eventName);
}
Event Payload Modification
Extend the base AttributeEvent class to add metadata:
class CustomAttributeEvent extends AttributeEvent
{
public function __construct(
Model $model,
string $attribute,
$oldValue,
$newValue,
public array $extraData = []
) {
parent::__construct($model, $attribute, $oldValue, $newValue);
}
}
Performance Optimization Disable events for non-critical models:
class Product extends Model
{
use AttributeEvents;
protected $dispatchesEvents = []; // No events
}
Queue Events Dispatch events to queues for async processing:
Event::dispatch(new OrderShipped($order, $oldStatus, $newStatus))
->onQueue('orders');
How can I help you explore Laravel packages today?