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

Async Event Dispatcher Laravel Package

bodaclick/async-event-dispatcher

Async event dispatcher for PHP inspired by Symfony. Add one or more drivers (listeners) and dispatch AsyncEventInterface events using a fire-and-forget pub/sub style. Includes RabbitMQ and file drivers, with an easy interface for custom drivers.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require bodaclick/async-event-dispatcher:1.0.x-dev
    

    Ensure your composer.json includes the package under require.

  2. Basic Initialization:

    use BDK\AsyncEventDispatcher\AsyncEventDispatcher;
    use BDK\AsyncEventDispatcher\AsyncDriver\RabbitMQDriver;
    
    $dispatcher = new AsyncEventDispatcher();
    $driver = new RabbitMQDriver(['connection_params' => [...]]);
    $dispatcher->addDriver($driver); // Registers for ALL events
    
  3. First Use Case: Create an event class implementing AsyncEventInterface:

    class UserRegisteredEvent implements AsyncEventInterface {
        public $userId;
        public function __construct($userId) { $this->userId = $userId; }
    }
    

    Dispatch it:

    $event = new UserRegisteredEvent(123);
    $dispatcher->dispatch($event);
    

Where to Look First

  • AsyncEventDispatcher class: Core logic for adding drivers and dispatching events.
  • AsyncDriver implementations: Check RabbitMQDriver or FileDriver for configuration patterns.
  • AsyncEventInterface: Minimal contract for events (no methods required, just a marker).

Implementation Patterns

Common Workflows

  1. Driver Registration:

    • Single Event: Register a driver for specific events (e.g., addDriver($driver, 'user.registered')).
    • All Events: Register for all events (e.g., addDriver($driver)). Useful for cross-cutting concerns like logging.
    • Dynamic Registration: Register drivers conditionally (e.g., in a service provider’s boot() method).
  2. Event Dispatching:

    • Fire-and-Forget: Dispatch events without blocking (e.g., during HTTP requests).
    • Bulk Dispatching: Loop through events and dispatch them asynchronously:
      foreach ($userEvents as $event) {
          $dispatcher->dispatch($event);
      }
      
  3. Driver Configuration:

    • RabbitMQ: Pass connection parameters (e.g., host, port, credentials) to the driver constructor.
    • File: Specify a directory path for event serialization:
      $fileDriver = new FileDriver(['storage_path' => storage_path('app/async_events')]);
      
  4. Integration with Laravel:

    • Service Provider: Bind the dispatcher to the container:
      $this->app->singleton(AsyncEventDispatcher::class, function ($app) {
          $dispatcher = new AsyncEventDispatcher();
          $dispatcher->addDriver(new RabbitMQDriver($app['config']['rabbitmq']));
          return $dispatcher;
      });
      
    • Event Listeners: Use the dispatcher in listeners for synchronous events:
      public function handle(UserRegistered $event) {
          $asyncDispatcher->dispatch(new UserRegisteredEvent($event->user->id));
      }
      
  5. Event Serialization:

    • Events must be serializable (e.g., avoid closures, resources). Use json_encode()/json_decode() or a library like spatie/array-to-object.

Gotchas and Tips

Pitfalls

  1. Driver-Specific Errors:

    • RabbitMQ: Connection issues or queue unavailability will silently fail. Add error handling:
      try {
          $dispatcher->dispatch($event);
      } catch (Exception $e) {
          Log::error("Async dispatch failed: " . $e->getMessage());
      }
      
    • File: Disk space or permission issues may cause silent failures. Validate the storage path.
  2. Event Serialization:

    • Non-serializable properties (e.g., database connections, closures) will throw errors. Use #[Spatie\LaravelData\Attributes\With] or similar for complex objects.
  3. Ordering Guarantees:

    • Async dispatch does not guarantee order. Use event IDs or timestamps if order matters.
  4. Driver Overhead:

    • Async drivers add latency. Avoid dispatching trivial events (e.g., Event::DISPATCHING).
  5. Memory Leaks:

    • Unbound drivers or dispatchers in long-running processes (e.g., queues) may accumulate resources. Clean up in terminate() or use dependency injection.

Debugging Tips

  1. Log Events: Add a debug driver to log dispatched events:

    $dispatcher->addDriver(new class implements AsyncEventDriverInterface {
        public function handle(AsyncEventInterface $event) {
            Log::debug("Async event dispatched: " . get_class($event));
        }
    });
    
  2. Check Drivers: Verify drivers are registered:

    $drivers = $dispatcher->getDrivers(); // Hypothetical method; may need reflection.
    
  3. Test Locally: Use the FileDriver for testing (no external dependencies):

    $dispatcher->addDriver(new FileDriver(['storage_path' => __DIR__ . '/tests/events']));
    

Extension Points

  1. Custom Drivers: Implement AsyncEventDriverInterface:

    class SlackDriver implements AsyncEventDriverInterface {
        public function handle(AsyncEventInterface $event) {
            $webhook = config('services.slack.webhook');
            Http::post($webhook, ['text' => "Event: " . get_class($event)]);
        }
    }
    
  2. Event Prioritization: Extend the dispatcher to support priority queues (e.g., by adding a priority property to events and sorting drivers).

  3. Retry Logic: Wrap drivers in a retry decorator:

    class RetryDriver implements AsyncEventDriverInterface {
        public function __construct(private AsyncEventDriverInterface $driver, private int $retries) {}
        public function handle(AsyncEventInterface $event) {
            retry($this->retries, function () use ($event) {
                $this->driver->handle($event);
            });
        }
    }
    
  4. Event Validation: Add a validation driver to reject malformed events:

    $dispatcher->addDriver(new class implements AsyncEventDriverInterface {
        public function handle(AsyncEventInterface $event) {
            if (!property_exists($event, 'requiredField')) {
                throw new InvalidArgumentException("Event missing required field.");
            }
        }
    });
    

Configuration Quirks

  1. RabbitMQ Driver:

    • Requires php-amqplib (composer require php-amqplib/php-amqplib).
    • Default exchange/queue names are hardcoded (e.g., async_events). Override via constructor:
      new RabbitMQDriver(['exchange' => 'custom_exchange']);
      
  2. File Driver:

    • Events are serialized to JSON. Customize the format by extending the driver:
      class CustomFileDriver extends FileDriver {
          protected function serialize(AsyncEventInterface $event): string {
              return json_encode(['type' => get_class($event), 'data' => $event->toArray()]);
          }
      }
      
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.
nasirkhan/laravel-sharekit
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony