Installation
composer require fico7489/laravel-pivot
Enable Events
Use the PivotEventTrait in your base model (recommended) or specific models:
use Fico7489\Laravel\Pivot\Traits\PivotEventTrait;
use Illuminate\Database\Eloquent\Model;
abstract class BaseModel extends Model
{
use PivotEventTrait;
}
First Use Case
Listen for pivot events in a ModelObserver or service provider:
// app/Observers/UserObserver.php
public function pivotAttached($model, $relation, $pivot)
{
Log::info("User {$model->id} attached to {$relation} via pivot {$pivot->id}");
}
AppServiceProvider@boot():
User::observe(UserObserver::class);
Syncing Relationships with Events
$user->roles()->sync([1, 2, 3]); // Triggers pivotAttaching/pivotAttached
pivotAttaching → sync logic → pivotAttached (for new attachments).
pivotDetaching → sync logic → pivotDetached (for removals).Detaching with Granular Control
$user->roles()->detach(3); // Triggers pivotDetaching/pivotDetached
Updating Pivot Data
$user->roles()->updateExistingPivot(1, ['expires_at' => now()]);
// Triggers pivotUpdating/pivotUpdated
Dynamic Event Handling
// Listen to all pivot events dynamically
$model->pivotEvents()->each(function ($event) {
Log::channel('pivot')->info($event);
});
// app/Providers/EventServiceProvider.php
protected $listen = [
'pivotAttached' => [
'App\Listeners\LogPivotAttachment',
],
];
public function pivotAttached($model, $relation, $pivot)
{
cache()->forget("user_{$model->id}_$relation");
}
$this->expectsEvents(PivotAttached::class)
->when($user->roles()->attach(1));
Trait Placement
PivotEventTrait in models breaks event dispatching.Event Order Confusion
pivotAttached fires after sync() completes (it does, but pivotAttaching fires before).pivotAttaching for pre-validation logic (e.g., permissions).Laravel 5.5+ Only
laravel-5.4 branch if necessary (check releases).Custom Pivot Tables
user_role instead of role_user).belongsToMany definition matches the actual pivot table name.Event Not Firing?
composer dump-autoload).dd($model->pivotEvents()) to inspect available events.pivotAttached vs. pivot_attached).Performance Impact
$model->pivotEvents(false);
$model->roles()->sync([...]); // No events
$model->pivotEvents(true); // Re-enable
Custom Events Extend the trait to add domain-specific events:
// app/Traits/CustomPivotEvents.php
public static function bootCustomPivotEvents()
{
static::addPivotEvent('pivotApproving', 'pivotApproved');
}
Pivot Data Validation
Hook into pivotUpdating to validate pivot attributes:
public function pivotUpdating($model, $relation, $pivot, $attributes)
{
if ($attributes['expires_at'] < now()) {
throw new \Exception('Invalid expiration date');
}
}
Real-Time Updates Pair with Laravel Echo/Pusher for live notifications:
public function pivotAttached($model, $relation, $pivot)
{
broadcast(new PivotAttached($model, $relation, $pivot));
}
Database Transactions Wrap pivot operations in transactions to ensure data consistency:
DB::transaction(function () use ($user) {
$user->roles()->sync([1, 2]);
});
How can I help you explore Laravel packages today?