diff --git a/composer.json b/composer.json
index 0fdf1deb152330570da7b9ec8962475b8deb67e9..dd114b6810b569cf5c8fd97cebe915b1e78b5e0d 100644
--- a/composer.json
+++ b/composer.json
@@ -22,7 +22,7 @@
         "distantmagic/swoole-futures": "^0.1.2",
         "doctrine/dbal": "^3.7",
         "doctrine/migrations": "^3.6",
-        "doctrine/orm": "^2.16",
+        "doctrine/orm": "^3.0",
         "doctrine/sql-formatter": "^1.1",
         "ezyang/htmlpurifier": "^4.16",
         "guzzlehttp/guzzle": "^7.8",
diff --git a/composer.lock b/composer.lock
index d9298424c3b9f33bb0c0f199b126ba1c888775c7..fbf586959de452a362edc5761f77c98e1eae8b84 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "a48f98db536d0c397573db6845651935",
+    "content-hash": "2008e1dfe7cb51ec6de8a61bdd663c0e",
     "packages": [
         {
             "name": "amphp/amp",
@@ -955,97 +955,6 @@
             ],
             "time": "2023-10-03T09:22:33+00:00"
         },
-        {
-            "name": "doctrine/common",
-            "version": "3.4.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/common.git",
-                "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/common/zipball/8b5e5650391f851ed58910b3e3d48a71062eeced",
-                "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/persistence": "^2.0 || ^3.0",
-                "php": "^7.1 || ^8.0"
-            },
-            "require-dev": {
-                "doctrine/coding-standard": "^9.0 || ^10.0",
-                "doctrine/collections": "^1",
-                "phpstan/phpstan": "^1.4.1",
-                "phpstan/phpstan-phpunit": "^1",
-                "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0",
-                "squizlabs/php_codesniffer": "^3.0",
-                "symfony/phpunit-bridge": "^6.1",
-                "vimeo/psalm": "^4.4"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Doctrine\\Common\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
-                {
-                    "name": "Roman Borschel",
-                    "email": "roman@code-factory.org"
-                },
-                {
-                    "name": "Benjamin Eberlei",
-                    "email": "kontakt@beberlei.de"
-                },
-                {
-                    "name": "Jonathan Wage",
-                    "email": "jonwage@gmail.com"
-                },
-                {
-                    "name": "Johannes Schmitt",
-                    "email": "schmittjoh@gmail.com"
-                },
-                {
-                    "name": "Marco Pivetta",
-                    "email": "ocramius@gmail.com"
-                }
-            ],
-            "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.",
-            "homepage": "https://www.doctrine-project.org/projects/common.html",
-            "keywords": [
-                "common",
-                "doctrine",
-                "php"
-            ],
-            "support": {
-                "issues": "https://github.com/doctrine/common/issues",
-                "source": "https://github.com/doctrine/common/tree/3.4.3"
-            },
-            "funding": [
-                {
-                    "url": "https://www.doctrine-project.org/sponsorship.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://www.patreon.com/phpdoctrine",
-                    "type": "patreon"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-10-09T11:47:59+00:00"
-        },
         {
             "name": "doctrine/dbal",
             "version": "3.8.1",
@@ -1639,61 +1548,48 @@
         },
         {
             "name": "doctrine/orm",
-            "version": "2.18.0",
+            "version": "3.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/orm.git",
-                "reference": "f2176a9ce56cafdfd1624d54bfdb076819083d5b"
+                "reference": "5b8b5f28f535e1f03b54dcfb0427407ed92f5b72"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/orm/zipball/f2176a9ce56cafdfd1624d54bfdb076819083d5b",
-                "reference": "f2176a9ce56cafdfd1624d54bfdb076819083d5b",
+                "url": "https://api.github.com/repos/doctrine/orm/zipball/5b8b5f28f535e1f03b54dcfb0427407ed92f5b72",
+                "reference": "5b8b5f28f535e1f03b54dcfb0427407ed92f5b72",
                 "shasum": ""
             },
             "require": {
                 "composer-runtime-api": "^2",
-                "doctrine/cache": "^1.12.1 || ^2.1.1",
-                "doctrine/collections": "^1.5 || ^2.1",
-                "doctrine/common": "^3.0.3",
-                "doctrine/dbal": "^2.13.1 || ^3.2",
+                "doctrine/collections": "^2.1",
+                "doctrine/dbal": "^3.6 || ^4",
                 "doctrine/deprecations": "^0.5.3 || ^1",
                 "doctrine/event-manager": "^1.2 || ^2",
                 "doctrine/inflector": "^1.4 || ^2.0",
                 "doctrine/instantiator": "^1.3 || ^2",
-                "doctrine/lexer": "^2 || ^3",
-                "doctrine/persistence": "^2.4 || ^3",
+                "doctrine/lexer": "^3",
+                "doctrine/persistence": "^3.1.1",
                 "ext-ctype": "*",
-                "php": "^7.1 || ^8.0",
+                "php": "^8.1",
                 "psr/cache": "^1 || ^2 || ^3",
-                "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0",
-                "symfony/polyfill-php72": "^1.23",
-                "symfony/polyfill-php80": "^1.16"
-            },
-            "conflict": {
-                "doctrine/annotations": "<1.13 || >= 3.0"
+                "symfony/console": "^5.4 || ^6.0 || ^7.0",
+                "symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0"
             },
             "require-dev": {
-                "doctrine/annotations": "^1.13 || ^2",
-                "doctrine/coding-standard": "^9.0.2 || ^12.0",
-                "phpbench/phpbench": "^0.16.10 || ^1.0",
-                "phpstan/phpstan": "~1.4.10 || 1.10.35",
-                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
+                "doctrine/coding-standard": "^12.0",
+                "phpbench/phpbench": "^1.0",
+                "phpstan/phpstan": "1.10.35",
+                "phpunit/phpunit": "^10.4.0",
                 "psr/log": "^1 || ^2 || ^3",
                 "squizlabs/php_codesniffer": "3.7.2",
-                "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
-                "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
-                "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
-                "vimeo/psalm": "4.30.0 || 5.16.0"
+                "symfony/cache": "^5.4 || ^6.2 || ^7.0",
+                "vimeo/psalm": "5.16.0"
             },
             "suggest": {
                 "ext-dom": "Provides support for XSD validation for XML mapping files",
-                "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0",
-                "symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
+                "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
             },
-            "bin": [
-                "bin/doctrine"
-            ],
             "type": "library",
             "autoload": {
                 "psr-4": {
@@ -1734,9 +1630,9 @@
             ],
             "support": {
                 "issues": "https://github.com/doctrine/orm/issues",
-                "source": "https://github.com/doctrine/orm/tree/2.18.0"
+                "source": "https://github.com/doctrine/orm/tree/3.0.0"
             },
-            "time": "2024-01-31T15:53:12+00:00"
+            "time": "2024-02-03T16:50:09+00:00"
         },
         {
             "name": "doctrine/persistence",
diff --git a/src/Attribute/ListensToDoctrineEntityEvents.php b/src/Attribute/ListensToDoctrineEntityEvents.php
new file mode 100644
index 0000000000000000000000000000000000000000..7378937498d2d729337184294ed7425201b1c5d6
--- /dev/null
+++ b/src/Attribute/ListensToDoctrineEntityEvents.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\Attribute;
+
+use Attribute;
+use Distantmagic\Resonance\Attribute as BaseAttribute;
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final readonly class ListensToDoctrineEntityEvents extends BaseAttribute
+{
+    /**
+     * @param class-string $className
+     */
+    public function __construct(public string $className) {}
+}
diff --git a/src/Attribute/ListensToDoctrineEvents.php b/src/Attribute/ListensToDoctrineEvents.php
new file mode 100644
index 0000000000000000000000000000000000000000..bf4055f64a279ffb09785bac981cc036920eea15
--- /dev/null
+++ b/src/Attribute/ListensToDoctrineEvents.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\Attribute;
+
+use Attribute;
+use Distantmagic\Resonance\Attribute as BaseAttribute;
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final readonly class ListensToDoctrineEvents extends BaseAttribute {}
diff --git a/src/DatabaseConnectionPoolConfiguration.php b/src/DatabaseConnectionPoolConfiguration.php
index 2da56a894f5d2e5cc6d29e560164192173335f1b..0219178710f6a1312e4aefcf60d8d38f1929f9d0 100644
--- a/src/DatabaseConnectionPoolConfiguration.php
+++ b/src/DatabaseConnectionPoolConfiguration.php
@@ -12,6 +12,7 @@ readonly class DatabaseConnectionPoolConfiguration
      * @psalm-taint-source file $unixSocket
      *
      * @param non-empty-string      $host
+     * @param null|non-empty-string $password
      * @param null|non-empty-string $unixSocket
      * @param non-empty-string      $username
      */
@@ -25,7 +26,7 @@ readonly class DatabaseConnectionPoolConfiguration
         #[SensitiveParameter]
         public bool $logQueries,
         #[SensitiveParameter]
-        public string $password,
+        public ?string $password,
         #[SensitiveParameter]
         public bool $poolPrefill,
         #[SensitiveParameter]
diff --git a/src/DoctrineConnectionRepository.php b/src/DoctrineConnectionRepository.php
index 841cbbf884dad1faa4d62ea0c3e86f883bcbfb67..568e36df009bde2e7b26b574d9e8bf02bb4ba27e 100644
--- a/src/DoctrineConnectionRepository.php
+++ b/src/DoctrineConnectionRepository.php
@@ -6,6 +6,7 @@ namespace Distantmagic\Resonance;
 
 use Distantmagic\Resonance\Attribute\GrantsFeature;
 use Distantmagic\Resonance\Attribute\Singleton;
+use Doctrine\Common\EventManager;
 use Doctrine\DBAL\Configuration;
 use Doctrine\DBAL\Connection;
 use Doctrine\DBAL\Driver;
@@ -35,6 +36,7 @@ readonly class DoctrineConnectionRepository
         private DoctrineMySQLDriver $doctrineMySQLDriver,
         private DoctrinePostgreSQLDriver $doctrinePostgreSQLDriver,
         private DoctrineSQLiteDriver $doctrineSQLiteDriver,
+        private EventManager $eventManager,
         private LoggerInterface $logger,
     ) {
         /**
@@ -49,13 +51,14 @@ readonly class DoctrineConnectionRepository
     public function buildConnection(string $name = 'default'): Connection
     {
         return new Connection(
-            [
+            config: $this->configuration,
+            driver: $this->getDriver($name),
+            eventManager: $this->eventManager,
+            params: [
                 'driverOptions' => [
                     'connectionPoolName' => $name,
                 ],
             ],
-            $this->getDriver($name),
-            $this->configuration,
         );
     }
 
diff --git a/src/DoctrineEntityListener.php b/src/DoctrineEntityListener.php
new file mode 100644
index 0000000000000000000000000000000000000000..47ba961366316173ac928c484367c64f0c67b306
--- /dev/null
+++ b/src/DoctrineEntityListener.php
@@ -0,0 +1,7 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+abstract readonly class DoctrineEntityListener {}
diff --git a/src/DoctrineEntityListenerCollection.php b/src/DoctrineEntityListenerCollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d0fd9d2002601315931874f79f551664671ea48
--- /dev/null
+++ b/src/DoctrineEntityListenerCollection.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+use Ds\Map;
+use Ds\Set;
+
+readonly class DoctrineEntityListenerCollection
+{
+    /**
+     * @var Map<class-string,Set<object>>
+     */
+    public Map $listeners;
+
+    public function __construct()
+    {
+        $this->listeners = new Map();
+    }
+
+    /**
+     * @param class-string $className
+     */
+    public function addEntityListener(string $className, object $listener): void
+    {
+        if (!$this->listeners->hasKey($className)) {
+            $this->listeners->put($className, new Set());
+        }
+
+        $this->listeners->get($className)->add($listener);
+    }
+}
diff --git a/src/DoctrineEntityListenerResolver.php b/src/DoctrineEntityListenerResolver.php
new file mode 100644
index 0000000000000000000000000000000000000000..142898dbeb5d5613c366c4b48c7b6c19f49a4bc7
--- /dev/null
+++ b/src/DoctrineEntityListenerResolver.php
@@ -0,0 +1,75 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+use Doctrine\ORM\Mapping\EntityListenerResolver;
+use Ds\Map;
+use InvalidArgumentException;
+use LogicException;
+use RuntimeException;
+
+readonly class DoctrineEntityListenerResolver implements EntityListenerResolver
+{
+    /**
+     * @var Map<class-string,object>
+     */
+    private Map $instances;
+
+    public function __construct()
+    {
+        $this->instances = new Map();
+    }
+
+    public function clear($className = null): never
+    {
+        throw new LogicException('Listeners cannot be cleared on runtime');
+    }
+
+    /**
+     * @param mixed $object explicitly mixed for typechecks
+     */
+    public function register($object): void
+    {
+        if (!is_object($object)) {
+            throw new InvalidArgumentException(sprintf(
+                'Expected object, got "%s"',
+                gettype($object)
+            ));
+        }
+
+        if ($this->instances->hasKey($object::class)) {
+            throw new RuntimeException(sprintf(
+                'Listener is already registered: "%s"',
+                $object::class,
+            ));
+        }
+
+        $this->instances->put($object::class, $object);
+    }
+
+    /**
+     * @param string $className
+     */
+    public function resolve($className): object
+    {
+        if (!class_exists($className)) {
+            throw new InvalidArgumentException(sprintf(
+                'Expected a class-string, got "%s"',
+                $className
+            ));
+        }
+
+        $listener = $this->instances->get($className, null);
+
+        if (is_null($listener)) {
+            throw new RuntimeException(sprintf(
+                'Could not find listener: "%s"',
+                $className,
+            ));
+        }
+
+        return $listener;
+    }
+}
diff --git a/src/DoctrineEntityManagerRepository.php b/src/DoctrineEntityManagerRepository.php
index 92b658f5e508c73b89aefd167470fb0993c6fd9f..b0c2465eab762a8642aa457aa90e75d7d705a29b 100644
--- a/src/DoctrineEntityManagerRepository.php
+++ b/src/DoctrineEntityManagerRepository.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Distantmagic\Resonance;
 
+use Doctrine\Common\EventManager;
+use Doctrine\DBAL\Connection;
 use Doctrine\ORM\Configuration;
 use Doctrine\ORM\EntityManager;
 use Doctrine\ORM\EntityManagerInterface;
@@ -23,8 +25,9 @@ readonly class DoctrineEntityManagerRepository
     private WeakMap $entityManagers;
 
     public function __construct(
-        private DoctrineConnectionRepository $doctrineConnectionRepository,
         private Configuration $configuration,
+        private DoctrineConnectionRepository $doctrineConnectionRepository,
+        private EventManager $eventManager,
     ) {
         /**
          * @var WeakMap<Request,Map<string,EntityManagerInterface>>
@@ -37,9 +40,8 @@ readonly class DoctrineEntityManagerRepository
      */
     public function buildEntityManager(string $name = 'default'): EntityManagerInterface
     {
-        return new EntityManager(
-            $this->doctrineConnectionRepository->buildConnection($name),
-            $this->configuration,
+        return $this->buildEntityManagerFromConnection(
+            connection: $this->doctrineConnectionRepository->buildConnection($name),
         );
     }
 
@@ -76,8 +78,9 @@ readonly class DoctrineEntityManagerRepository
             return $context[$contextKey]->getEntityManager();
         }
 
-        $conn = $this->doctrineConnectionRepository->getConnection($request, $name);
-        $entityManager = new EntityManager($conn, $this->configuration);
+        $entityManager = $this->buildEntityManagerFromConnection(
+            connection: $this->doctrineConnectionRepository->getConnection($request, $name),
+        );
 
         if ($context) {
             $context[$contextKey] = new EntityManagerWeakReference($entityManager);
@@ -152,6 +155,15 @@ readonly class DoctrineEntityManagerRepository
         }, $name, $flush);
     }
 
+    private function buildEntityManagerFromConnection(Connection $connection): EntityManagerInterface
+    {
+        return new EntityManager(
+            conn: $connection,
+            config: $this->configuration,
+            eventManager: $this->eventManager,
+        );
+    }
+
     /**
      * @param non-empty-string $name
      */
diff --git a/src/DoctrineEventSubscriber.php b/src/DoctrineEventSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..05db9b191c94ea93f322b0d26eeefe2582d52584
--- /dev/null
+++ b/src/DoctrineEventSubscriber.php
@@ -0,0 +1,9 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+use Doctrine\Common\EventSubscriber;
+
+abstract readonly class DoctrineEventSubscriber implements EventSubscriber {}
diff --git a/src/DoctrineEventSubscriber/RegisterEntityListeners.php b/src/DoctrineEventSubscriber/RegisterEntityListeners.php
new file mode 100644
index 0000000000000000000000000000000000000000..b6a74d57c3a5899d9094c05f0329fe2aa77eee8b
--- /dev/null
+++ b/src/DoctrineEventSubscriber/RegisterEntityListeners.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\DoctrineEventSubscriber;
+
+use Distantmagic\Resonance\Attribute\ListensToDoctrineEvents;
+use Distantmagic\Resonance\Attribute\Singleton;
+use Distantmagic\Resonance\DoctrineEntityListenerCollection;
+use Distantmagic\Resonance\DoctrineEventSubscriber;
+use Distantmagic\Resonance\SingletonCollection;
+use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
+use Doctrine\ORM\Events;
+use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
+
+#[ListensToDoctrineEvents]
+#[Singleton(collection: SingletonCollection::DoctrineEventListener)]
+readonly class RegisterEntityListeners extends DoctrineEventSubscriber
+{
+    public function __construct(
+        private DoctrineEntityListenerCollection $doctrineEntityListenerCollection,
+    ) {}
+
+    public function getSubscribedEvents()
+    {
+        return [
+            Events::loadClassMetadata,
+        ];
+    }
+
+    public function loadClassMetadata(LoadClassMetadataEventArgs $args): void
+    {
+        $classMetadata = $args->getClassMetadata();
+
+        $entityListeners = $this
+            ->doctrineEntityListenerCollection
+            ->listeners
+            ->get($classMetadata->name, null)
+        ;
+
+        if (is_null($entityListeners)) {
+            return;
+        }
+
+        foreach ($entityListeners as $entityListener) {
+            EntityListenerBuilder::bindEntityListener($classMetadata, $entityListener::class);
+        }
+    }
+}
diff --git a/src/SingletonCollection.php b/src/SingletonCollection.php
index 33d2aa227dfa7a52f23b8df1113eb808dcc324ff..f1204287d1e4ff42092820cfd7d8120f2b9eb3df 100644
--- a/src/SingletonCollection.php
+++ b/src/SingletonCollection.php
@@ -9,6 +9,8 @@ enum SingletonCollection implements SingletonCollectionInterface
     case AuthenticatedUserStore;
     case CronJob;
     case CrudActionGate;
+    case DoctrineEntityListener;
+    case DoctrineEventListener;
     case EventListener;
     case GraphQLRootField;
     case HttpControllerParameterResolver;
diff --git a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php
index 14fe195d546da93bcfc2ab1036749e30581aa416..dddd3e439adbb2d72db86b39b093cdfda5579dd6 100644
--- a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php
+++ b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php
@@ -25,7 +25,7 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider;
  *         driver: string,
  *         host: non-empty-string,
  *         log_queries: bool,
- *         password: string,
+ *         password: null|non-empty-string,
  *         pool_prefill: bool,
  *         pool_size: int,
  *         port: int,
diff --git a/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php b/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php
index e3e216d7f1becfcdc01b36175f8a596a51af69ac..a79340aad2566e49ecf0c381e44eb04c408635fb 100644
--- a/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php
+++ b/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php
@@ -43,8 +43,12 @@ final readonly class DatabaseConnectionPoolRepositoryProvider extends SingletonP
 
             $pdoConfig->withDbName($connectionPoolConfiguration->database);
             $pdoConfig->withDriver($connectionPoolConfiguration->driver->value);
+
             $pdoConfig->withUsername($connectionPoolConfiguration->username);
-            $pdoConfig->withPassword($connectionPoolConfiguration->password);
+
+            if (is_string($connectionPoolConfiguration->password)) {
+                $pdoConfig->withPassword($connectionPoolConfiguration->password);
+            }
 
             $pdoPool = new PDOPool(
                 $this->pdoPoolConnectionBuilderCollection->getBuildersForConnection($name),
diff --git a/src/SingletonProvider/DoctrineEntityListenerCollectionProvider.php b/src/SingletonProvider/DoctrineEntityListenerCollectionProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..6150c6a929af95efccb7fdd61b3062dab1a6f4d1
--- /dev/null
+++ b/src/SingletonProvider/DoctrineEntityListenerCollectionProvider.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\SingletonProvider;
+
+use Distantmagic\Resonance\Attribute\GrantsFeature;
+use Distantmagic\Resonance\Attribute\ListensToDoctrineEntityEvents;
+use Distantmagic\Resonance\Attribute\RequiresSingletonCollection;
+use Distantmagic\Resonance\Attribute\Singleton;
+use Distantmagic\Resonance\DoctrineEntityListenerCollection;
+use Distantmagic\Resonance\Feature;
+use Distantmagic\Resonance\PHPProjectFiles;
+use Distantmagic\Resonance\SingletonCollection;
+use Distantmagic\Resonance\SingletonContainer;
+use Distantmagic\Resonance\SingletonContainerAttributeIterator;
+use Distantmagic\Resonance\SingletonProvider;
+
+/**
+ * @template-extends SingletonProvider<DoctrineEntityListenerCollection>
+ */
+#[GrantsFeature(Feature::Doctrine)]
+#[RequiresSingletonCollection(SingletonCollection::DoctrineEntityListener)]
+#[Singleton(provides: DoctrineEntityListenerCollection::class)]
+final readonly class DoctrineEntityListenerCollectionProvider extends SingletonProvider
+{
+    public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): DoctrineEntityListenerCollection
+    {
+        $entityListenerCollection = new DoctrineEntityListenerCollection();
+
+        foreach (new SingletonContainerAttributeIterator($singletons, ListensToDoctrineEntityEvents::class) as $listenerAttribute) {
+            $entityListenerCollection->addEntityListener(
+                $listenerAttribute->attribute->className,
+                $listenerAttribute->singleton,
+            );
+        }
+
+        return $entityListenerCollection;
+    }
+}
diff --git a/src/SingletonProvider/DoctrineEntityListenerResolverProvider.php b/src/SingletonProvider/DoctrineEntityListenerResolverProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..546f2277484298b63e6836661d02cba66f25cd8b
--- /dev/null
+++ b/src/SingletonProvider/DoctrineEntityListenerResolverProvider.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\SingletonProvider;
+
+use Distantmagic\Resonance\Attribute\GrantsFeature;
+use Distantmagic\Resonance\Attribute\RequiresSingletonCollection;
+use Distantmagic\Resonance\Attribute\Singleton;
+use Distantmagic\Resonance\DoctrineEntityListenerCollection;
+use Distantmagic\Resonance\DoctrineEntityListenerResolver;
+use Distantmagic\Resonance\Feature;
+use Distantmagic\Resonance\PHPProjectFiles;
+use Distantmagic\Resonance\SingletonCollection;
+use Distantmagic\Resonance\SingletonContainer;
+use Distantmagic\Resonance\SingletonProvider;
+
+/**
+ * @template-extends SingletonProvider<DoctrineEntityListenerResolver>
+ */
+#[GrantsFeature(Feature::Doctrine)]
+#[RequiresSingletonCollection(SingletonCollection::DoctrineEntityListener)]
+#[Singleton(provides: DoctrineEntityListenerResolver::class)]
+final readonly class DoctrineEntityListenerResolverProvider extends SingletonProvider
+{
+    public function __construct(
+        private DoctrineEntityListenerCollection $doctrineEntityListenerCollection,
+    ) {}
+
+    public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): DoctrineEntityListenerResolver
+    {
+        $entityListenerResolver = new DoctrineEntityListenerResolver();
+
+        foreach ($this->doctrineEntityListenerCollection->listeners as $listeners) {
+            foreach ($listeners as $listener) {
+                $entityListenerResolver->register($listener);
+            }
+        }
+
+        return $entityListenerResolver;
+    }
+}
diff --git a/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php b/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php
index cc77f59f9df5ad6d881f28315a861ff9fc53e4ef..6167b487198daff56221fb5360115ca370c541e5 100644
--- a/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php
+++ b/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php
@@ -14,6 +14,7 @@ use Distantmagic\Resonance\Feature;
 use Distantmagic\Resonance\PHPProjectFiles;
 use Distantmagic\Resonance\SingletonContainer;
 use Distantmagic\Resonance\SingletonProvider;
+use Doctrine\Common\EventManager;
 use Doctrine\ORM\Configuration;
 use Symfony\Component\Filesystem\Filesystem;
 
@@ -26,15 +27,17 @@ final readonly class DoctrineEntityManagerRepositoryProvider extends SingletonPr
 {
     public function __construct(
         private ApplicationConfiguration $applicationConfiguration,
-        private DoctrineConnectionRepository $doctrineConnectionRepository,
         private Configuration $configuration,
+        private DoctrineConnectionRepository $doctrineConnectionRepository,
+        private EventManager $eventManager,
     ) {}
 
     public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): DoctrineEntityManagerRepository
     {
         $doctrineEntityManagerRepository = new DoctrineEntityManagerRepository(
-            $this->doctrineConnectionRepository,
             $this->configuration,
+            $this->doctrineConnectionRepository,
+            $this->eventManager,
         );
 
         if (Environment::Development !== $this->applicationConfiguration->environment) {
diff --git a/src/SingletonProvider/DoctrineEventManagerProvider.php b/src/SingletonProvider/DoctrineEventManagerProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..6de1db1784c82cee1c94fb587cd8d5bfd7adbda8
--- /dev/null
+++ b/src/SingletonProvider/DoctrineEventManagerProvider.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\SingletonProvider;
+
+use Distantmagic\Resonance\Attribute\ListensToDoctrineEvents;
+use Distantmagic\Resonance\Attribute\RequiresSingletonCollection;
+use Distantmagic\Resonance\Attribute\Singleton;
+use Distantmagic\Resonance\PHPProjectFiles;
+use Distantmagic\Resonance\SingletonAttribute;
+use Distantmagic\Resonance\SingletonCollection;
+use Distantmagic\Resonance\SingletonContainer;
+use Distantmagic\Resonance\SingletonProvider;
+use Doctrine\Common\EventManager;
+use Doctrine\Common\EventSubscriber;
+
+/**
+ * @template-extends SingletonProvider<EventManager>
+ */
+#[RequiresSingletonCollection(SingletonCollection::DoctrineEventListener)]
+#[Singleton(provides: EventManager::class)]
+final readonly class DoctrineEventManagerProvider extends SingletonProvider
+{
+    public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): EventManager
+    {
+        $eventManger = new EventManager();
+
+        foreach ($this->collectEventSubscribers($singletons) as $subscriberAttribute) {
+            $eventManger->addEventSubscriber($subscriberAttribute->singleton);
+        }
+
+        return $eventManger;
+    }
+
+    /**
+     * @return iterable<SingletonAttribute<EventSubscriber,ListensToDoctrineEvents>>
+     */
+    private function collectEventSubscribers(SingletonContainer $singletons): iterable
+    {
+        return $this->collectAttributes(
+            $singletons,
+            EventSubscriber::class,
+            ListensToDoctrineEvents::class,
+        );
+    }
+}
diff --git a/src/SingletonProvider/DoctrineORMConfigurationProvider.php b/src/SingletonProvider/DoctrineORMConfigurationProvider.php
index 7db879250d693b24f01496c489a7e8736e00b388..5167ace066b5617bb0b0cd7a8f748371cef4d45f 100644
--- a/src/SingletonProvider/DoctrineORMConfigurationProvider.php
+++ b/src/SingletonProvider/DoctrineORMConfigurationProvider.php
@@ -8,6 +8,7 @@ use Distantmagic\Resonance\ApplicationConfiguration;
 use Distantmagic\Resonance\Attribute\GrantsFeature;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Distantmagic\Resonance\DoctrineAttributeDriver;
+use Distantmagic\Resonance\DoctrineEntityListenerResolver;
 use Distantmagic\Resonance\Environment;
 use Distantmagic\Resonance\Feature;
 use Distantmagic\Resonance\PHPProjectFiles;
@@ -28,18 +29,22 @@ final readonly class DoctrineORMConfigurationProvider extends SingletonProvider
     public function __construct(
         private ApplicationConfiguration $applicationConfiguration,
         private DoctrineAttributeDriver $doctrineAttributeDriver,
+        private DoctrineEntityListenerResolver $doctrineEntityListenerResolver,
     ) {}
 
     public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): Configuration
     {
         $isDevMode = Environment::Development === $this->applicationConfiguration->environment;
 
+        $cache = new ArrayAdapter(storeSerialized: false);
+
         $configuration = ORMSetup::createConfiguration(
-            cache: new ArrayAdapter(storeSerialized: false),
+            cache: $cache,
             proxyDir: DM_ROOT.'/cache/doctrine',
             isDevMode: $isDevMode,
         );
 
+        $configuration->setEntityListenerResolver($this->doctrineEntityListenerResolver);
         $configuration->setMetadataDriverImpl($this->doctrineAttributeDriver);
         $configuration->setAutoGenerateProxyClasses(
             $isDevMode
diff --git a/src/SingletonProvider/MailerRepositoryProvider.php b/src/SingletonProvider/MailerRepositoryProvider.php
index 5f7e0958ce0b47a05d4d4dcbcbfb187710710043..8850a8b837848842ee7b9175120a0a55685a9595 100644
--- a/src/SingletonProvider/MailerRepositoryProvider.php
+++ b/src/SingletonProvider/MailerRepositoryProvider.php
@@ -5,7 +5,6 @@ declare(strict_types=1);
 namespace Distantmagic\Resonance\SingletonProvider;
 
 use Distantmagic\Resonance\Attribute\Singleton;
-use Distantmagic\Resonance\EventDispatcherInterface;
 use Distantmagic\Resonance\Mailer;
 use Distantmagic\Resonance\MailerConfiguration;
 use Distantmagic\Resonance\MailerRepository;
@@ -28,7 +27,6 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
 final readonly class MailerRepositoryProvider extends SingletonProvider
 {
     public function __construct(
-        private EventDispatcherInterface $eventDispatcher,
         private HttpClientInterface $httpClient,
         private LoggerInterface $logger,
         private MailerConfiguration $mailerConfiguration,
@@ -96,7 +94,6 @@ final readonly class MailerRepositoryProvider extends SingletonProvider
     {
         return Transport::fromDsn(
             client: $this->httpClient,
-            dispatcher: $this->eventDispatcher,
             dsn: $transportConfiguration->transportDsn,
             logger: $this->logger,
         );