@@ -31,7 +31,10 @@ final class PersistenceManager
31
31
private bool $ flush = true ;
32
32
private bool $ persist = true ;
33
33
34
- /** @var list<callable():void> */
34
+ /** @var array<int, list<object>> */
35
+ private array $ objectsToPersist = [];
36
+
37
+ /** @var array<int, list<callable():void>> */
35
38
private array $ afterPersistCallbacks = [];
36
39
37
40
/**
@@ -78,6 +81,45 @@ public function save(object $object): object
78
81
return $ object ;
79
82
}
80
83
84
+ /**
85
+ * We're using so-called "transactions" to group multiple persist/flush operations
86
+ * This prevents such code to persist the whole batch of objects in the normalization phase:
87
+ * ```php
88
+ * SomeFactory::createOne(['item' => lazy(fn() => OtherFactory::createOne())]);
89
+ * ```.
90
+ */
91
+ public function startTransaction (): void
92
+ {
93
+ $ this ->objectsToPersist [] = [];
94
+ $ this ->afterPersistCallbacks [] = [];
95
+ }
96
+
97
+ public function commit (): void
98
+ {
99
+ $ objectManagers = [];
100
+
101
+ $ objectsToPersist = \array_pop ($ this ->objectsToPersist );
102
+
103
+ if (null === $ objectsToPersist ) {
104
+ return ;
105
+ }
106
+
107
+ foreach ($ objectsToPersist as $ object ) {
108
+ $ om = $ this ->strategyFor ($ object ::class)->objectManagerFor ($ object ::class);
109
+ $ om ->persist ($ object );
110
+
111
+ if (!\in_array ($ om , $ objectManagers , true )) {
112
+ $ objectManagers [] = $ om ;
113
+ }
114
+ }
115
+
116
+ foreach ($ objectManagers as $ om ) {
117
+ $ this ->flush ($ om );
118
+ }
119
+
120
+ $ this ->callPostPersistCallbacks ();
121
+ }
122
+
81
123
/**
82
124
* @template T of object
83
125
*
@@ -92,23 +134,19 @@ public function scheduleForInsert(object $object, array $afterPersistCallbacks =
92
134
$ object = unproxy ($ object );
93
135
}
94
136
95
- $ om = $ this ->strategyFor ($ object ::class)->objectManagerFor ($ object ::class);
96
- $ om ->persist ($ object );
97
-
98
- $ this ->afterPersistCallbacks = [...$ this ->afterPersistCallbacks , ...$ afterPersistCallbacks ];
99
-
100
- return $ object ;
101
- }
102
-
103
- public function forget (object $ object ): void
104
- {
105
- if ($ this ->isPersisted ($ object )) {
106
- throw new \LogicException ('Cannot forget an object already persisted. ' );
137
+ if (0 === \count ($ this ->objectsToPersist )) {
138
+ throw new \LogicException ('No transaction started yet. ' );
107
139
}
108
140
109
- $ om = $ this ->strategyFor ($ object ::class)->objectManagerFor ($ object ::class);
141
+ $ transactionCount = \count ($ this ->objectsToPersist ) - 1 ;
142
+ $ this ->objectsToPersist [$ transactionCount ][] = $ object ;
143
+
144
+ $ this ->afterPersistCallbacks [$ transactionCount ] = [
145
+ ...$ this ->afterPersistCallbacks [$ transactionCount ],
146
+ ...$ afterPersistCallbacks ,
147
+ ];
110
148
111
- $ om -> detach ( $ object) ;
149
+ return $ object ;
112
150
}
113
151
114
152
/**
@@ -126,11 +164,9 @@ public function flushAfter(callable $callback): mixed
126
164
127
165
$ this ->flush = true ;
128
166
129
- foreach ($ this ->strategies as $ strategy ) {
130
- foreach ($ strategy ->objectManagers () as $ om ) {
131
- $ this ->flush ($ om );
132
- }
133
- }
167
+ $ this ->flushAllStrategies ();
168
+
169
+ $ this ->callPostPersistCallbacks ();
134
170
135
171
return $ result ;
136
172
}
@@ -139,17 +175,6 @@ public function flush(ObjectManager $om): void
139
175
{
140
176
if ($ this ->flush ) {
141
177
$ om ->flush ();
142
-
143
- if ($ this ->afterPersistCallbacks ) {
144
- $ afterPersistCallbacks = $ this ->afterPersistCallbacks ;
145
- $ this ->afterPersistCallbacks = [];
146
-
147
- foreach ($ afterPersistCallbacks as $ afterPersistCallback ) {
148
- $ afterPersistCallback ();
149
- }
150
-
151
- $ this ->flush ($ om );
152
- }
153
178
}
154
179
}
155
180
@@ -372,6 +397,30 @@ public static function isOrmOnly(): bool
372
397
})();
373
398
}
374
399
400
+ private function flushAllStrategies (): void
401
+ {
402
+ foreach ($ this ->strategies as $ strategy ) {
403
+ foreach ($ strategy ->objectManagers () as $ om ) {
404
+ $ this ->flush ($ om );
405
+ }
406
+ }
407
+ }
408
+
409
+ private function callPostPersistCallbacks (): void
410
+ {
411
+ if (!$ this ->flush || [] === $ this ->afterPersistCallbacks ) {
412
+ return ;
413
+ }
414
+
415
+ $ afterPersistCallbacks = \array_pop ($ this ->afterPersistCallbacks );
416
+
417
+ foreach ($ afterPersistCallbacks as $ afterPersistCallback ) {
418
+ $ afterPersistCallback ();
419
+ }
420
+
421
+ $ this ->flushAllStrategies ();
422
+ }
423
+
375
424
/**
376
425
* @param class-string $class
377
426
*
0 commit comments