From 6a348d68de06d2d4aa3beb4255859aa1d7702c7a Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Mon, 11 Apr 2022 10:28:18 +0200 Subject: [PATCH] Enhance connection closed by EM stracktrace. --- src/Decorator/EntityManagerDecorator.php | 5 ++-- src/EntityManager.php | 12 +++++--- src/EntityManagerInterface.php | 3 +- src/Exception/EntityManagerClosed.php | 28 +++++++++++++++++++ .../Mock/NonProxyLoadingEntityManager.php | 5 ++-- .../Decorator/EntityManagerDecoratorTest.php | 10 +++++++ 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/Decorator/EntityManagerDecorator.php b/src/Decorator/EntityManagerDecorator.php index c8007e40ae1..042732b73e8 100644 --- a/src/Decorator/EntityManagerDecorator.php +++ b/src/Decorator/EntityManagerDecorator.php @@ -9,6 +9,7 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\ObjectManagerDecorator; +use Throwable; use function func_get_arg; use function func_num_args; @@ -184,9 +185,9 @@ public function getPartialReference($entityName, $identifier) /** * {@inheritDoc} */ - public function close() + public function close(?Throwable $e = null) { - $this->wrapped->close(); + $this->wrapped->close($e); } /** diff --git a/src/EntityManager.php b/src/EntityManager.php index f7d47d7b12e..6cb724de1da 100644 --- a/src/EntityManager.php +++ b/src/EntityManager.php @@ -152,6 +152,9 @@ class EntityManager implements EntityManagerInterface */ private $cache; + /** @var EntityManagerClosed|null */ + private $closedWhere = null; + /** * Creates a new EntityManager that operates on the given database connection * and uses the given Configuration and EventManager implementations. @@ -254,7 +257,7 @@ public function transactional($func) return $return ?: true; } catch (Throwable $e) { - $this->close(); + $this->close($e); $this->conn->rollBack(); throw $e; @@ -637,11 +640,12 @@ public function clear($entityName = null) /** * {@inheritDoc} */ - public function close() + public function close(?Throwable $e = null) { $this->clear(); - $this->closed = true; + $this->closed = true; + $this->closedWhere = EntityManagerClosed::createClosedHere($e); } /** @@ -880,7 +884,7 @@ public function getConfiguration() private function errorIfClosed(): void { if ($this->closed) { - throw EntityManagerClosed::create(); + throw EntityManagerClosed::createWhere($this->closedWhere); } } diff --git a/src/EntityManagerInterface.php b/src/EntityManagerInterface.php index 326cb933a59..84f125e463c 100644 --- a/src/EntityManagerInterface.php +++ b/src/EntityManagerInterface.php @@ -16,6 +16,7 @@ use Doctrine\ORM\Query\FilterCollection; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\ObjectManager; +use Throwable; /** * EntityManager interface @@ -220,7 +221,7 @@ public function getPartialReference($entityName, $identifier); * * @return void */ - public function close(); + public function close(?Throwable $e = null); /** * Creates a copy of the given entity. Can create a shallow or a deep copy. diff --git a/src/Exception/EntityManagerClosed.php b/src/Exception/EntityManagerClosed.php index 403205f5319..21777da8ecd 100644 --- a/src/Exception/EntityManagerClosed.php +++ b/src/Exception/EntityManagerClosed.php @@ -4,10 +4,38 @@ namespace Doctrine\ORM\Exception; +use Throwable; + final class EntityManagerClosed extends ORMException implements ManagerException { public static function create(): self { return new self('The EntityManager is closed.'); } + + public static function createWhere(?Throwable $e = null): self + { + if ($e === null) { + return self::create(); + } + + return new self( + 'The EntityManager is closed (previous allows to trace back to where it was closed).', + 0, + $e + ); + } + + public static function createClosedHere(?Throwable $cause = null): self + { + if ($cause === null) { + return new self('This exception allows to trace back to where close() was called.'); + } + + return new self( + 'This exception allows to trace back to where close() was called (see previous exception for the cause).', + 0, + $cause + ); + } } diff --git a/tests/Performance/Mock/NonProxyLoadingEntityManager.php b/tests/Performance/Mock/NonProxyLoadingEntityManager.php index 55e687ecaf4..770fbd37dfa 100644 --- a/tests/Performance/Mock/NonProxyLoadingEntityManager.php +++ b/tests/Performance/Mock/NonProxyLoadingEntityManager.php @@ -7,6 +7,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Query\ResultSetMapping; +use Throwable; /** * An entity manager mock that prevents lazy-loading of proxies @@ -183,9 +184,9 @@ public function getPartialReference($entityName, $identifier) /** * {@inheritDoc} */ - public function close() + public function close(?Throwable $e = null) { - $this->realEntityManager->close(); + $this->realEntityManager->close($e); } /** diff --git a/tests/Tests/ORM/Decorator/EntityManagerDecoratorTest.php b/tests/Tests/ORM/Decorator/EntityManagerDecoratorTest.php index 55af448fcde..c7e06ed1dbb 100644 --- a/tests/Tests/ORM/Decorator/EntityManagerDecoratorTest.php +++ b/tests/Tests/ORM/Decorator/EntityManagerDecoratorTest.php @@ -8,6 +8,7 @@ use Doctrine\ORM\Decorator\EntityManagerDecorator; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\ResultSetMapping; +use Exception; use Generator; use LogicException; use PHPUnit\Framework\MockObject\MockObject; @@ -80,6 +81,15 @@ static function (): void { ]; } + if ($method->getName() === 'close') { + return [ + $method->getName(), + [ + new Exception(), + ], + ]; + } + $parameters = []; foreach ($method->getParameters() as $parameter) {