dualmedia/symfony-doctrine-event-converter-bundle
Install the Bundle
composer require dualmedia/symfony-doctrine-event-converter-bundle
Register in config/bundles.php:
DualMedia\DoctrineEventConverterBundle\DoctrineEventConverterBundle::class => ['all' => true],
Annotate Your Entity
Extend IdentifiableInterface and mark with @ORM\Entity:
use Doctrine\ORM\Mapping as ORM;
use DualMedia\Common\Interface\IdentifiableInterface;
#[ORM\Entity]
class User implements IdentifiableInterface
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private ?int $id = null;
public function getId(): ?int { return $this->id; }
}
Create an Abstract Event
Define a base event class (e.g., UserEvent):
abstract class UserEvent extends AbstractDoctrineEvent
{
public function __construct(private User $user) {}
public function getUser(): User { return $this->user; }
}
Trigger Events via Doctrine
Use the bundle’s EventDispatcher to dispatch Symfony events from Doctrine operations:
$entityManager->persist($user);
$entityManager->flush(); // Triggers `UserEvent` automatically
Event Mapping
Define a mapping between Doctrine lifecycle events (e.g., prePersist, postUpdate) and Symfony events:
# config/packages/doctrine_event_converter.yaml
doctrine_event_converter:
mappings:
App\Entity\User:
prePersist: [App\Event\UserCreatedEvent]
postUpdate: [App\Event\UserUpdatedEvent]
Event Subscribers Subscribe to Symfony events in a subscriber service:
class UserEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
UserCreatedEvent::class => 'onUserCreated',
UserUpdatedEvent::class => 'onUserUpdated',
];
}
public function onUserCreated(UserCreatedEvent $event): void
{
// Business logic (e.g., send welcome email)
}
}
Conditional Event Dispatching
Use EventDispatcher checks in your abstract event:
abstract class UserEvent extends AbstractDoctrineEvent
{
public function __construct(private User $user)
{
if (!$this->shouldDispatch()) {
return; // Skip if conditions fail (e.g., soft-deleted user)
}
}
protected function shouldDispatch(): bool
{
return !$this->user->isDeleted();
}
}
Doctrine Listeners as Fallback If the bundle doesn’t cover a use case, fall back to native Doctrine listeners:
$eventManager->addEventListener(
[User::class, 'prePersist'],
[$this, 'handlePrePersist']
);
Event Data Enrichment Extend events with metadata (e.g., timestamps, user context):
class UserCreatedEvent extends UserEvent
{
public function __construct(User $user, private \DateTimeImmutable $createdAt) {}
}
Testing
Mock the EventDispatcher in tests:
$dispatcher = $this->createMock(EventDispatcherInterface::class);
$container->set('event_dispatcher', $dispatcher);
Missing IdentifiableInterface
Forgetting to implement getId() will cause silent failures. Always verify:
class User implements IdentifiableInterface { ... }
Circular Dependencies
Avoid injecting the EntityManager into events—use the bundle’s EventDispatcher instead:
// ❌ Anti-pattern
class UserEvent { public function __construct(EntityManager $em) {} }
// ✅ Preferred
class UserEvent { public function __construct(User $user) {} }
Event Ordering
Symfony events fire after Doctrine lifecycle callbacks. For pre-hooks, use pre* events:
mappings:
App\Entity\User:
prePersist: [App\Event\ValidateUserEvent] # Runs before persist
Performance Overhead
Heavy event logic in post* hooks may slow down bulk operations. Offload to async tasks (e.g., Symfony Messenger).
Enable Debug Mode
Set DUALMEDIA_DEBUG: true in .env to log dispatched events:
DUALMEDIA_DEBUG=1
Check Event Dispatching
Verify events are fired via Doctrine’s EventManager logs:
bin/console debug:event-dispatcher
Custom Event Converters Override the default converter for complex entities:
class CustomUserConverter implements EventConverterInterface
{
public function convert(UnitOfWork $uow, EntityChangeSet $changeSet): array
{
return [new UserUpdatedEvent($changeSet->getEntity())];
}
}
Register in services.yaml:
services:
App\Event\Converter\CustomUserConverter:
tags: [doctrine_event_converter.converter]
Dynamic Event Mapping Use a service to generate mappings at runtime (e.g., based on entity traits):
class DynamicMappingService
{
public function getMappings(): array
{
return [
User::class => [
'prePersist' => [UserCreatedEvent::class],
],
];
}
}
Event Validation
Add constraints to events (e.g., validate User before dispatching):
class UserCreatedEvent extends UserEvent
{
public function __construct(User $user)
{
if (!$user->isValid()) {
throw new \RuntimeException('Invalid user data');
}
}
}
How can I help you explore Laravel packages today?