Skip to content

Commit dcf139f

Browse files
committed
ManagerRegistry: reset also persisters in UnitOfWork
UnitOfWork is cleared after EntityManager failure, but it keeps cached EntityPersister inside. So if you want to e.g. create other insert query, UoW uses the same persister as before failure. The EntityPersister has its own cache for inserts and it tries to insert previous query with empty values. Using this EntityManager with same persister fails though.
1 parent d3fe320 commit dcf139f

2 files changed

Lines changed: 77 additions & 14 deletions

File tree

src/ManagerRegistry.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Nettrine\ORM;
44

55
use Doctrine\ORM\EntityManager;
6+
use Doctrine\ORM\EntityManagerInterface;
7+
use Doctrine\ORM\UnitOfWork;
68
use Doctrine\Persistence\AbstractManagerRegistry;
79
use Doctrine\Persistence\ObjectManagerDecorator;
810
use Doctrine\Persistence\Proxy;
@@ -36,19 +38,30 @@ public function __construct(
3638
);
3739
}
3840

39-
/**
40-
* @param ObjectManagerDecorator<EntityManager>|EntityManager $manager
41-
*/
42-
public static function reopen(ObjectManagerDecorator|EntityManager $manager): void
41+
public static function reopen(EntityManagerInterface $manager): void
4342
{
4443
// @phpcs:disable
4544
Binder::use($manager, function (): void {
4645
if ($this instanceof EntityManager) { // @phpstan-ignore-line
4746
$this->closed = false; // @phpstan-ignore-line
47+
48+
$uow = $this->getUnitOfWork();
49+
Binder::use($uow, function (): void {
50+
if ($this instanceof UnitOfWork) { // @phpstan-ignore-line
51+
$this->persisters = [];
52+
}
53+
});
4854
} elseif ($this instanceof ObjectManagerDecorator) {
4955
Binder::use($this->wrapped, function (): void { // @phpstan-ignore-line
5056
if ($this instanceof EntityManager) { // @phpstan-ignore-line
5157
$this->closed = false;
58+
59+
$uow = $this->getUnitOfWork();
60+
Binder::use($uow, function (): void {
61+
if ($this instanceof UnitOfWork) { // @phpstan-ignore-line
62+
$this->persisters = [];
63+
}
64+
});
5265
}
5366
});
5467
}

tests/Cases/ManagerRegistry.phpt

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use Contributte\Tester\Toolkit;
44
use Contributte\Tester\Utils\ContainerBuilder;
55
use Contributte\Tester\Utils\Neonkit;
66
use Doctrine\DBAL\Connection;
7-
use Doctrine\ORM\EntityManager;
7+
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
88
use Doctrine\ORM\EntityManagerInterface;
99
use Doctrine\Persistence\ManagerRegistry;
1010
use Nette\DI\Compiler;
@@ -368,6 +368,7 @@ Toolkit::test(function (): void {
368368
$compiler->addConfig([
369369
'parameters' => [
370370
'tempDir' => Tests::TEMP_PATH,
371+
'fixturesDir' => Tests::FIXTURES_PATH,
371372
],
372373
]);
373374
$compiler->addConfig(Neonkit::load(
@@ -379,30 +380,79 @@ Toolkit::test(function (): void {
379380
password: test
380381
user: test
381382
path: ":memory:"
383+
second:
384+
driver: pdo_sqlite
385+
password: test
386+
user: test
387+
path: ":memory:"
382388
nettrine.orm:
383389
managers:
384390
default:
385391
connection: default
386392
mapping:
387393
App:
388394
type: attributes
389-
directories: [app/Database]
390-
namespace: App\Database
395+
directories: [%fixturesDir%/Entity]
396+
namespace: Tests\Mocks\Entity
397+
second:
398+
connection: second
399+
entityManagerDecoratorClass: Tests\Mocks\DummyEntityManagerDecorator
400+
mapping:
401+
App:
402+
type: attributes
403+
directories: [%fixturesDir%/Entity]
404+
namespace: Tests\Mocks\Entity
391405
NEON
392406
));
393407
})
394408
->build();
395409

396-
/** @var EntityManager $em */
397-
$em = $container->getByType(EntityManagerInterface::class);
410+
/** @var ManagerRegistry $registry */
411+
$registry = $container->getByType(ManagerRegistry::class);
412+
413+
foreach (['default', 'second'] as $managerName) {
414+
/** @var EntityManagerInterface $em */
415+
$em = $registry->getManager($managerName);
416+
Assert::true($em->isOpen());
417+
418+
/** @var Connection $connection */
419+
$connection = $registry->getConnection($managerName);
420+
421+
$connection->executeQuery('CREATE TABLE dummy_entity (id integer primary key autoincrement, username string unique not null)');
422+
423+
$persister1 = $em->getUnitOfWork()->getEntityPersister(DummyEntity::class);
398424

399-
Assert::true($em->isOpen());
400-
$em->close();
401-
Assert::false($em->isOpen());
425+
$em->persist(new DummyEntity('test'));
426+
$em->flush();
402427

403-
NettrineManagerRegistry::reopen($em);
428+
Assert::count(0, $persister1->getInserts(), 'Persister should have no inserts, have ' . count($persister1->getInserts()));
404429

405-
Assert::true($em->isOpen());
430+
// try to create a new entity with the same username - not unique
431+
$em->persist(new DummyEntity('test'));
432+
try {
433+
$em->flush();
434+
} catch (UniqueConstraintViolationException) {
435+
Assert::false($em->isOpen());
436+
}
437+
438+
// after failing queue there is cached last insert
439+
Assert::count(1, $persister1->getInserts(), 'Persister should have 1 insert, have ' . count($persister1->getInserts()));
440+
441+
NettrineManagerRegistry::reopen($em);
442+
443+
Assert::true($em->isOpen());
444+
445+
$persister2 = $em->getUnitOfWork()->getEntityPersister(DummyEntity::class);
446+
447+
// check if the persister is different after reopening
448+
Assert::notSame($persister1, $persister2);
449+
450+
$dummy2 = new DummyEntity('test2');
451+
$em->persist($dummy2);
452+
$em->flush();
453+
454+
Assert::count(2, $em->getRepository(DummyEntity::class)->findAll());
455+
}
406456
});
407457

408458
// Get repository for manager

0 commit comments

Comments
 (0)