diff --git a/CHANGELOG.md b/CHANGELOG.md index 668547a..afdae4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] +### Added +* *Nothing* + +### Changed +* Extract configuration-related code from `EntityManagerFactory` into `ConfigurationFactory`. + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [6.4.0] - 2024-10-27 ### Added * Add support for `endroid/qr-code` 6.0 diff --git a/config/doctrine.config.php b/config/doctrine.config.php index 7865b66..e324794 100644 --- a/config/doctrine.config.php +++ b/config/doctrine.config.php @@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Common; use Doctrine\DBAL\Connection; +use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; return [ @@ -19,6 +20,7 @@ 'dependencies' => [ 'factories' => [ + Configuration::class => Doctrine\ConfigurationFactory::class, EntityManager::class => Doctrine\EntityManagerFactory::class, Connection::class => Doctrine\ConnectionFactory::class, Doctrine\NoDbNameConnectionFactory::SERVICE_NAME => Doctrine\NoDbNameConnectionFactory::class, diff --git a/src/Doctrine/ConfigurationFactory.php b/src/Doctrine/ConfigurationFactory.php new file mode 100644 index 0000000..5664f04 --- /dev/null +++ b/src/Doctrine/ConfigurationFactory.php @@ -0,0 +1,55 @@ +get('config'); + $isDevMode = (bool) ($globalConfig['debug'] ?? false); + $cache = $container->get(CacheItemPoolInterface::class); + $emConfig = $globalConfig['entity_manager'] ?? []; + $ormConfig = $emConfig['orm'] ?? []; + $funcStyle = $ormConfig['load_mappings_using_functional_style'] ?? false; + $defaultRepo = $ormConfig['default_repository_classname'] ?? null; + + $this->registerTypes($ormConfig); + + $config = new Configuration(); + $config->setMetadataCache($cache); + $config->setQueryCache($cache); + $config->setResultCache($cache); + $config->setProxyDir($ormConfig['proxies_dir'] ?? ''); + $config->setProxyNamespace('DoctrineProxies'); + $config->setAutoGenerateProxyClasses($isDevMode); + $config->setMetadataDriverImpl( + new EnhancedPHPDriver($ormConfig['entities_mappings'] ?? [], $emConfig, $funcStyle), + ); + + if ($defaultRepo !== null) { + $config->setDefaultRepositoryClassName($defaultRepo); + } + + return $config; + } + + private function registerTypes(array $ormConfig): void + { + $types = $ormConfig['types'] ?? []; + + foreach ($types as $name => $className) { + if (! Type::hasType($name)) { + Type::addType($name, $className); + } + } + } +} diff --git a/src/Doctrine/EntityManagerFactory.php b/src/Doctrine/EntityManagerFactory.php index e937a76..18a042f 100644 --- a/src/Doctrine/EntityManagerFactory.php +++ b/src/Doctrine/EntityManagerFactory.php @@ -5,74 +5,29 @@ namespace Shlinkio\Shlink\Common\Doctrine; use Doctrine\DBAL\DriverManager; -use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; -use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface; -use Shlinkio\Shlink\Common\Doctrine\Mapping\EnhancedPHPDriver; class EntityManagerFactory { public function __invoke(ContainerInterface $container): EntityManager { $globalConfig = $container->get('config'); - $isDevMode = (bool) ($globalConfig['debug'] ?? false); - $cache = $container->get(CacheItemPoolInterface::class); + $ormConfig = $container->get(Configuration::class); + $emConfig = $globalConfig['entity_manager'] ?? []; $connectionConfig = $emConfig['connection'] ?? []; - $ormConfig = $emConfig['orm'] ?? []; - $funcStyle = $ormConfig['load_mappings_using_functional_style'] ?? false; - $defaultRepo = $ormConfig['default_repository_classname'] ?? null; - - $this->registerTypes($ormConfig); - - $config = $this->createConfiguration($isDevMode, $ormConfig['proxies_dir'] ?? '', $cache); - $config->setMetadataDriverImpl( - new EnhancedPHPDriver($ormConfig['entities_mappings'] ?? [], $emConfig, $funcStyle), - ); - - if ($defaultRepo !== null) { - $config->setDefaultRepositoryClassName($defaultRepo); - } + $em = new EntityManager(DriverManager::getConnection($connectionConfig, $ormConfig), $ormConfig); - $em = new EntityManager(DriverManager::getConnection($connectionConfig, $config), $config); - - $this->registerListeners($ormConfig, $em, $container); + $this->registerListeners($emConfig['orm']['listeners'] ?? [], $em, $container); return $em; } - private function registerTypes(array $ormConfig): void - { - $types = $ormConfig['types'] ?? []; - - foreach ($types as $name => $className) { - if (! Type::hasType($name)) { - Type::addType($name, $className); - } - } - } - - private function createConfiguration(bool $isDev, string $proxyDir, CacheItemPoolInterface $cache): Configuration + private function registerListeners(array $listeners, EntityManager $em, ContainerInterface $container): void { - $config = new Configuration(); - - $config->setMetadataCache($cache); - $config->setQueryCache($cache); - $config->setResultCache($cache); - $config->setProxyDir($proxyDir); - $config->setProxyNamespace('DoctrineProxies'); - $config->setAutoGenerateProxyClasses($isDev); - - return $config; - } - - private function registerListeners(array $ormConfig, EntityManager $em, ContainerInterface $container): void - { - $listeners = $ormConfig['listeners'] ?? []; $events = $em->getEventManager(); - foreach ($listeners as $event => $services) { foreach ($services as $service) { $events->addEventListener($event, $container->get($service)); diff --git a/test/Doctrine/ConfigurationFactoryTest.php b/test/Doctrine/ConfigurationFactoryTest.php new file mode 100644 index 0000000..5b22dfd --- /dev/null +++ b/test/Doctrine/ConfigurationFactoryTest.php @@ -0,0 +1,106 @@ +getProperty('instances'); + $instancesProp->setAccessible(true); + $withoutChronosType = array_filter( + $typeRegistry->getMap(), + fn (string $key): bool => $key !== ChronosDateTimeType::CHRONOS_DATETIME, + ARRAY_FILTER_USE_KEY, + ); + $instancesProp->setValue($typeRegistry, $withoutChronosType); + } + + $this->factory = new ConfigurationFactory(); + } + + #[Test, DataProvider('provideConfig')] + public function serviceIsCreated( + array $config, + int $expectedAutoGenerateProxies, + string $expectedDefaultRepo, + ): void { + $sm = new ServiceManager(['services' => [ + 'config' => $config, + CacheItemPoolInterface::class => new ArrayAdapter(), + ]]); + + self::assertFalse(Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)); + $config = ($this->factory)($sm); + + self::assertTrue(Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)); + self::assertEquals($expectedAutoGenerateProxies, $config->getAutoGenerateProxyClasses()); + self::assertEquals(__DIR__, $config->getProxyDir()); + self::assertEquals($expectedDefaultRepo, $config->getDefaultRepositoryClassName()); + + /** @var PHPDriver $metaDriver */ + $metaDriver = $config->getMetadataDriverImpl(); + self::assertEquals([__FILE__], $metaDriver->getLocator()->getPaths()); + } + + public static function provideConfig(): iterable + { + $baseConfig = [ + 'entity_manager' => [ + 'orm' => [ + 'types' => [ + ChronosDateTimeType::CHRONOS_DATETIME => ChronosDateTimeType::class, + ], + 'proxies_dir' => __DIR__, + 'entities_mappings' => [__FILE__], + ], + 'connection' => [ + 'driver' => 'pdo_sqlite', + ], + ], + ]; + + yield [array_merge($baseConfig, ['debug' => true]), 1, EntityRepository::class]; + yield [array_merge($baseConfig, ['debug' => '1']), 1, EntityRepository::class]; + yield [array_merge($baseConfig, ['debug' => 'true']), 1, EntityRepository::class]; + yield [array_merge($baseConfig, ['debug' => false]), 0, EntityRepository::class]; + yield [array_merge($baseConfig, ['debug' => null]), 0, EntityRepository::class]; + yield [array_merge($baseConfig, ['debug' => null]), 0, EntityRepository::class]; + yield [ + array_merge_recursive($baseConfig, [ + 'entity_manager' => [ + 'orm' => ['default_repository_classname' => CustomRepository::class], + ], + ]), + 0, + CustomRepository::class, + ]; + } +} diff --git a/test/Doctrine/EntityManagerFactoryTest.php b/test/Doctrine/EntityManagerFactoryTest.php index 34648fe..78b4e31 100644 --- a/test/Doctrine/EntityManagerFactoryTest.php +++ b/test/Doctrine/EntityManagerFactoryTest.php @@ -5,78 +5,52 @@ namespace ShlinkioTest\Shlink\Common\Doctrine; use Doctrine\DBAL\Driver\PDO\SQLite\Driver as SQLiteDriver; -use Doctrine\DBAL\Types\Type; -use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Configuration; use Doctrine\ORM\Events; -use Doctrine\Persistence\Mapping\Driver\PHPDriver; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Laminas\ServiceManager\ServiceManager; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use Psr\Cache\CacheItemPoolInterface; use ReflectionObject; use Shlinkio\Shlink\Common\Doctrine\EntityManagerFactory; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; use ShlinkioTest\Shlink\Common\Repository\CustomRepository; use stdClass; -use Symfony\Component\Cache\Adapter\ArrayAdapter; -use function array_filter; use function array_merge; use function array_merge_recursive; use function count; -use const ARRAY_FILTER_USE_KEY; - class EntityManagerFactoryTest extends TestCase { private EntityManagerFactory $factory; public function setUp(): void { - if (Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)) { - $typeRegistry = Type::getTypeRegistry(); - $ref = new ReflectionObject($typeRegistry); - $instancesProp = $ref->getProperty('instances'); - $instancesProp->setAccessible(true); - $withoutChronosType = array_filter( - $typeRegistry->getMap(), - fn (string $key): bool => $key !== ChronosDateTimeType::CHRONOS_DATETIME, - ARRAY_FILTER_USE_KEY, - ); - $instancesProp->setValue($typeRegistry, $withoutChronosType); - } - $this->factory = new EntityManagerFactory(); } #[Test, DataProvider('provideConfig')] public function serviceIsCreated( array $config, - int $expectedAutoGenerateProxies, - string $expectedDefaultRepo, int $expectedListeners, ): void { + $ormConfig = new Configuration(); + $ormConfig->setMetadataDriverImpl($this->createMock(MappingDriver::class)); + $ormConfig->setProxyDir(__DIR__); + $ormConfig->setProxyNamespace('DoctrineProxies'); + $sm = new ServiceManager(['services' => [ 'config' => $config, 'foo_listener' => new stdClass(), 'bar_listener' => new stdClass(), 'baz_listener' => new stdClass(), - CacheItemPoolInterface::class => new ArrayAdapter(), + Configuration::class => $ormConfig, ]]); - self::assertFalse(Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)); $em = ($this->factory)($sm); - - self::assertTrue(Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)); - self::assertEquals($expectedAutoGenerateProxies, $em->getConfiguration()->getAutoGenerateProxyClasses()); - self::assertEquals(__DIR__, $em->getConfiguration()->getProxyDir()); self::assertInstanceOf(SQLiteDriver::class, $em->getConnection()->getDriver()); - self::assertEquals($expectedDefaultRepo, $em->getConfiguration()->getDefaultRepositoryClassName()); - - /** @var PHPDriver $metaDriver */ - $metaDriver = $em->getConfiguration()->getMetadataDriverImpl(); - self::assertEquals([__FILE__], $metaDriver->getLocator()->getPaths()); $events = $em->getEventManager(); $ref = new ReflectionObject($events); @@ -109,12 +83,12 @@ public static function provideConfig(): iterable ], ]; - yield [array_merge($baseConfig, ['debug' => true]), 1, EntityRepository::class, 0]; - yield [array_merge($baseConfig, ['debug' => '1']), 1, EntityRepository::class, 0]; - yield [array_merge($baseConfig, ['debug' => 'true']), 1, EntityRepository::class, 0]; - yield [array_merge($baseConfig, ['debug' => false]), 0, EntityRepository::class, 0]; - yield [array_merge($baseConfig, ['debug' => null]), 0, EntityRepository::class, 0]; - yield [array_merge($baseConfig, ['debug' => null]), 0, EntityRepository::class, 0]; + yield [array_merge($baseConfig, ['debug' => true]), 0]; + yield [array_merge($baseConfig, ['debug' => '1']), 0]; + yield [array_merge($baseConfig, ['debug' => 'true']), 0]; + yield [array_merge($baseConfig, ['debug' => false]), 0]; + yield [array_merge($baseConfig, ['debug' => null]), 0]; + yield [array_merge($baseConfig, ['debug' => null]), 0]; yield [ array_merge_recursive($baseConfig, [ 'entity_manager' => [ @@ -122,8 +96,6 @@ public static function provideConfig(): iterable ], ]), 0, - CustomRepository::class, - 0, ]; yield [ array_merge_recursive($baseConfig, [ @@ -136,8 +108,6 @@ public static function provideConfig(): iterable ], ], ]), - 0, - EntityRepository::class, 5, ]; }