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, );