Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract configuration-related code from EntityManagerFactory into ConfigurationFactory #155

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions config/doctrine.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Shlinkio\Shlink\Common;

use Doctrine\DBAL\Connection;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;

return [
Expand All @@ -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,
Expand Down
55 changes: 55 additions & 0 deletions src/Doctrine/ConfigurationFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Common\Doctrine;

use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Configuration;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Container\ContainerInterface;
use Shlinkio\Shlink\Common\Doctrine\Mapping\EnhancedPHPDriver;

class ConfigurationFactory
{
public function __invoke(ContainerInterface $container): Configuration
{
$globalConfig = $container->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);
}
}
}
}
55 changes: 5 additions & 50 deletions src/Doctrine/EntityManagerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
106 changes: 106 additions & 0 deletions test/Doctrine/ConfigurationFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

namespace ShlinkioTest\Shlink\Common\Doctrine;

use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\Mapping\Driver\PHPDriver;
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\ConfigurationFactory;
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
use ShlinkioTest\Shlink\Common\Repository\CustomRepository;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

use function array_filter;
use function array_merge;
use function array_merge_recursive;

use const ARRAY_FILTER_USE_KEY;

class ConfigurationFactoryTest extends TestCase
{
private ConfigurationFactory $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 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,
];
}
}
Loading