diff --git a/config/autoload/entity-manager.global.php b/config/autoload/entity-manager.global.php index 951c909f..04f999d9 100644 --- a/config/autoload/entity-manager.global.php +++ b/config/autoload/entity-manager.global.php @@ -1,7 +1,7 @@ [ 'factories' => [ - EntityManager::class => Factory\EntityManagerFactory::class, GuzzleClient::class => InvokableFactory::class, Cache::class => Factory\CacheFactory::class, Filesystem::class => InvokableFactory::class, @@ -45,7 +43,6 @@ return [ Service\PreviewGenerator::class => ConfigAbstractFactory::class, ], 'aliases' => [ - 'em' => EntityManager::class, 'httpClient' => GuzzleClient::class, 'translator' => Translator::class, diff --git a/module/Common/config/doctrine.config.php b/module/Common/config/doctrine.config.php new file mode 100644 index 00000000..66d678af --- /dev/null +++ b/module/Common/config/doctrine.config.php @@ -0,0 +1,32 @@ + [ + 'orm' => [ + 'types' => [ + Type\ChronosDateTimeType::CHRONOS_DATETIME => Type\ChronosDateTimeType::class, + ], + ], + ], + + 'dependencies' => [ + 'factories' => [ + EntityManager::class => Doctrine\EntityManagerFactory::class, + ], + 'aliases' => [ + 'em' => EntityManager::class, + ], + 'delegators' => [ + EntityManager::class => [ + Doctrine\ReopeningEntityManagerDelegator::class, + ], + ], + ], + +]; diff --git a/module/Common/src/Factory/EntityManagerFactory.php b/module/Common/src/Doctrine/EntityManagerFactory.php similarity index 52% rename from module/Common/src/Factory/EntityManagerFactory.php rename to module/Common/src/Doctrine/EntityManagerFactory.php index b1de186b..43847645 100644 --- a/module/Common/src/Factory/EntityManagerFactory.php +++ b/module/Common/src/Doctrine/EntityManagerFactory.php @@ -1,7 +1,7 @@ get('config'); - $isDevMode = isset($globalConfig['debug']) ? ((bool) $globalConfig['debug']) : false; + $isDevMode = (bool) ($globalConfig['debug'] ?? false); $cache = $container->has(Cache::class) ? $container->get(Cache::class) : new ArrayCache(); $emConfig = $globalConfig['entity_manager'] ?? []; $connectionConfig = $emConfig['connection'] ?? []; $ormConfig = $emConfig['orm'] ?? []; - if (! Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)) { - Type::addType(ChronosDateTimeType::CHRONOS_DATETIME, ChronosDateTimeType::class); - } + $this->registerTypes($ormConfig); $config = Setup::createConfiguration($isDevMode, $ormConfig['proxies_dir'] ?? null, $cache); $config->setMetadataDriverImpl(new PHPDriver($ormConfig['entities_mappings'] ?? [])); return EntityManager::create($connectionConfig, $config); } + + /** + * @throws DBALException + */ + 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/module/Common/src/Doctrine/ReopeningEntityManager.php b/module/Common/src/Doctrine/ReopeningEntityManager.php new file mode 100644 index 00000000..134dcd22 --- /dev/null +++ b/module/Common/src/Doctrine/ReopeningEntityManager.php @@ -0,0 +1,48 @@ +wrapped->isOpen()) { + $this->wrapped= EntityManager::create( + $this->wrapped->getConnection(), + $this->wrapped->getConfiguration(), + $this->wrapped->getEventManager() + ); + } + + return $this->wrapped; + } + + public function flush($entity = null): void + { + $this->getWrappedEntityManager()->flush($entity); + } + + public function persist($object): void + { + $this->getWrappedEntityManager()->persist($object); + } + + public function remove($object): void + { + $this->getWrappedEntityManager()->remove($object); + } + + public function refresh($object): void + { + $this->getWrappedEntityManager()->refresh($object); + } + + public function merge($object) + { + return $this->getWrappedEntityManager()->merge($object); + } +} diff --git a/module/Common/src/Doctrine/ReopeningEntityManagerDelegator.php b/module/Common/src/Doctrine/ReopeningEntityManagerDelegator.php new file mode 100644 index 00000000..2de8b651 --- /dev/null +++ b/module/Common/src/Doctrine/ReopeningEntityManagerDelegator.php @@ -0,0 +1,17 @@ +handle($request); - } catch (Throwable $e) { - // FIXME Mega ugly hack to avoid a closed EntityManager to make shlink fail forever on swoole contexts - // Should be fixed with request-shared EntityManagers, which is not supported by the ServiceManager - if (! $this->em->isOpen()) { - (function () { - $this->closed = false; - })->bindTo($this->em, EntityManager::class)(); - } - - throw $e; } finally { $this->em->getConnection()->close(); $this->em->clear(); diff --git a/module/Common/test/Factory/EntityManagerFactoryTest.php b/module/Common/test/Doctrine/EntityManagerFactoryTest.php similarity index 60% rename from module/Common/test/Factory/EntityManagerFactoryTest.php rename to module/Common/test/Doctrine/EntityManagerFactoryTest.php index 70a969f9..ade8d84d 100644 --- a/module/Common/test/Factory/EntityManagerFactoryTest.php +++ b/module/Common/test/Doctrine/EntityManagerFactoryTest.php @@ -1,11 +1,12 @@ [ 'config' => [ 'debug' => true, 'entity_manager' => [ + 'orm' => [ + 'types' => [ + ChronosDateTimeType::CHRONOS_DATETIME => ChronosDateTimeType::class, + ], + ], 'connection' => [ 'driver' => 'pdo_sqlite', ], @@ -32,7 +38,7 @@ class EntityManagerFactoryTest extends TestCase ], ]]); - $em = $this->factory->__invoke($sm, EntityManager::class); + $em = ($this->factory)($sm, EntityManager::class); $this->assertInstanceOf(EntityManager::class, $em); } } diff --git a/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php b/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php index 34d141ad..2bf93a85 100644 --- a/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php +++ b/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php @@ -10,7 +10,6 @@ use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; use Shlinkio\Shlink\Common\Middleware\CloseDbConnectionMiddleware; -use Throwable; use Zend\Diactoros\Response; use Zend\Diactoros\ServerRequest; @@ -35,7 +34,6 @@ class CloseDbConnectionMiddlewareTest extends TestCase $this->em->getConnection()->willReturn($this->conn->reveal()); $this->em->clear()->will(function () { }); - $this->em->isOpen()->willReturn(true); $this->middleware = new CloseDbConnectionMiddleware($this->em->reveal()); } @@ -71,31 +69,4 @@ class CloseDbConnectionMiddlewareTest extends TestCase $this->middleware->process($req, $this->handler->reveal()); } - - /** - * @test - * @dataProvider provideClosed - */ - public function entityManagerIsReopenedAfterAnExceptionWhichClosedIt(bool $closed): void - { - $req = new ServerRequest(); - $expectedError = new RuntimeException(); - $this->handler->handle($req)->willThrow($expectedError) - ->shouldBeCalledOnce(); - $this->em->closed = $closed; - $this->em->isOpen()->willReturn(false); - - try { - $this->middleware->process($req, $this->handler->reveal()); - $this->fail('Expected exception to be thrown but it did not.'); - } catch (Throwable $e) { - $this->assertSame($expectedError, $e); - $this->assertFalse($this->em->closed); - } - } - - public function provideClosed(): iterable - { - return [[true, false]]; - } }