|
10 | 10 |
|
11 | 11 | use function array_flip; |
12 | 12 | use function array_intersect_key; |
13 | | -use function assert; |
14 | 13 | use function count; |
| 14 | +use function ctype_digit; |
15 | 15 | use function is_int; |
16 | 16 | use function is_scalar; |
17 | 17 | use function is_string; |
@@ -96,8 +96,9 @@ public function persist(object $object, Collection $onCollection): object |
96 | 96 | return $object; |
97 | 97 | } |
98 | 98 |
|
99 | | - if ($onCollection->name !== null && $this->tryReplaceFromIdentityMap($object, $onCollection)) { |
100 | | - return $object; |
| 99 | + $merged = $this->tryMergeWithIdentityMap($object, $onCollection); |
| 100 | + if ($merged !== null) { |
| 101 | + return $merged; |
101 | 102 | } |
102 | 103 |
|
103 | 104 | $this->pending[$object] = 'insert'; |
@@ -202,53 +203,84 @@ protected function findInIdentityMap(Collection $collection): object|null |
202 | 203 | return null; |
203 | 204 | } |
204 | 205 |
|
205 | | - $condition = $collection->condition; |
206 | | - if (!is_int($condition) && !is_string($condition)) { |
| 206 | + $condition = $this->normalizeIdValue($collection->condition); |
| 207 | + if ($condition === null) { |
207 | 208 | return null; |
208 | 209 | } |
209 | 210 |
|
210 | 211 | return $this->identityMap[$collection->name][$condition] ?? null; |
211 | 212 | } |
212 | 213 |
|
213 | | - private function tryReplaceFromIdentityMap(object $entity, Collection $coll): bool |
| 214 | + private function tryMergeWithIdentityMap(object $entity, Collection $coll): object|null |
214 | 215 | { |
215 | | - assert($coll->name !== null); |
216 | | - $entityId = $this->entityIdValue($entity, $coll->name); |
217 | | - $idValue = $entityId; |
218 | | - |
219 | | - if ($idValue === null && is_scalar($coll->condition)) { |
220 | | - $idValue = $coll->condition; |
| 216 | + if ($coll->name === null) { |
| 217 | + return null; |
221 | 218 | } |
222 | 219 |
|
223 | | - if ($idValue === null || (!is_int($idValue) && !is_string($idValue))) { |
224 | | - return false; |
| 220 | + $entityId = $this->entityIdValue($entity, $coll->name); |
| 221 | + $idValue = $entityId ?? $this->normalizeIdValue($coll->condition); |
| 222 | + |
| 223 | + if ($idValue === null) { |
| 224 | + return null; |
225 | 225 | } |
226 | 226 |
|
227 | 227 | $existing = $this->identityMap[$coll->name][$idValue] ?? null; |
228 | 228 | if ($existing === null || $existing === $entity) { |
229 | | - return false; |
| 229 | + return null; |
230 | 230 | } |
231 | 231 |
|
232 | 232 | if ($entityId === null) { |
233 | 233 | $idName = $this->style->identifier($coll->name); |
234 | 234 | $this->entityFactory->set($entity, $idName, $idValue); |
235 | 235 | } |
236 | 236 |
|
237 | | - $this->tracked->offsetUnset($existing); |
238 | | - $this->pending->offsetUnset($existing); |
239 | | - $this->evictFromIdentityMap($existing, $coll); |
240 | | - $this->markTracked($entity, $coll); |
241 | | - $this->registerInIdentityMap($entity, $coll); |
242 | | - $this->pending[$entity] = 'update'; |
| 237 | + if ($this->entityFactory->isReadOnly($existing)) { |
| 238 | + $merged = $this->entityFactory->mergeEntities($existing, $entity); |
243 | 239 |
|
244 | | - return true; |
| 240 | + if ($merged !== $existing) { |
| 241 | + $this->tracked->offsetUnset($existing); |
| 242 | + $this->pending->offsetUnset($existing); |
| 243 | + $this->evictFromIdentityMap($existing, $coll); |
| 244 | + $this->markTracked($merged, $coll); |
| 245 | + $this->registerInIdentityMap($merged, $coll); |
| 246 | + } |
| 247 | + |
| 248 | + $this->pending[$merged] = 'update'; |
| 249 | + |
| 250 | + return $merged; |
| 251 | + } |
| 252 | + |
| 253 | + foreach ($this->entityFactory->extractProperties($entity) as $prop => $value) { |
| 254 | + $this->entityFactory->set($existing, $prop, $value); |
| 255 | + } |
| 256 | + |
| 257 | + if (!$this->isTracked($existing)) { |
| 258 | + $this->markTracked($existing, $coll); |
| 259 | + } |
| 260 | + |
| 261 | + $this->pending[$existing] = 'update'; |
| 262 | + |
| 263 | + return $existing; |
245 | 264 | } |
246 | 265 |
|
247 | 266 | private function entityIdValue(object $entity, string $collName): int|string|null |
248 | 267 | { |
249 | | - $idValue = $this->entityFactory->get($entity, $this->style->identifier($collName)); |
| 268 | + return $this->normalizeIdValue( |
| 269 | + $this->entityFactory->get($entity, $this->style->identifier($collName)), |
| 270 | + ); |
| 271 | + } |
| 272 | + |
| 273 | + private function normalizeIdValue(mixed $value): int|string|null |
| 274 | + { |
| 275 | + if (is_int($value)) { |
| 276 | + return $value; |
| 277 | + } |
| 278 | + |
| 279 | + if (is_string($value)) { |
| 280 | + return ctype_digit($value) ? (int) $value : $value; |
| 281 | + } |
250 | 282 |
|
251 | | - return is_int($idValue) || is_string($idValue) ? $idValue : null; |
| 283 | + return null; |
252 | 284 | } |
253 | 285 |
|
254 | 286 | public function __get(string $name): Collection |
|
0 commit comments