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

Outbox Bundle Laravel Package

agluh/outbox-bundle

Laravel outbox pattern bundle for reliable event publishing: capture domain events in an outbox table within your transaction, then dispatch them asynchronously to queues or brokers. Helps avoid dual-write issues and improves consistency between your DB and integrations.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Bundle

    composer require agluh/outbox-bundle
    

    Register the bundle in config/bundles.php:

    return [
        // ...
        Agluh\OutboxBundle\AgluhOutboxBundle::class => ['all' => true],
    ];
    
  2. Configure the Database Run migrations to create the outbox_message table:

    php bin/console doctrine:migrations:diff
    php bin/console doctrine:migrations:migrate
    
  3. First Use Case: Publishing a Message Inject the OutboxPublisher service and publish a message:

    use Agluh\OutboxBundle\Publisher\OutboxPublisherInterface;
    
    class MyCommand
    {
        public function __construct(private OutboxPublisherInterface $publisher) {}
    
        public function handle()
        {
            $this->publisher->publish(
                new MyDomainEvent('event_id', ['data' => 'value']),
                new \DateTimeImmutable()
            );
        }
    }
    

Where to Look First

  • Configuration: Check config/packages/agluh_outbox.yaml for default settings (e.g., outbox_table, lock_timeout).
  • Doctrine Entities: Review src/Entity/OutboxMessage.php to understand the schema.
  • Publisher Interface: Study Agluh\OutboxBundle\Publisher\OutboxPublisherInterface for method signatures.

Implementation Patterns

Core Workflow: Outbox Pattern

  1. Publish Events Use OutboxPublisherInterface to enqueue domain events:

    $this->publisher->publish(
        new OrderCreatedEvent($orderId, $customerId),
        new \DateTimeImmutable()
    );
    
  2. Process Outbox (Cron Job or Symfony Command) Run the outbox:process command to publish messages to their destinations (e.g., Kafka, RabbitMQ):

    php bin/console outbox:process
    

    Configure destinations in agluh_outbox.yaml:

    outbox:
        destinations:
            kafka:
                type: kafka
                config: '%env(KAFKA_CONFIG)%'
                topics: ['events']
    
  3. Idempotency Use UUIDs for event IDs and leverage the processed_at field to avoid duplicates.


Integration Tips

  • Symfony Messenger Bridge Combine with symfony/messenger for async processing:

    # config/packages/messenger.yaml
    messenger:
        transports:
            outbox: '%env(MESSENGER_TRANSPORT_DSN)%'
        routing:
            'Agluh\OutboxBundle\Message\ProcessOutboxMessage': outbox
    
  • Doctrine Events Auto-publish events after entity persistence:

    // src/EventListener/OrderListener.php
    public function postPersist(Order $order, LifecycleEventArgs $args)
    {
        $this->publisher->publish(new OrderCreatedEvent($order->getId()));
    }
    
  • Testing Use OutboxPublisherTest (provided by the bundle) to assert published messages:

    $this->assertPublished(new OrderCreatedEvent($orderId));
    

Gotchas and Tips

Pitfalls

  1. Locking Issues

    • The bundle uses Symfony’s LockFactory to prevent concurrent processing.
    • Fix: Increase lock_timeout in config if processing takes >30s (default):
      outbox:
          lock_timeout: 60
      
  2. Transaction Boundaries

    • Ensure publish() is called within a transaction (e.g., after EntityManager::flush()).
    • Anti-pattern:
      // ❌ Avoid: No transaction!
      $this->publisher->publish($event);
      $this->entityManager->flush();
      
  3. Destination Configuration

    • Custom destinations must implement DestinationInterface. The bundle ships with Kafka/RabbitMQ adapters.
    • Error: Missing type in destinations config will silently fail. Validate with:
      php bin/console debug:config agluh_outbox
      

Debugging Tips

  1. Query the Outbox Table Check pending messages:

    SELECT * FROM outbox_message WHERE processed_at IS NULL;
    
  2. Enable Debug Logging Add to config/packages/dev/agluh_outbox.yaml:

    outbox:
        debug: true
    
  3. Manual Processing Force-process a single message (for testing):

    php bin/console outbox:process --message-id=UUID_HERE
    

Extension Points

  1. Custom Destinations Create a new destination class:

    use Agluh\OutboxBundle\Destination\DestinationInterface;
    
    class SlackDestination implements DestinationInterface
    {
        public function publish(array $message): void
        {
            // Send to Slack webhook
        }
    }
    

    Register in config:

    outbox:
        destinations:
            slack:
                type: slack
                config: '%env(SLACK_WEBHOOK)%'
    
  2. Event Transformers Override OutboxMessageTransformer to customize serialization:

    // src/Outbox/Transformer/CustomTransformer.php
    use Agluh\OutboxBundle\Transformer\OutboxMessageTransformerInterface;
    
    class CustomTransformer implements OutboxMessageTransformerInterface
    {
        public function transform(object $event): array
        {
            return [
                'type' => get_class($event),
                'payload' => json_encode($event->toArray()),
            ];
        }
    }
    

    Bind in services.yaml:

    services:
        Agluh\OutboxBundle\Transformer\OutboxMessageTransformerInterface: '@App\Outbox\Transformer\CustomTransformer'
    
  3. Post-Processing Hooks Subscribe to outbox.message.processed event:

    // src/EventListener/OutboxListener.php
    use Agluh\OutboxBundle\Event\MessageProcessedEvent;
    
    public function onMessageProcessed(MessageProcessedEvent $event)
    {
        // Log or trigger side effects
    }
    

    Register as a service tag:

    tags:
        - { name: kernel.event_listener, event: outbox.message.processed, method: onMessageProcessed }
    
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.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware