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

Doctrine Orm Bridge Laravel Package

simple-bus/doctrine-orm-bridge

Doctrine ORM bridge for SimpleBus/MessageBus. Provides command bus middleware to run command handling inside Doctrine transactions and to dispatch domain events generated by entities. Part of the SimpleBus ecosystem.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install Dependencies

    composer require simplebus/message-bus simplebus/doctrine-orm-bridge doctrine/orm
    
  2. Register the Bridge Add the transaction middleware to your SimpleBus pipeline in a service provider:

    use SimpleBus\DoctrineORMBridge\Middleware\TransactionMiddleware;
    use SimpleBus\MessageBus\Middleware\MiddlewareStack;
    
    $entityManager = $this->app->make(\Doctrine\ORM\EntityManagerInterface::class);
    $bus = new \SimpleBus\MessageBus\MessageBus(
        new MiddlewareStack(
            new TransactionMiddleware($entityManager),
            // Other middleware...
        )
    );
    
  3. First Use Case: Transactional Command Create a command handler that persists entities:

    use SimpleBus\MessageBus\Command\CommandHandler;
    
    class CreateUserHandler implements CommandHandler
    {
        private $entityManager;
    
        public function __construct(\Doctrine\ORM\EntityManagerInterface $entityManager)
        {
            $this->entityManager = $entityManager;
        }
    
        public function handle(CreateUser $command)
        {
            $user = new User($command->email, $command->password);
            $this->entityManager->persist($user);
            // No need to manually flush/commit - handled by middleware
        }
    }
    
  4. Dispatch a Command

    $bus->dispatch(new CreateUser('user@example.com', 'password123'));
    

Implementation Patterns

Core Workflows

1. Transactional Command Handling

  • Pattern: Wrap all database operations in a single transaction.
  • Implementation:
    // In your command handler
    public function handle(SomeCommand $command)
    {
        $entity = new Entity();
        $entity->setProperty($command->value);
    
        // Transaction is automatically committed if no exceptions
        // or rolled back if exceptions occur
    }
    
  • Middleware Stack Example:
    $middlewareStack = new MiddlewareStack(
        new TransactionMiddleware($entityManager),
        new ValidateCommandMiddleware(),
        new LogCommandMiddleware()
    );
    

2. Domain Event Publishing

  • Pattern: Collect events from Doctrine entities and dispatch them after transaction completion.
  • Implementation:
    1. Annotate Entities:
      use SimpleBus\DoctrineORMBridge\DomainEvent\DomainEvent;
      
      class User implements DomainEvent
      {
          public function occurredOn(): \DateTimeInterface
          {
              return new \DateTime();
          }
      }
      
    2. Record Events in Entity Lifecycle Callbacks:
      use Doctrine\ORM\Mapping as ORM;
      use SimpleBus\DoctrineORMBridge\DomainEvent\DomainEventRecorder;
      
      class UserEntity
      {
          #[ORM\PrePersist]
          public function recordDomainEvents()
          {
              DomainEventRecorder::record(new User());
          }
      }
      
    3. Add Event Dispatcher Middleware:
      use SimpleBus\DoctrineORMBridge\Middleware\DomainEventDispatcherMiddleware;
      
      $middlewareStack = new MiddlewareStack(
          new TransactionMiddleware($entityManager),
          new DomainEventDispatcherMiddleware($entityManager, $eventBus)
      );
      

3. Conditional Transaction Usage

  • Pattern: Only wrap specific commands in transactions.
  • Implementation:
    $bus = new \SimpleBus\MessageBus\MessageBus(
        new MiddlewareStack(
            new ConditionalTransactionMiddleware($entityManager, function ($message) {
                return $message instanceof RequiresTransaction;
            }),
            // Other middleware...
        )
    );
    

Integration Tips

Laravel-Specific Integration

  1. Service Provider Setup:

    public function register()
    {
        $this->app->singleton(\SimpleBus\MessageBus\MessageBus::class, function ($app) {
            $entityManager = $app->make(\Doctrine\ORM\EntityManagerInterface::class);
            $bus = new \SimpleBus\MessageBus\MessageBus(
                new MiddlewareStack(
                    new TransactionMiddleware($entityManager),
                    new DomainEventDispatcherMiddleware($entityManager, $app->make(\SimpleBus\MessageBus\EventBus::class))
                )
            );
            return $bus;
        });
    }
    
  2. Binding Handlers:

    $this->app->bind(\SimpleBus\MessageBus\Command\CommandHandler::class, function ($app, $command) {
        return new CreateUserHandler($app->make(\Doctrine\ORM\EntityManagerInterface::class));
    });
    

Testing Patterns

  1. Mocking the EntityManager:

    $entityManager = $this->createMock(\Doctrine\ORM\EntityManagerInterface::class);
    $entityManager->method('persist')->willReturnCallback(function ($entity) {
        // Store entity for assertions
    });
    $entityManager->method('flush')->willReturnCallback(function () {
        // Simulate flush behavior
    });
    
    $bus = new \SimpleBus\MessageBus\MessageBus(
        new MiddlewareStack(new TransactionMiddleware($entityManager))
    );
    
  2. Testing Event Dispatching:

    $eventBus = $this->createMock(\SimpleBus\MessageBus\EventBus::class);
    $eventBus->expects($this->once())
            ->method('dispatch')
            ->with($this->isInstanceOf(User::class));
    
    $middlewareStack = new MiddlewareStack(
        new TransactionMiddleware($entityManager),
        new DomainEventDispatcherMiddleware($entityManager, $eventBus)
    );
    

Gotchas and Tips

Pitfalls

  1. Transaction Rollback Leaks

    • Issue: If a transaction fails, the EntityManager may enter a broken state, causing subsequent commands to fail.
    • Fix: The package auto-resets the EntityManager (since v3.0.0), but ensure your application can handle this gracefully. Test with:
      $entityManager->expects($this->once())->method('reset');
      
  2. Event Deduplication

    • Issue: Events are erased after processing (since v2.0.1), which prevents reprocessing. If idempotency is required, implement compensating logic in your event handlers.
  3. Circular Dependencies

    • Issue: If your command handler depends on the EntityManager and the bridge also uses it, ensure proper dependency injection to avoid circular references.
    • Fix: Use constructor injection and Laravel’s service container to manage lifetimes.
  4. Middleware Order Matters

    • Issue: Placing DomainEventDispatcherMiddleware before TransactionMiddleware will dispatch events before the transaction completes, leading to inconsistent state.
    • Fix: Always place TransactionMiddleware first in the stack.
  5. Doctrine Event Listeners Conflict

    • Issue: If you have Doctrine lifecycle callbacks (e.g., @PrePersist) that also modify the entity state, they may interfere with domain event recording.
    • Fix: Ensure domain event recording happens in a controlled order, typically in a dedicated method called from lifecycle callbacks.

Debugging Tips

  1. Transaction Debugging

    • Enable Doctrine logging to trace transaction boundaries:
      $entityManager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
      
    • Check for TransactionRequiredException if commands fail unexpectedly.
  2. Event Dispatching Issues

    • Verify events are recorded by inspecting the DomainEventRecorder:
      $recorder = DomainEventRecorder::getRecorder();
      $events = $recorder->getRecordedEvents();
      
    • Ensure events implement DomainEvent interface and have an occurredOn() method.
  3. Middleware Stack Debugging

    • Add a logging middleware to trace message flow:
      use Psr\Log\LoggerInterface;
      
      class LoggingMiddleware implements Middleware
      {
          private $logger;
      
          public function __construct(LoggerInterface $logger)
          {
              $this->logger = $logger;
          }
      
          public function handle($message, callable $next)
          {
              $this->logger->info('Handling message', ['class' => get_class($message)]);
              return $next($message);
          }
      }
      

Extension Points

  1. Custom Transaction Strategies

    • Extend TransactionMiddleware to support custom transaction managers or isolation levels:
      class CustomTransactionMiddleware extends TransactionMiddleware
      {
          public function __construct(
              \Doctrine\ORM\EntityManagerInterface $entityManager,
              private \Doctrine\DBAL\Connection $connection
          ) {
              parent::__construct($entityManager);
          }
      
          protected function beginTransaction()
          {
              $this->connection->beginTransaction();
          }
      }
      
  2. Event Filtering

    • Filter events before dispatching by extending DomainEventDispatcherMiddleware:
      class FilteredEventDispatcherMiddleware extends DomainEventDispatcherMiddleware
      {
          public function __construct(
              \Doctrine\ORM\EntityManagerInterface $entityManager,
              \SimpleBus\MessageBus\EventBus $eventBus,
              private callable $filter
          )
      
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.
terminal42/code-quality-tools
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