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

Eventor Symfony Laravel Package

becklyn/eventor-symfony

Minimal pub/sub abstraction for Symfony, with built-in support for Dapr’s Pub/Sub API. Configure via env vars and publish typed messages to topics, then register handlers and expose simple subscription and topic endpoints through a controller/registry.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require becklyn/eventor-symfony
    

    Ensure symfony/framework-bundle (v6.1+) is installed (required dependency).

  2. Configure Environment: Add these to your .env:

    DAPR_HOST=http://localhost:3500  # Dapr endpoint (required)
    DAPR_PUBSUB=pubsubname         # Pub/sub name (required)
    
  3. First Use Case: Publish an Event Define a message class (e.g., app/Domain/Events/Message.php):

    class Message {
        public function __construct(
            public string $id,
            public string $body,
        ) {}
    }
    

    Publish it in a service:

    use Becklyn\Eventor\Publisher;
    
    class OrderService {
        public function __construct(private Publisher $publisher) {}
    
        public function createOrder() {
            $this->publisher->publish("orders.created", new Message(
                id: "123",
                body: "Order #123 created",
            ));
        }
    }
    
  4. First Use Case: Subscribe to an Event Register a subscriber in a controller (e.g., src/Controller/DaprSubscriptionController.php):

    use Becklyn\Eventor\DaprSubscriptionRegistry;
    use Becklyn\Eventor\On;
    
    class DaprSubscriptionController {
        public function __construct(private DaprSubscriptionRegistry $registry) {}
    
        public function __invoke() {
            new On(
                fn(Message $msg) => logger()->info($msg->body),
                $this->registry,
                "orders.created"
            );
        }
    }
    
  5. Expose Dapr Endpoints: Add routes in config/routes.yaml:

    dapr_subscribe:
        path: /dapr/subscribe
        controller: Becklyn\Eventor\DaprSubscriptionController
        methods: [GET]
    
    dapr_topic:
        path: /dapr/{pubsub}/orders.created
        controller: Becklyn\Eventor\DaprSubscriptionController
        methods: [POST]
    

Implementation Patterns

Core Workflows

  1. Event Publishing:

    • Pattern: Use Publisher to emit events with a topic and payload.
    • Example:
      $this->publisher->publish("user.updated", new UserUpdatedEvent($userId, $changes));
      
    • Best Practice: Decouple event creation from publishing (e.g., use a EventDispatcher or EventBus pattern).
  2. Subscription Management:

    • Pattern: Register subscribers via On in a controller or service.
    • Example:
      new On(
          fn(UserUpdatedEvent $event) => $this->notifyUser($event),
          $this->registry,
          "user.updated"
      );
      
    • Best Practice: Group related subscriptions in a dedicated service (e.g., UserEventSubscriber).
  3. Dependency Injection:

    • Pattern: Inject Publisher and DaprSubscriptionRegistry into services/controllers.
    • Example:
      class AnalyticsService {
          public function __construct(
              private Publisher $publisher,
              private DaprSubscriptionRegistry $registry
          ) {}
      }
      
  4. Error Handling:

    • Pattern: Exceptions in subscribers bubble up (since v3.0.0) and stop further execution.
    • Example:
      new On(
          fn(UserUpdatedEvent $event) => $this->handleUpdate($event),
          $this->registry,
          "user.updated"
      );
      // Handle exceptions at the controller level or wrap in a try-catch.
      

Integration Tips

  1. Symfony Serialization:

    • The library uses Symfony’s Serializer for payload serialization.
    • Ensure your event classes are serializable (e.g., use #[SerializedName] or implement __serialize()).
  2. OpenTelemetry:

    • Tracing is enabled by default (since v2.0.0).
    • Configure your OpenTelemetry provider (e.g., symfony/otel) separately.
  3. Testing:

    • Mock Publisher: Use Mockery or PHPUnit to stub publish() calls.
    • Mock Subscriptions: Override DaprSubscriptionRegistry in tests to verify event handling.
    • Example:
      $mockPublisher = Mockery::mock(Publisher::class);
      $mockPublisher->shouldReceive('publish')->once();
      $this->app->instance(Publisher::class, $mockPublisher);
      
  4. Dynamic Topics:

    • For dynamic topic names (e.g., user.{id}.updated), use a factory or service locator to register subscribers.

Gotchas and Tips

Pitfalls

  1. Exception Handling:

    • Gotcha: Subscriber exceptions stop further execution (since v3.0.0). Avoid swallowing exceptions unless intentional.
    • Fix: Wrap subscribers in try-catch or handle errors at the controller level.
      new On(
          fn($event) => try { $this->handle($event); } catch (\Exception $e) { logger()->error($e); },
          $this->registry,
          "topic"
      );
      
  2. Dapr Configuration:

    • Gotcha: Missing DAPR_HOST or DAPR_PUBSUB in .env will cause silent failures.
    • Fix: Validate config in a service provider:
      if (!getenv('DAPR_HOST')) {
          throw new \RuntimeException('DAPR_HOST is not configured.');
      }
      
  3. Serialization Issues:

    • Gotcha: Custom objects may fail to serialize if not annotated or implemented correctly.
    • Fix: Use #[SerializedName] or implement __serialize()/__unserialize():
      class UserEvent {
          #[SerializedName('user_id')]
          public string $id;
      }
      
  4. Route Conflicts:

    • Gotcha: Dapr expects exact route paths (e.g., /dapr/{pubsub}/{topic}). Conflicts with other routes may occur.
    • Fix: Use _dapr prefix or route priorities:
      _dapr:
          resource: "@BecklynEventorDaprSubscriptionController"
          path: /_dapr/{pubsub}/{topic}
      
  5. Dependency Versioning:

    • Gotcha: The package requires symfony/framework-bundle:^6.1. Downgrading may break functionality.
    • Fix: Pin versions in composer.json if using older Symfony.

Debugging Tips

  1. Log Subscriptions:

    • Enable debug logging for Becklyn\Eventor to trace subscription calls:
      // config/packages/monolog.yaml
      handlers:
          main:
              level: debug
              channels: ["eventor"]
      
  2. Dapr HTTP Errors:

    • Check Dapr logs (http://localhost:3500/v1.0/log) for pub/sub failures.
    • Validate the Content-Type: application/json header in Dapr requests.
  3. Payload Inspection:

    • Use var_dump() or logger() in subscribers to inspect incoming events:
      new On(
          fn($event) => logger()->debug('Received:', $event),
          $this->registry,
          "topic"
      );
      

Extension Points

  1. Custom Brokers:

    • The library is broker-agnostic. Extend Publisher or SubscriptionRegistry to support other brokers (e.g., RabbitMQ, Kafka).
    • Example:
      class RabbitMqPublisher implements PublisherInterface {
          public function publish(string $topic, mixed $payload): void {
              // Custom RabbitMQ logic
          }
      }
      
  2. Middleware:

    • Add preprocessing/postprocessing logic by wrapping Publisher or On handlers:
      $publisher = new MiddlewarePublisher(
          new Publisher(),
          fn($topic, $payload) => logger()->info("Publishing to $topic")
      );
      
  3. Dynamic Subscriptions:

    • Register subscribers dynamically at runtime (e.g., from a database):
      $subscriber = new DynamicSubscriber($this->registry);
      $subscriber->registerFromConfig($config);
      
  4. Event Validation:

    • Validate payloads before publishing/subscribing using Symfony Validator:
      use Symfony\Component\Validator\Validator\ValidatorInterface;
      
      class ValidatingPublisher implements PublisherInterface {
          public function __construct(
              private Publisher $decorated,
              private ValidatorInterface $validator
          ) {}
      
          public function publish(string $topic, mixed $payload): void {
              $errors = $this->validator->validate($payload);
              if ($errors->count() > 0) {
                  throw new \InvalidArgumentException('Validation failed');
              }
              $this->decorated->publish($topic
      
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.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle