Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 11f55e7

Browse files
authored
feat: throw when withoutDoctrineEvents() is called within flush_after() (#1111)
1 parent f81db0d commit 11f55e7

21 files changed

Lines changed: 750 additions & 382 deletions

.roave-backward-compatibility-check.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
<ignored-regex>#(.*)Zenstruck\\Foundry\\Utils\\Rector(.*)#</ignored-regex>
1010
<ignored-regex>#(.*)initializeInternal(.*)#</ignored-regex>
1111
<ignored-regex>#\[BC\] CHANGED: Method getIdentifierValues\(\) of class Zenstruck\\Foundry\\Persistence\\PersistenceStrategy became final#</ignored-regex>
12+
<ignored-regex>#"Zenstruck\\Foundry\\Test\\(CommonResetDatabase|CommonFactories)" could not be found in the located source(.*)#</ignored-regex>
1213
</baseline>
1314
</roave-bc-check>

bin/tools/bc-check/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"require": {
3-
"roave/backward-compatibility-check": "^8.15"
3+
"roave/backward-compatibility-check": "^8.17"
44
}
55
}

bin/tools/bc-check/composer.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/tools/psalm/composer.lock

Lines changed: 140 additions & 118 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,9 +1319,7 @@ them during object creation to avoid this.
13191319

13201320
::
13211321

1322-
use App\Entity\Post;
13231322
use App\Factory\PostFactory;
1324-
use function Zenstruck\Foundry\Persistence\persistent_factory;
13251323

13261324
// disable ALL Doctrine event listeners during creation
13271325
$post = PostFactory::new()->withoutDoctrineEvents()->create(); // returns Post
@@ -1342,6 +1340,10 @@ If you'd like your factory to always disable Doctrine events, override its ``ini
13421340
;
13431341
}
13441342

1343+
.. note::
1344+
1345+
``withoutDoctrineEvents()`` cannot be used inside ``flush_after()``.
1346+
13451347
Array factories
13461348
~~~~~~~~~~~~~~~
13471349

src/Mongo/MongoPersistenceStrategy.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Zenstruck\Foundry\Mongo;
1313

14+
use Doctrine\Common\EventManager;
1415
use Doctrine\ODM\MongoDB\DocumentManager;
1516
use Doctrine\ODM\MongoDB\Mapping\MappingException as MongoMappingException;
1617
use Doctrine\Persistence\Mapping\MappingException;
@@ -94,4 +95,29 @@ public function getIdentifierValues(object $object): array
9495
{
9596
return $this->classMetadata($object::class)->getIdentifierValues($object);
9697
}
98+
99+
public function withoutDoctrineEvents(string $entityClass, array $disabledClasses, callable $callback): mixed
100+
{
101+
$eventManager = $this->objectManagerFor($entityClass)->getEventManager();
102+
$removed = [];
103+
104+
foreach ($eventManager->getAllListeners() as $eventName => $listeners) {
105+
foreach ($listeners as $listener) {
106+
if ([] === $disabledClasses || \in_array($listener::class, $disabledClasses, true)) {
107+
$eventManager->removeEventListener([$eventName], $listener);
108+
$removed[$eventName][] = $listener;
109+
}
110+
}
111+
}
112+
113+
try {
114+
return $callback();
115+
} finally {
116+
foreach ($removed as $eventName => $listeners) {
117+
foreach ($listeners as $listener) {
118+
$eventManager->addEventListener([$eventName], $listener);
119+
}
120+
}
121+
}
122+
}
97123
}

src/ORM/AbstractORMPersistenceStrategy.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Zenstruck\Foundry\ORM;
1313

14+
use Doctrine\Common\EventManager;
1415
use Doctrine\ORM\EntityManagerInterface;
1516
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
1617
use Doctrine\Persistence\Mapping\MappingException;
18+
use Doctrine\Persistence\ObjectManager;
1719
use Zenstruck\Foundry\Persistence\PersistenceStrategy;
1820

1921
/**
@@ -118,4 +120,103 @@ function(mixed $value) use ($object) {
118120
$identifiers
119121
);
120122
}
123+
124+
public function withoutDoctrineEvents(string $entityClass, array $disabledClasses, callable $callback): mixed
125+
{
126+
$om = $this->objectManagerFor($entityClass);
127+
128+
// Entity listeners first: getClassMetadata() triggers loadClassMetadata which registers
129+
// #[AsEntityListener] listeners. Global listeners must still be active at that point.
130+
$entityListenersBackup = $this->removeEntityListeners($om, $entityClass, $disabledClasses);
131+
$globalListenersBackup = $this->removeGlobalListeners($om, $disabledClasses);
132+
133+
try {
134+
return $callback();
135+
} finally {
136+
$this->restoreGlobalListeners($om, $globalListenersBackup);
137+
$this->restoreEntityListeners($om, $entityClass, $entityListenersBackup);
138+
}
139+
}
140+
141+
/**
142+
* @param list<class-string> $disabledClasses
143+
*
144+
* @return array<string, list<object>>
145+
*/
146+
private function removeGlobalListeners(EntityManagerInterface $om, array $disabledClasses): array
147+
{
148+
$eventManager = $om->getEventManager();
149+
$removed = [];
150+
151+
foreach ($eventManager->getAllListeners() as $eventName => $listeners) {
152+
foreach ($listeners as $listener) {
153+
if ([] === $disabledClasses || \in_array($listener::class, $disabledClasses, true)) {
154+
$eventManager->removeEventListener([$eventName], $listener);
155+
$removed[$eventName][] = $listener;
156+
}
157+
}
158+
}
159+
160+
return $removed;
161+
}
162+
163+
/**
164+
* @param array<string, list<object>> $removedListeners
165+
*/
166+
private function restoreGlobalListeners(EntityManagerInterface $om, array $removedListeners): void
167+
{
168+
$eventManager = $om->getEventManager();
169+
170+
foreach ($removedListeners as $eventName => $listeners) {
171+
foreach ($listeners as $listener) {
172+
$eventManager->addEventListener([$eventName], $listener);
173+
}
174+
}
175+
}
176+
177+
/**
178+
* @param class-string $entityClass
179+
* @param list<class-string> $disabledClasses
180+
*
181+
* @return array<string, list<array{class: class-string, method: string}>>
182+
*/
183+
private function removeEntityListeners(EntityManagerInterface $om, string $entityClass, array $disabledClasses): array
184+
{
185+
$metadata = $om->getClassMetadata($entityClass);
186+
$original = $metadata->entityListeners;
187+
188+
if ([] === $original) {
189+
return [];
190+
}
191+
192+
if ([] === $disabledClasses) {
193+
$metadata->entityListeners = [];
194+
195+
return $original;
196+
}
197+
198+
$metadata->entityListeners = \array_filter(
199+
\array_map(
200+
static fn(array $listeners) => \array_values(\array_filter(
201+
$listeners,
202+
static fn(array $listener) => !\in_array($listener['class'], $disabledClasses, true),
203+
)),
204+
$original,
205+
),
206+
static fn(array $listeners) => [] !== $listeners,
207+
);
208+
209+
return $original;
210+
}
211+
212+
/**
213+
* @param class-string $entityClass
214+
* @param array<string, list<array{class: class-string, method: string}>> $original
215+
*/
216+
private function restoreEntityListeners(EntityManagerInterface $om, string $entityClass, array $original): void
217+
{
218+
if ([] !== $original) {
219+
$om->getClassMetadata($entityClass)->entityListeners = $original;
220+
}
221+
}
121222
}

0 commit comments

Comments
 (0)