diff options
Diffstat (limited to 'vendor/doctrine/orm/src/EntityManager.php')
-rw-r--r-- | vendor/doctrine/orm/src/EntityManager.php | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/EntityManager.php b/vendor/doctrine/orm/src/EntityManager.php new file mode 100644 index 0000000..8045ac2 --- /dev/null +++ b/vendor/doctrine/orm/src/EntityManager.php | |||
@@ -0,0 +1,626 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\ORM; | ||
6 | |||
7 | use BackedEnum; | ||
8 | use DateTimeInterface; | ||
9 | use Doctrine\Common\EventManager; | ||
10 | use Doctrine\DBAL\Connection; | ||
11 | use Doctrine\DBAL\LockMode; | ||
12 | use Doctrine\ORM\Exception\EntityManagerClosed; | ||
13 | use Doctrine\ORM\Exception\InvalidHydrationMode; | ||
14 | use Doctrine\ORM\Exception\MissingIdentifierField; | ||
15 | use Doctrine\ORM\Exception\MissingMappingDriverImplementation; | ||
16 | use Doctrine\ORM\Exception\ORMException; | ||
17 | use Doctrine\ORM\Exception\UnrecognizedIdentifierFields; | ||
18 | use Doctrine\ORM\Internal\Hydration\AbstractHydrator; | ||
19 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
20 | use Doctrine\ORM\Mapping\ClassMetadataFactory; | ||
21 | use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver; | ||
22 | use Doctrine\ORM\Proxy\ProxyFactory; | ||
23 | use Doctrine\ORM\Query\Expr; | ||
24 | use Doctrine\ORM\Query\FilterCollection; | ||
25 | use Doctrine\ORM\Query\ResultSetMapping; | ||
26 | use Doctrine\ORM\Repository\RepositoryFactory; | ||
27 | use Throwable; | ||
28 | |||
29 | use function array_keys; | ||
30 | use function is_array; | ||
31 | use function is_object; | ||
32 | use function ltrim; | ||
33 | use function method_exists; | ||
34 | |||
35 | /** | ||
36 | * The EntityManager is the central access point to ORM functionality. | ||
37 | * | ||
38 | * It is a facade to all different ORM subsystems such as UnitOfWork, | ||
39 | * Query Language and Repository API. The quickest way to obtain a fully | ||
40 | * configured EntityManager is: | ||
41 | * | ||
42 | * use Doctrine\ORM\Tools\ORMSetup; | ||
43 | * use Doctrine\ORM\EntityManager; | ||
44 | * | ||
45 | * $paths = ['/path/to/entity/mapping/files']; | ||
46 | * | ||
47 | * $config = ORMSetup::createAttributeMetadataConfiguration($paths); | ||
48 | * $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config); | ||
49 | * $entityManager = new EntityManager($connection, $config); | ||
50 | * | ||
51 | * For more information see | ||
52 | * {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/configuration.html} | ||
53 | * | ||
54 | * You should never attempt to inherit from the EntityManager: Inheritance | ||
55 | * is not a valid extension point for the EntityManager. Instead you | ||
56 | * should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator} | ||
57 | * and wrap your entity manager in a decorator. | ||
58 | * | ||
59 | * @final | ||
60 | */ | ||
61 | class EntityManager implements EntityManagerInterface | ||
62 | { | ||
63 | /** | ||
64 | * The metadata factory, used to retrieve the ORM metadata of entity classes. | ||
65 | */ | ||
66 | private ClassMetadataFactory $metadataFactory; | ||
67 | |||
68 | /** | ||
69 | * The UnitOfWork used to coordinate object-level transactions. | ||
70 | */ | ||
71 | private UnitOfWork $unitOfWork; | ||
72 | |||
73 | /** | ||
74 | * The event manager that is the central point of the event system. | ||
75 | */ | ||
76 | private EventManager $eventManager; | ||
77 | |||
78 | /** | ||
79 | * The proxy factory used to create dynamic proxies. | ||
80 | */ | ||
81 | private ProxyFactory $proxyFactory; | ||
82 | |||
83 | /** | ||
84 | * The repository factory used to create dynamic repositories. | ||
85 | */ | ||
86 | private RepositoryFactory $repositoryFactory; | ||
87 | |||
88 | /** | ||
89 | * The expression builder instance used to generate query expressions. | ||
90 | */ | ||
91 | private Expr|null $expressionBuilder = null; | ||
92 | |||
93 | /** | ||
94 | * Whether the EntityManager is closed or not. | ||
95 | */ | ||
96 | private bool $closed = false; | ||
97 | |||
98 | /** | ||
99 | * Collection of query filters. | ||
100 | */ | ||
101 | private FilterCollection|null $filterCollection = null; | ||
102 | |||
103 | /** | ||
104 | * The second level cache regions API. | ||
105 | */ | ||
106 | private Cache|null $cache = null; | ||
107 | |||
108 | /** | ||
109 | * Creates a new EntityManager that operates on the given database connection | ||
110 | * and uses the given Configuration and EventManager implementations. | ||
111 | * | ||
112 | * @param Connection $conn The database connection used by the EntityManager. | ||
113 | */ | ||
114 | public function __construct( | ||
115 | private Connection $conn, | ||
116 | private Configuration $config, | ||
117 | EventManager|null $eventManager = null, | ||
118 | ) { | ||
119 | if (! $config->getMetadataDriverImpl()) { | ||
120 | throw MissingMappingDriverImplementation::create(); | ||
121 | } | ||
122 | |||
123 | $this->eventManager = $eventManager | ||
124 | ?? (method_exists($conn, 'getEventManager') | ||
125 | ? $conn->getEventManager() | ||
126 | : new EventManager() | ||
127 | ); | ||
128 | |||
129 | $metadataFactoryClassName = $config->getClassMetadataFactoryName(); | ||
130 | |||
131 | $this->metadataFactory = new $metadataFactoryClassName(); | ||
132 | $this->metadataFactory->setEntityManager($this); | ||
133 | |||
134 | $this->configureMetadataCache(); | ||
135 | |||
136 | $this->repositoryFactory = $config->getRepositoryFactory(); | ||
137 | $this->unitOfWork = new UnitOfWork($this); | ||
138 | $this->proxyFactory = new ProxyFactory( | ||
139 | $this, | ||
140 | $config->getProxyDir(), | ||
141 | $config->getProxyNamespace(), | ||
142 | $config->getAutoGenerateProxyClasses(), | ||
143 | ); | ||
144 | |||
145 | if ($config->isSecondLevelCacheEnabled()) { | ||
146 | $cacheConfig = $config->getSecondLevelCacheConfiguration(); | ||
147 | $cacheFactory = $cacheConfig->getCacheFactory(); | ||
148 | $this->cache = $cacheFactory->createCache($this); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | public function getConnection(): Connection | ||
153 | { | ||
154 | return $this->conn; | ||
155 | } | ||
156 | |||
157 | public function getMetadataFactory(): ClassMetadataFactory | ||
158 | { | ||
159 | return $this->metadataFactory; | ||
160 | } | ||
161 | |||
162 | public function getExpressionBuilder(): Expr | ||
163 | { | ||
164 | return $this->expressionBuilder ??= new Expr(); | ||
165 | } | ||
166 | |||
167 | public function beginTransaction(): void | ||
168 | { | ||
169 | $this->conn->beginTransaction(); | ||
170 | } | ||
171 | |||
172 | public function getCache(): Cache|null | ||
173 | { | ||
174 | return $this->cache; | ||
175 | } | ||
176 | |||
177 | public function wrapInTransaction(callable $func): mixed | ||
178 | { | ||
179 | $this->conn->beginTransaction(); | ||
180 | |||
181 | try { | ||
182 | $return = $func($this); | ||
183 | |||
184 | $this->flush(); | ||
185 | $this->conn->commit(); | ||
186 | |||
187 | return $return; | ||
188 | } catch (Throwable $e) { | ||
189 | $this->close(); | ||
190 | $this->conn->rollBack(); | ||
191 | |||
192 | throw $e; | ||
193 | } | ||
194 | } | ||
195 | |||
196 | public function commit(): void | ||
197 | { | ||
198 | $this->conn->commit(); | ||
199 | } | ||
200 | |||
201 | public function rollback(): void | ||
202 | { | ||
203 | $this->conn->rollBack(); | ||
204 | } | ||
205 | |||
206 | /** | ||
207 | * Returns the ORM metadata descriptor for a class. | ||
208 | * | ||
209 | * Internal note: Performance-sensitive method. | ||
210 | * | ||
211 | * {@inheritDoc} | ||
212 | */ | ||
213 | public function getClassMetadata(string $className): Mapping\ClassMetadata | ||
214 | { | ||
215 | return $this->metadataFactory->getMetadataFor($className); | ||
216 | } | ||
217 | |||
218 | public function createQuery(string $dql = ''): Query | ||
219 | { | ||
220 | $query = new Query($this); | ||
221 | |||
222 | if (! empty($dql)) { | ||
223 | $query->setDQL($dql); | ||
224 | } | ||
225 | |||
226 | return $query; | ||
227 | } | ||
228 | |||
229 | public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery | ||
230 | { | ||
231 | $query = new NativeQuery($this); | ||
232 | |||
233 | $query->setSQL($sql); | ||
234 | $query->setResultSetMapping($rsm); | ||
235 | |||
236 | return $query; | ||
237 | } | ||
238 | |||
239 | public function createQueryBuilder(): QueryBuilder | ||
240 | { | ||
241 | return new QueryBuilder($this); | ||
242 | } | ||
243 | |||
244 | /** | ||
245 | * Flushes all changes to objects that have been queued up to now to the database. | ||
246 | * This effectively synchronizes the in-memory state of managed objects with the | ||
247 | * database. | ||
248 | * | ||
249 | * If an entity is explicitly passed to this method only this entity and | ||
250 | * the cascade-persist semantics + scheduled inserts/removals are synchronized. | ||
251 | * | ||
252 | * @throws OptimisticLockException If a version check on an entity that | ||
253 | * makes use of optimistic locking fails. | ||
254 | * @throws ORMException | ||
255 | */ | ||
256 | public function flush(): void | ||
257 | { | ||
258 | $this->errorIfClosed(); | ||
259 | $this->unitOfWork->commit(); | ||
260 | } | ||
261 | |||
262 | /** | ||
263 | * {@inheritDoc} | ||
264 | */ | ||
265 | public function find($className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null | ||
266 | { | ||
267 | $class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\')); | ||
268 | |||
269 | if ($lockMode !== null) { | ||
270 | $this->checkLockRequirements($lockMode, $class); | ||
271 | } | ||
272 | |||
273 | if (! is_array($id)) { | ||
274 | if ($class->isIdentifierComposite) { | ||
275 | throw ORMInvalidArgumentException::invalidCompositeIdentifier(); | ||
276 | } | ||
277 | |||
278 | $id = [$class->identifier[0] => $id]; | ||
279 | } | ||
280 | |||
281 | foreach ($id as $i => $value) { | ||
282 | if (is_object($value)) { | ||
283 | $className = DefaultProxyClassNameResolver::getClass($value); | ||
284 | if ($this->metadataFactory->hasMetadataFor($className)) { | ||
285 | $id[$i] = $this->unitOfWork->getSingleIdentifierValue($value); | ||
286 | |||
287 | if ($id[$i] === null) { | ||
288 | throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($className); | ||
289 | } | ||
290 | } | ||
291 | } | ||
292 | } | ||
293 | |||
294 | $sortedId = []; | ||
295 | |||
296 | foreach ($class->identifier as $identifier) { | ||
297 | if (! isset($id[$identifier])) { | ||
298 | throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name); | ||
299 | } | ||
300 | |||
301 | if ($id[$identifier] instanceof BackedEnum) { | ||
302 | $sortedId[$identifier] = $id[$identifier]->value; | ||
303 | } else { | ||
304 | $sortedId[$identifier] = $id[$identifier]; | ||
305 | } | ||
306 | |||
307 | unset($id[$identifier]); | ||
308 | } | ||
309 | |||
310 | if ($id) { | ||
311 | throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id)); | ||
312 | } | ||
313 | |||
314 | $unitOfWork = $this->getUnitOfWork(); | ||
315 | |||
316 | $entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName); | ||
317 | |||
318 | // Check identity map first | ||
319 | if ($entity !== false) { | ||
320 | if (! ($entity instanceof $class->name)) { | ||
321 | return null; | ||
322 | } | ||
323 | |||
324 | switch (true) { | ||
325 | case $lockMode === LockMode::OPTIMISTIC: | ||
326 | $this->lock($entity, $lockMode, $lockVersion); | ||
327 | break; | ||
328 | |||
329 | case $lockMode === LockMode::NONE: | ||
330 | case $lockMode === LockMode::PESSIMISTIC_READ: | ||
331 | case $lockMode === LockMode::PESSIMISTIC_WRITE: | ||
332 | $persister = $unitOfWork->getEntityPersister($class->name); | ||
333 | $persister->refresh($sortedId, $entity, $lockMode); | ||
334 | break; | ||
335 | } | ||
336 | |||
337 | return $entity; // Hit! | ||
338 | } | ||
339 | |||
340 | $persister = $unitOfWork->getEntityPersister($class->name); | ||
341 | |||
342 | switch (true) { | ||
343 | case $lockMode === LockMode::OPTIMISTIC: | ||
344 | $entity = $persister->load($sortedId); | ||
345 | |||
346 | if ($entity !== null) { | ||
347 | $unitOfWork->lock($entity, $lockMode, $lockVersion); | ||
348 | } | ||
349 | |||
350 | return $entity; | ||
351 | |||
352 | case $lockMode === LockMode::PESSIMISTIC_READ: | ||
353 | case $lockMode === LockMode::PESSIMISTIC_WRITE: | ||
354 | return $persister->load($sortedId, null, null, [], $lockMode); | ||
355 | |||
356 | default: | ||
357 | return $persister->loadById($sortedId); | ||
358 | } | ||
359 | } | ||
360 | |||
361 | public function getReference(string $entityName, mixed $id): object|null | ||
362 | { | ||
363 | $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); | ||
364 | |||
365 | if (! is_array($id)) { | ||
366 | $id = [$class->identifier[0] => $id]; | ||
367 | } | ||
368 | |||
369 | $sortedId = []; | ||
370 | |||
371 | foreach ($class->identifier as $identifier) { | ||
372 | if (! isset($id[$identifier])) { | ||
373 | throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name); | ||
374 | } | ||
375 | |||
376 | $sortedId[$identifier] = $id[$identifier]; | ||
377 | unset($id[$identifier]); | ||
378 | } | ||
379 | |||
380 | if ($id) { | ||
381 | throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id)); | ||
382 | } | ||
383 | |||
384 | $entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName); | ||
385 | |||
386 | // Check identity map first, if its already in there just return it. | ||
387 | if ($entity !== false) { | ||
388 | return $entity instanceof $class->name ? $entity : null; | ||
389 | } | ||
390 | |||
391 | if ($class->subClasses) { | ||
392 | return $this->find($entityName, $sortedId); | ||
393 | } | ||
394 | |||
395 | $entity = $this->proxyFactory->getProxy($class->name, $sortedId); | ||
396 | |||
397 | $this->unitOfWork->registerManaged($entity, $sortedId, []); | ||
398 | |||
399 | return $entity; | ||
400 | } | ||
401 | |||
402 | /** | ||
403 | * Clears the EntityManager. All entities that are currently managed | ||
404 | * by this EntityManager become detached. | ||
405 | */ | ||
406 | public function clear(): void | ||
407 | { | ||
408 | $this->unitOfWork->clear(); | ||
409 | } | ||
410 | |||
411 | public function close(): void | ||
412 | { | ||
413 | $this->clear(); | ||
414 | |||
415 | $this->closed = true; | ||
416 | } | ||
417 | |||
418 | /** | ||
419 | * Tells the EntityManager to make an instance managed and persistent. | ||
420 | * | ||
421 | * The entity will be entered into the database at or before transaction | ||
422 | * commit or as a result of the flush operation. | ||
423 | * | ||
424 | * NOTE: The persist operation always considers entities that are not yet known to | ||
425 | * this EntityManager as NEW. Do not pass detached entities to the persist operation. | ||
426 | * | ||
427 | * @throws ORMInvalidArgumentException | ||
428 | * @throws ORMException | ||
429 | */ | ||
430 | public function persist(object $object): void | ||
431 | { | ||
432 | $this->errorIfClosed(); | ||
433 | |||
434 | $this->unitOfWork->persist($object); | ||
435 | } | ||
436 | |||
437 | /** | ||
438 | * Removes an entity instance. | ||
439 | * | ||
440 | * A removed entity will be removed from the database at or before transaction commit | ||
441 | * or as a result of the flush operation. | ||
442 | * | ||
443 | * @throws ORMInvalidArgumentException | ||
444 | * @throws ORMException | ||
445 | */ | ||
446 | public function remove(object $object): void | ||
447 | { | ||
448 | $this->errorIfClosed(); | ||
449 | |||
450 | $this->unitOfWork->remove($object); | ||
451 | } | ||
452 | |||
453 | public function refresh(object $object, LockMode|int|null $lockMode = null): void | ||
454 | { | ||
455 | $this->errorIfClosed(); | ||
456 | |||
457 | $this->unitOfWork->refresh($object, $lockMode); | ||
458 | } | ||
459 | |||
460 | /** | ||
461 | * Detaches an entity from the EntityManager, causing a managed entity to | ||
462 | * become detached. Unflushed changes made to the entity if any | ||
463 | * (including removal of the entity), will not be synchronized to the database. | ||
464 | * Entities which previously referenced the detached entity will continue to | ||
465 | * reference it. | ||
466 | * | ||
467 | * @throws ORMInvalidArgumentException | ||
468 | */ | ||
469 | public function detach(object $object): void | ||
470 | { | ||
471 | $this->unitOfWork->detach($object); | ||
472 | } | ||
473 | |||
474 | public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void | ||
475 | { | ||
476 | $this->unitOfWork->lock($entity, $lockMode, $lockVersion); | ||
477 | } | ||
478 | |||
479 | /** | ||
480 | * Gets the repository for an entity class. | ||
481 | * | ||
482 | * @psalm-param class-string<T> $className | ||
483 | * | ||
484 | * @psalm-return EntityRepository<T> | ||
485 | * | ||
486 | * @template T of object | ||
487 | */ | ||
488 | public function getRepository(string $className): EntityRepository | ||
489 | { | ||
490 | return $this->repositoryFactory->getRepository($this, $className); | ||
491 | } | ||
492 | |||
493 | /** | ||
494 | * Determines whether an entity instance is managed in this EntityManager. | ||
495 | * | ||
496 | * @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise. | ||
497 | */ | ||
498 | public function contains(object $object): bool | ||
499 | { | ||
500 | return $this->unitOfWork->isScheduledForInsert($object) | ||
501 | || $this->unitOfWork->isInIdentityMap($object) | ||
502 | && ! $this->unitOfWork->isScheduledForDelete($object); | ||
503 | } | ||
504 | |||
505 | public function getEventManager(): EventManager | ||
506 | { | ||
507 | return $this->eventManager; | ||
508 | } | ||
509 | |||
510 | public function getConfiguration(): Configuration | ||
511 | { | ||
512 | return $this->config; | ||
513 | } | ||
514 | |||
515 | /** | ||
516 | * Throws an exception if the EntityManager is closed or currently not active. | ||
517 | * | ||
518 | * @throws EntityManagerClosed If the EntityManager is closed. | ||
519 | */ | ||
520 | private function errorIfClosed(): void | ||
521 | { | ||
522 | if ($this->closed) { | ||
523 | throw EntityManagerClosed::create(); | ||
524 | } | ||
525 | } | ||
526 | |||
527 | public function isOpen(): bool | ||
528 | { | ||
529 | return ! $this->closed; | ||
530 | } | ||
531 | |||
532 | public function getUnitOfWork(): UnitOfWork | ||
533 | { | ||
534 | return $this->unitOfWork; | ||
535 | } | ||
536 | |||
537 | public function newHydrator(string|int $hydrationMode): AbstractHydrator | ||
538 | { | ||
539 | return match ($hydrationMode) { | ||
540 | Query::HYDRATE_OBJECT => new Internal\Hydration\ObjectHydrator($this), | ||
541 | Query::HYDRATE_ARRAY => new Internal\Hydration\ArrayHydrator($this), | ||
542 | Query::HYDRATE_SCALAR => new Internal\Hydration\ScalarHydrator($this), | ||
543 | Query::HYDRATE_SINGLE_SCALAR => new Internal\Hydration\SingleScalarHydrator($this), | ||
544 | Query::HYDRATE_SIMPLEOBJECT => new Internal\Hydration\SimpleObjectHydrator($this), | ||
545 | Query::HYDRATE_SCALAR_COLUMN => new Internal\Hydration\ScalarColumnHydrator($this), | ||
546 | default => $this->createCustomHydrator((string) $hydrationMode), | ||
547 | }; | ||
548 | } | ||
549 | |||
550 | public function getProxyFactory(): ProxyFactory | ||
551 | { | ||
552 | return $this->proxyFactory; | ||
553 | } | ||
554 | |||
555 | public function initializeObject(object $obj): void | ||
556 | { | ||
557 | $this->unitOfWork->initializeObject($obj); | ||
558 | } | ||
559 | |||
560 | /** | ||
561 | * {@inheritDoc} | ||
562 | */ | ||
563 | public function isUninitializedObject($obj): bool | ||
564 | { | ||
565 | return $this->unitOfWork->isUninitializedObject($obj); | ||
566 | } | ||
567 | |||
568 | public function getFilters(): FilterCollection | ||
569 | { | ||
570 | return $this->filterCollection ??= new FilterCollection($this); | ||
571 | } | ||
572 | |||
573 | public function isFiltersStateClean(): bool | ||
574 | { | ||
575 | return $this->filterCollection === null || $this->filterCollection->isClean(); | ||
576 | } | ||
577 | |||
578 | public function hasFilters(): bool | ||
579 | { | ||
580 | return $this->filterCollection !== null; | ||
581 | } | ||
582 | |||
583 | /** | ||
584 | * @psalm-param LockMode::* $lockMode | ||
585 | * | ||
586 | * @throws OptimisticLockException | ||
587 | * @throws TransactionRequiredException | ||
588 | */ | ||
589 | private function checkLockRequirements(LockMode|int $lockMode, ClassMetadata $class): void | ||
590 | { | ||
591 | switch ($lockMode) { | ||
592 | case LockMode::OPTIMISTIC: | ||
593 | if (! $class->isVersioned) { | ||
594 | throw OptimisticLockException::notVersioned($class->name); | ||
595 | } | ||
596 | |||
597 | break; | ||
598 | case LockMode::PESSIMISTIC_READ: | ||
599 | case LockMode::PESSIMISTIC_WRITE: | ||
600 | if (! $this->getConnection()->isTransactionActive()) { | ||
601 | throw TransactionRequiredException::transactionRequired(); | ||
602 | } | ||
603 | } | ||
604 | } | ||
605 | |||
606 | private function configureMetadataCache(): void | ||
607 | { | ||
608 | $metadataCache = $this->config->getMetadataCache(); | ||
609 | if (! $metadataCache) { | ||
610 | return; | ||
611 | } | ||
612 | |||
613 | $this->metadataFactory->setCache($metadataCache); | ||
614 | } | ||
615 | |||
616 | private function createCustomHydrator(string $hydrationMode): AbstractHydrator | ||
617 | { | ||
618 | $class = $this->config->getCustomHydrationMode($hydrationMode); | ||
619 | |||
620 | if ($class !== null) { | ||
621 | return new $class($this); | ||
622 | } | ||
623 | |||
624 | throw InvalidHydrationMode::fromMode($hydrationMode); | ||
625 | } | ||
626 | } | ||