summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/EntityManager.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/orm/src/EntityManager.php')
-rw-r--r--vendor/doctrine/orm/src/EntityManager.php626
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
3declare(strict_types=1);
4
5namespace Doctrine\ORM;
6
7use BackedEnum;
8use DateTimeInterface;
9use Doctrine\Common\EventManager;
10use Doctrine\DBAL\Connection;
11use Doctrine\DBAL\LockMode;
12use Doctrine\ORM\Exception\EntityManagerClosed;
13use Doctrine\ORM\Exception\InvalidHydrationMode;
14use Doctrine\ORM\Exception\MissingIdentifierField;
15use Doctrine\ORM\Exception\MissingMappingDriverImplementation;
16use Doctrine\ORM\Exception\ORMException;
17use Doctrine\ORM\Exception\UnrecognizedIdentifierFields;
18use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
19use Doctrine\ORM\Mapping\ClassMetadata;
20use Doctrine\ORM\Mapping\ClassMetadataFactory;
21use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
22use Doctrine\ORM\Proxy\ProxyFactory;
23use Doctrine\ORM\Query\Expr;
24use Doctrine\ORM\Query\FilterCollection;
25use Doctrine\ORM\Query\ResultSetMapping;
26use Doctrine\ORM\Repository\RepositoryFactory;
27use Throwable;
28
29use function array_keys;
30use function is_array;
31use function is_object;
32use function ltrim;
33use 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 */
61class 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}