Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Pivot Events Laravel Package

mikebronner/laravel-pivot-events

Adds Eloquent model events for many-to-many pivot operations: sync, attach, detach, and updateExistingPivot on BelongsToMany/MorphToMany. Listen for pivotSyncing/Synced, pivotAttaching/Attached, pivotDetaching/Detached, and pivotUpdating/Updated.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require mikebronner/laravel-pivot-events
    

    Ensure your project meets the requirements: Laravel 11.0+ and PHP 8.2+.

  2. Add the Trait: Include GeneaLabs\LaravelPivotEvents\Traits\PivotEventTrait in your base model or specific models:

    use GeneaLabs\LaravelPivotEvents\Traits\PivotEventTrait;
    
    class User extends Model
    {
        use PivotEventTrait;
        // ...
    }
    
  3. First Use Case: Listen to a pivot event in your model’s boot() method. For example, log when a user’s roles are synced:

    public static function boot()
    {
        parent::boot();
    
        static::pivotSynced(function ($model, $relationName, $changes) {
            \Log::info("Roles synced for {$model->id}. Changes: " . json_encode($changes));
        });
    }
    

Where to Look First

  • Documentation: Focus on the README for event names and payload structures.
  • Events List: Refer to the Laravel Eloquent Events for context on how events work.
  • Usage Examples: The README provides clear examples for attach, detach, sync, and updateExistingPivot.

Implementation Patterns

Usage Patterns

  1. Event Listeners in Models: Use the boot() method to register listeners for pivot events. This is the most common pattern:

    public static function boot()
    {
        parent::boot();
    
        static::pivotAttached(function ($model, $relationName, $pivotIds, $pivotIdsAttributes) {
            // Handle attached pivots
        });
    
        static::pivotDetached(function ($model, $relationName, $pivotIds) {
            // Handle detached pivots
        });
    }
    
  2. Global Event Listeners: Register listeners globally using Laravel’s Event facade for cross-model pivot events:

    use Illuminate\Support\Facades\Event;
    
    Event::listen('eloquent.pivotAttached', function ($event) {
        // Handle all pivotAttached events globally
    });
    
  3. Custom Event Classes: For complex logic, create custom event classes implementing GeneaLabs\LaravelPivotEvents\Contracts\ReceivesPivotPayload:

    use GeneaLabs\LaravelPivotEvents\Contracts\ReceivesPivotPayload;
    
    class RoleAssigned implements ReceivesPivotPayload
    {
        public function __construct(public array $payload) {}
    }
    
    // Dispatch in your model:
    Event::dispatch(new RoleAssigned($payload));
    
  4. Conditional Logic: Use the $changes payload in pivotSynced to conditionally trigger actions:

    static::pivotSynced(function ($model, $relationName, $changes) {
        if (!empty($changes['attached'])) {
            // Trigger logic for new attachments
        }
    });
    

Workflows

  1. Audit Logging: Log pivot changes to a database or external service:

    static::pivotUpdated(function ($model, $relationName, $pivotIds, $pivotIdsAttributes) {
        foreach ($pivotIdsAttributes as $id => $attributes) {
            \DB::table('pivot_audit')->insert([
                'model_id' => $model->id,
                'relation' => $relationName,
                'pivot_id' => $id,
                'changes' => json_encode($attributes),
                'created_at' => now(),
            ]);
        }
    });
    
  2. Real-Time Notifications: Use Laravel Echo or similar to notify users of pivot changes:

    static::pivotAttached(function ($model, $relationName, $pivotIds) {
        broadcast(new RoleAssignedEvent($model, $relationName, $pivotIds))->toOthers();
    });
    
  3. Data Synchronization: Sync external systems when pivots are updated:

    static::pivotSynced(function ($model, $relationName, $changes) {
        if (!empty($changes['updated'])) {
            $this->syncExternalSystem($model, $relationName, $changes['updated']);
        }
    });
    
  4. Validation: Validate pivot attributes before they are saved:

    static::pivotUpdating(function ($model, $relationName, $pivotIds, $pivotIdsAttributes) {
        foreach ($pivotIdsAttributes as $id => $attributes) {
            if (!$this->validatePivotAttributes($attributes)) {
                throw new \Exception("Invalid pivot attributes for ID {$id}");
            }
        }
    });
    

Integration Tips

  1. Leverage Laravel’s Queues: Dispatch pivot events to queues for async processing:

    static::pivotDetached(function ($model, $relationName, $pivotIds) {
        dispatch(new HandlePivotDetached($model, $relationName, $pivotIds))->delay(now()->addSeconds(10));
    });
    
  2. Combine with Observers: Use observers for additional logic alongside pivot events:

    class UserObserver
    {
        public function syncingRoles(User $user)
        {
            // Pre-sync logic
        }
    
        public function syncedRoles(User $user)
        {
            // Post-sync logic
        }
    }
    
  3. Testing: Test pivot events using Laravel’s event testing helpers:

    public function test_pivot_attached_event()
    {
        $user = User::factory()->create();
        Event::fake();
    
        $user->roles()->attach(1);
    
        Event::assertDispatched(PivotAttached::class);
    }
    
  4. Performance: For high-frequency pivot updates, consider batching events or using syncWithoutEvents to disable events temporarily:

    $user->roles()->syncWithoutEvents([1, 2, 3]);
    

Gotchas and Tips

Pitfalls

  1. Event Order:

    • sync() dispatches pivotDetaching/pivotDetached before pivotAttaching/pivotAttached. This is because sync() first detaches all existing pivots, then attaches new ones.
    • Example: If you rely on the order of events, ensure your logic accounts for this behavior.
  2. Payload Structure:

    • The $changes payload in pivotSynced includes arrays like ["attached" => [1, 2], "detached" => [3]]. Ensure your code handles missing keys (e.g., empty($changes['attached'])).
    • $pivotIdsAttributes in pivotAttached/pivotUpdated may include empty arrays for pivots without custom attributes. Validate before accessing nested keys.
  3. Duplicate Events:

    • Avoid registering the same event listener multiple times (e.g., in boot() and globally). Use Event::forget() if needed:
      Event::forget('eloquent.pivotAttached', $listener);
      
  4. MorphToMany:

    • Events for MorphToMany relationships follow the same pattern as BelongsToMany, but ensure your models are correctly set up for polymorphic relations.
  5. Event Suppression:

    • Since v13.1.0, pivotSynced and pivotDetached are suppressed if no changes occur. This can break assumptions in older code expecting events for "no-op" syncs.
  6. Model Caching:

    • If using GeneaLabs/laravel-model-caching, ensure pivot events are not cached unintentionally. The package is designed to work with it, but test thoroughly.

Debugging

  1. Event Not Firing:

    • Verify the PivotEventTrait is included in the model.
    • Check for typos in event names (e.g., pivotAttaching vs. pivotAttached).
    • Use Event::listen('eloquent.*', function ($eventName) { \Log::info($eventName); }); to debug dispatched events.
  2. Payload Issues:

    • Dump the payload in your listener to verify structure:
      static::pivotSynced(function ($model, $relationName, $changes) {
          \Log::debug(['model' => $model, 'relation' => $relationName, 'changes' => $changes]);
      });
      
  3. Performance Bottlenecks:

    • If events slow down pivot operations, profile the listeners using Laravel Debugbar or Xdebug. Consider moving heavy logic to queues.

Tips

  1. Naming Conventions:

    • Use descriptive relation names (e.g., roles instead of pivots) for clarity in event listeners.
  2. Custom Payloads:

    • Extend the payload for your use case by modifying the trait or creating a wrapper:
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor