summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Cache
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
committerpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
commitbf6655a534a6775d30cafa67bd801276bda1d98d (patch)
treec6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/doctrine/orm/src/Cache
parent94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff)
downloadAppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/doctrine/orm/src/Cache')
-rw-r--r--vendor/doctrine/orm/src/Cache/AssociationCacheEntry.php30
-rw-r--r--vendor/doctrine/orm/src/Cache/CacheConfiguration.php60
-rw-r--r--vendor/doctrine/orm/src/Cache/CacheEntry.php16
-rw-r--r--vendor/doctrine/orm/src/Cache/CacheException.php26
-rw-r--r--vendor/doctrine/orm/src/Cache/CacheFactory.php64
-rw-r--r--vendor/doctrine/orm/src/Cache/CacheKey.php16
-rw-r--r--vendor/doctrine/orm/src/Cache/CollectionCacheEntry.php25
-rw-r--r--vendor/doctrine/orm/src/Cache/CollectionCacheKey.php39
-rw-r--r--vendor/doctrine/orm/src/Cache/CollectionHydrator.php21
-rw-r--r--vendor/doctrine/orm/src/Cache/ConcurrentRegion.php36
-rw-r--r--vendor/doctrine/orm/src/Cache/DefaultCache.php245
-rw-r--r--vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php189
-rw-r--r--vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php75
-rw-r--r--vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php176
-rw-r--r--vendor/doctrine/orm/src/Cache/DefaultQueryCache.php414
-rw-r--r--vendor/doctrine/orm/src/Cache/EntityCacheEntry.php50
-rw-r--r--vendor/doctrine/orm/src/Cache/EntityCacheKey.php38
-rw-r--r--vendor/doctrine/orm/src/Cache/EntityHydrator.php28
-rw-r--r--vendor/doctrine/orm/src/Cache/Exception/CacheException.php14
-rw-r--r--vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyCollection.php19
-rw-r--r--vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyEntity.php15
-rw-r--r--vendor/doctrine/orm/src/Cache/Exception/FeatureNotImplemented.php23
-rw-r--r--vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntity.php18
-rw-r--r--vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntityAssociation.php19
-rw-r--r--vendor/doctrine/orm/src/Cache/Lock.php25
-rw-r--r--vendor/doctrine/orm/src/Cache/LockException.php14
-rw-r--r--vendor/doctrine/orm/src/Cache/Logging/CacheLogger.php60
-rw-r--r--vendor/doctrine/orm/src/Cache/Logging/CacheLoggerChain.php94
-rw-r--r--vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php174
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php25
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Collection/AbstractCollectionPersister.php168
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php36
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php74
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php24
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php103
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php557
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php20
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php85
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php19
-rw-r--r--vendor/doctrine/orm/src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php105
-rw-r--r--vendor/doctrine/orm/src/Cache/QueryCache.php28
-rw-r--r--vendor/doctrine/orm/src/Cache/QueryCacheEntry.php29
-rw-r--r--vendor/doctrine/orm/src/Cache/QueryCacheKey.php23
-rw-r--r--vendor/doctrine/orm/src/Cache/QueryCacheValidator.php16
-rw-r--r--vendor/doctrine/orm/src/Cache/Region.php73
-rw-r--r--vendor/doctrine/orm/src/Cache/Region/DefaultRegion.php113
-rw-r--r--vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php194
-rw-r--r--vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php20
-rw-r--r--vendor/doctrine/orm/src/Cache/RegionsConfiguration.php63
-rw-r--r--vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php29
-rw-r--r--vendor/doctrine/orm/src/Cache/TimestampCacheKey.php17
-rw-r--r--vendor/doctrine/orm/src/Cache/TimestampQueryCacheValidator.php38
-rw-r--r--vendor/doctrine/orm/src/Cache/TimestampRegion.php18
53 files changed, 3900 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/Cache/AssociationCacheEntry.php b/vendor/doctrine/orm/src/Cache/AssociationCacheEntry.php
new file mode 100644
index 0000000..7dc1fbe
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/AssociationCacheEntry.php
@@ -0,0 +1,30 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7class AssociationCacheEntry implements CacheEntry
8{
9 /**
10 * @param array<string, mixed> $identifier The entity identifier.
11 * @param class-string $class The entity class name
12 */
13 public function __construct(
14 public readonly string $class,
15 public readonly array $identifier,
16 ) {
17 }
18
19 /**
20 * Creates a new AssociationCacheEntry
21 *
22 * This method allow Doctrine\Common\Cache\PhpFileCache compatibility
23 *
24 * @param array<string, mixed> $values array containing property values
25 */
26 public static function __set_state(array $values): self
27 {
28 return new self($values['class'], $values['identifier']);
29 }
30}
diff --git a/vendor/doctrine/orm/src/Cache/CacheConfiguration.php b/vendor/doctrine/orm/src/Cache/CacheConfiguration.php
new file mode 100644
index 0000000..0f8dea7
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CacheConfiguration.php
@@ -0,0 +1,60 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Cache\Logging\CacheLogger;
8
9/**
10 * Configuration container for second-level cache.
11 */
12class CacheConfiguration
13{
14 private CacheFactory|null $cacheFactory = null;
15 private RegionsConfiguration|null $regionsConfig = null;
16 private CacheLogger|null $cacheLogger = null;
17 private QueryCacheValidator|null $queryValidator = null;
18
19 public function getCacheFactory(): CacheFactory|null
20 {
21 return $this->cacheFactory;
22 }
23
24 public function setCacheFactory(CacheFactory $factory): void
25 {
26 $this->cacheFactory = $factory;
27 }
28
29 public function getCacheLogger(): CacheLogger|null
30 {
31 return $this->cacheLogger;
32 }
33
34 public function setCacheLogger(CacheLogger $logger): void
35 {
36 $this->cacheLogger = $logger;
37 }
38
39 public function getRegionsConfiguration(): RegionsConfiguration
40 {
41 return $this->regionsConfig ??= new RegionsConfiguration();
42 }
43
44 public function setRegionsConfiguration(RegionsConfiguration $regionsConfig): void
45 {
46 $this->regionsConfig = $regionsConfig;
47 }
48
49 public function getQueryValidator(): QueryCacheValidator
50 {
51 return $this->queryValidator ??= new TimestampQueryCacheValidator(
52 $this->cacheFactory->getTimestampRegion(),
53 );
54 }
55
56 public function setQueryValidator(QueryCacheValidator $validator): void
57 {
58 $this->queryValidator = $validator;
59 }
60}
diff --git a/vendor/doctrine/orm/src/Cache/CacheEntry.php b/vendor/doctrine/orm/src/Cache/CacheEntry.php
new file mode 100644
index 0000000..6e12de1
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CacheEntry.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7/**
8 * Cache entry interface
9 *
10 * <b>IMPORTANT NOTE:</b>
11 *
12 * Fields of classes that implement CacheEntry are public for performance reason.
13 */
14interface CacheEntry
15{
16}
diff --git a/vendor/doctrine/orm/src/Cache/CacheException.php b/vendor/doctrine/orm/src/Cache/CacheException.php
new file mode 100644
index 0000000..b422095
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CacheException.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Exception\ORMException;
8use LogicException;
9
10use function sprintf;
11
12/**
13 * Exception for cache.
14 */
15class CacheException extends LogicException implements ORMException
16{
17 public static function updateReadOnlyCollection(string $sourceEntity, string $fieldName): self
18 {
19 return new self(sprintf('Cannot update a readonly collection "%s#%s"', $sourceEntity, $fieldName));
20 }
21
22 public static function nonCacheableEntity(string $entityName): self
23 {
24 return new self(sprintf('Entity "%s" not configured as part of the second-level cache.', $entityName));
25 }
26}
diff --git a/vendor/doctrine/orm/src/Cache/CacheFactory.php b/vendor/doctrine/orm/src/Cache/CacheFactory.php
new file mode 100644
index 0000000..b15c23e
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CacheFactory.php
@@ -0,0 +1,64 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Cache;
8use Doctrine\ORM\Cache\Persister\Collection\CachedCollectionPersister;
9use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
10use Doctrine\ORM\EntityManagerInterface;
11use Doctrine\ORM\Mapping\AssociationMapping;
12use Doctrine\ORM\Mapping\ClassMetadata;
13use Doctrine\ORM\Persisters\Collection\CollectionPersister;
14use Doctrine\ORM\Persisters\Entity\EntityPersister;
15
16/**
17 * Contract for building second level cache regions components.
18 */
19interface CacheFactory
20{
21 /**
22 * Build an entity persister for the given entity metadata.
23 */
24 public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata): CachedEntityPersister;
25
26 /** Build a collection persister for the given relation mapping. */
27 public function buildCachedCollectionPersister(
28 EntityManagerInterface $em,
29 CollectionPersister $persister,
30 AssociationMapping $mapping,
31 ): CachedCollectionPersister;
32
33 /**
34 * Build a query cache based on the given region name
35 */
36 public function buildQueryCache(EntityManagerInterface $em, string|null $regionName = null): QueryCache;
37
38 /**
39 * Build an entity hydrator
40 */
41 public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata): EntityHydrator;
42
43 /**
44 * Build a collection hydrator
45 */
46 public function buildCollectionHydrator(EntityManagerInterface $em, AssociationMapping $mapping): CollectionHydrator;
47
48 /**
49 * Build a cache region
50 *
51 * @param array<string,mixed> $cache The cache configuration.
52 */
53 public function getRegion(array $cache): Region;
54
55 /**
56 * Build timestamp cache region
57 */
58 public function getTimestampRegion(): TimestampRegion;
59
60 /**
61 * Build \Doctrine\ORM\Cache
62 */
63 public function createCache(EntityManagerInterface $entityManager): Cache;
64}
diff --git a/vendor/doctrine/orm/src/Cache/CacheKey.php b/vendor/doctrine/orm/src/Cache/CacheKey.php
new file mode 100644
index 0000000..970702c
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CacheKey.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7/**
8 * Defines entity / collection / query key to be stored in the cache region.
9 * Allows multiple roles to be stored in the same cache region.
10 */
11abstract class CacheKey
12{
13 public function __construct(public readonly string $hash)
14 {
15 }
16}
diff --git a/vendor/doctrine/orm/src/Cache/CollectionCacheEntry.php b/vendor/doctrine/orm/src/Cache/CollectionCacheEntry.php
new file mode 100644
index 0000000..fde4575
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CollectionCacheEntry.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7class CollectionCacheEntry implements CacheEntry
8{
9 /** @param CacheKey[] $identifiers List of entity identifiers hold by the collection */
10 public function __construct(public readonly array $identifiers)
11 {
12 }
13
14 /**
15 * Creates a new CollectionCacheEntry
16 *
17 * This method allows for Doctrine\Common\Cache\PhpFileCache compatibility
18 *
19 * @param array<string, mixed> $values array containing property values
20 */
21 public static function __set_state(array $values): CollectionCacheEntry
22 {
23 return new self($values['identifiers']);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Cache/CollectionCacheKey.php b/vendor/doctrine/orm/src/Cache/CollectionCacheKey.php
new file mode 100644
index 0000000..51b408f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CollectionCacheKey.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use function implode;
8use function ksort;
9use function str_replace;
10use function strtolower;
11
12/**
13 * Defines entity collection roles to be stored in the cache region.
14 */
15class CollectionCacheKey extends CacheKey
16{
17 /**
18 * The owner entity identifier
19 *
20 * @var array<string, mixed>
21 */
22 public readonly array $ownerIdentifier;
23
24 /**
25 * @param array<string, mixed> $ownerIdentifier The identifier of the owning entity.
26 * @param class-string $entityClass The owner entity class
27 */
28 public function __construct(
29 public readonly string $entityClass,
30 public readonly string $association,
31 array $ownerIdentifier,
32 ) {
33 ksort($ownerIdentifier);
34
35 $this->ownerIdentifier = $ownerIdentifier;
36
37 parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
38 }
39}
diff --git a/vendor/doctrine/orm/src/Cache/CollectionHydrator.php b/vendor/doctrine/orm/src/Cache/CollectionHydrator.php
new file mode 100644
index 0000000..16a6572
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/CollectionHydrator.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\Common\Collections\Collection;
8use Doctrine\ORM\Mapping\ClassMetadata;
9use Doctrine\ORM\PersistentCollection;
10
11/**
12 * Hydrator cache entry for collections
13 */
14interface CollectionHydrator
15{
16 /** @param mixed[]|Collection $collection The collection. */
17 public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array|Collection $collection): CollectionCacheEntry;
18
19 /** @return mixed[]|null */
20 public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection): array|null;
21}
diff --git a/vendor/doctrine/orm/src/Cache/ConcurrentRegion.php b/vendor/doctrine/orm/src/Cache/ConcurrentRegion.php
new file mode 100644
index 0000000..e9ca870
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/ConcurrentRegion.php
@@ -0,0 +1,36 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7/**
8 * Defines contract for concurrently managed data region.
9 * It should be able to lock an specific cache entry in an atomic operation.
10 *
11 * When a entry is locked another process should not be able to read or write the entry.
12 * All evict operation should not consider locks, even though an entry is locked evict should be able to delete the entry and its lock.
13 */
14interface ConcurrentRegion extends Region
15{
16 /**
17 * Attempts to read lock the mapping for the given key.
18 *
19 * @param CacheKey $key The key of the item to lock.
20 *
21 * @return Lock|null A lock instance or NULL if the lock already exists.
22 *
23 * @throws LockException Indicates a problem accessing the region.
24 */
25 public function lock(CacheKey $key): Lock|null;
26
27 /**
28 * Attempts to read unlock the mapping for the given key.
29 *
30 * @param CacheKey $key The key of the item to unlock.
31 * @param Lock $lock The lock previously obtained from {@link readLock}
32 *
33 * @throws LockException Indicates a problem accessing the region.
34 */
35 public function unlock(CacheKey $key, Lock $lock): bool;
36}
diff --git a/vendor/doctrine/orm/src/Cache/DefaultCache.php b/vendor/doctrine/orm/src/Cache/DefaultCache.php
new file mode 100644
index 0000000..685181c
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/DefaultCache.php
@@ -0,0 +1,245 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Cache;
8use Doctrine\ORM\Cache\Persister\CachedPersister;
9use Doctrine\ORM\EntityManagerInterface;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\ORMInvalidArgumentException;
12use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
13use Doctrine\ORM\UnitOfWork;
14
15use function is_array;
16use function is_object;
17
18/**
19 * Provides an API for querying/managing the second level cache regions.
20 */
21class DefaultCache implements Cache
22{
23 private readonly UnitOfWork $uow;
24 private readonly CacheFactory $cacheFactory;
25
26 /**
27 * @var QueryCache[]
28 * @psalm-var array<string, QueryCache>
29 */
30 private array $queryCaches = [];
31
32 private QueryCache|null $defaultQueryCache = null;
33
34 public function __construct(
35 private readonly EntityManagerInterface $em,
36 ) {
37 $this->uow = $em->getUnitOfWork();
38 $this->cacheFactory = $em->getConfiguration()
39 ->getSecondLevelCacheConfiguration()
40 ->getCacheFactory();
41 }
42
43 public function getEntityCacheRegion(string $className): Region|null
44 {
45 $metadata = $this->em->getClassMetadata($className);
46 $persister = $this->uow->getEntityPersister($metadata->rootEntityName);
47
48 if (! ($persister instanceof CachedPersister)) {
49 return null;
50 }
51
52 return $persister->getCacheRegion();
53 }
54
55 public function getCollectionCacheRegion(string $className, string $association): Region|null
56 {
57 $metadata = $this->em->getClassMetadata($className);
58 $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
59
60 if (! ($persister instanceof CachedPersister)) {
61 return null;
62 }
63
64 return $persister->getCacheRegion();
65 }
66
67 public function containsEntity(string $className, mixed $identifier): bool
68 {
69 $metadata = $this->em->getClassMetadata($className);
70 $persister = $this->uow->getEntityPersister($metadata->rootEntityName);
71
72 if (! ($persister instanceof CachedPersister)) {
73 return false;
74 }
75
76 return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier));
77 }
78
79 public function evictEntity(string $className, mixed $identifier): void
80 {
81 $metadata = $this->em->getClassMetadata($className);
82 $persister = $this->uow->getEntityPersister($metadata->rootEntityName);
83
84 if (! ($persister instanceof CachedPersister)) {
85 return;
86 }
87
88 $persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier));
89 }
90
91 public function evictEntityRegion(string $className): void
92 {
93 $metadata = $this->em->getClassMetadata($className);
94 $persister = $this->uow->getEntityPersister($metadata->rootEntityName);
95
96 if (! ($persister instanceof CachedPersister)) {
97 return;
98 }
99
100 $persister->getCacheRegion()->evictAll();
101 }
102
103 public function evictEntityRegions(): void
104 {
105 $metadatas = $this->em->getMetadataFactory()->getAllMetadata();
106
107 foreach ($metadatas as $metadata) {
108 $persister = $this->uow->getEntityPersister($metadata->rootEntityName);
109
110 if (! ($persister instanceof CachedPersister)) {
111 continue;
112 }
113
114 $persister->getCacheRegion()->evictAll();
115 }
116 }
117
118 public function containsCollection(string $className, string $association, mixed $ownerIdentifier): bool
119 {
120 $metadata = $this->em->getClassMetadata($className);
121 $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
122
123 if (! ($persister instanceof CachedPersister)) {
124 return false;
125 }
126
127 return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
128 }
129
130 public function evictCollection(string $className, string $association, mixed $ownerIdentifier): void
131 {
132 $metadata = $this->em->getClassMetadata($className);
133 $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
134
135 if (! ($persister instanceof CachedPersister)) {
136 return;
137 }
138
139 $persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
140 }
141
142 public function evictCollectionRegion(string $className, string $association): void
143 {
144 $metadata = $this->em->getClassMetadata($className);
145 $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
146
147 if (! ($persister instanceof CachedPersister)) {
148 return;
149 }
150
151 $persister->getCacheRegion()->evictAll();
152 }
153
154 public function evictCollectionRegions(): void
155 {
156 $metadatas = $this->em->getMetadataFactory()->getAllMetadata();
157
158 foreach ($metadatas as $metadata) {
159 foreach ($metadata->associationMappings as $association) {
160 if (! $association->isToMany()) {
161 continue;
162 }
163
164 $persister = $this->uow->getCollectionPersister($association);
165
166 if (! ($persister instanceof CachedPersister)) {
167 continue;
168 }
169
170 $persister->getCacheRegion()->evictAll();
171 }
172 }
173 }
174
175 public function containsQuery(string $regionName): bool
176 {
177 return isset($this->queryCaches[$regionName]);
178 }
179
180 public function evictQueryRegion(string|null $regionName = null): void
181 {
182 if ($regionName === null && $this->defaultQueryCache !== null) {
183 $this->defaultQueryCache->clear();
184
185 return;
186 }
187
188 if (isset($this->queryCaches[$regionName])) {
189 $this->queryCaches[$regionName]->clear();
190 }
191 }
192
193 public function evictQueryRegions(): void
194 {
195 $this->getQueryCache()->clear();
196
197 foreach ($this->queryCaches as $queryCache) {
198 $queryCache->clear();
199 }
200 }
201
202 public function getQueryCache(string|null $regionName = null): QueryCache
203 {
204 if ($regionName === null) {
205 return $this->defaultQueryCache ??= $this->cacheFactory->buildQueryCache($this->em);
206 }
207
208 return $this->queryCaches[$regionName] ??= $this->cacheFactory->buildQueryCache($this->em, $regionName);
209 }
210
211 private function buildEntityCacheKey(ClassMetadata $metadata, mixed $identifier): EntityCacheKey
212 {
213 if (! is_array($identifier)) {
214 $identifier = $this->toIdentifierArray($metadata, $identifier);
215 }
216
217 return new EntityCacheKey($metadata->rootEntityName, $identifier);
218 }
219
220 private function buildCollectionCacheKey(
221 ClassMetadata $metadata,
222 string $association,
223 mixed $ownerIdentifier,
224 ): CollectionCacheKey {
225 if (! is_array($ownerIdentifier)) {
226 $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
227 }
228
229 return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
230 }
231
232 /** @return array<string, mixed> */
233 private function toIdentifierArray(ClassMetadata $metadata, mixed $identifier): array
234 {
235 if (is_object($identifier)) {
236 $class = DefaultProxyClassNameResolver::getClass($identifier);
237 if ($this->em->getMetadataFactory()->hasMetadataFor($class)) {
238 $identifier = $this->uow->getSingleIdentifierValue($identifier)
239 ?? throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
240 }
241 }
242
243 return [$metadata->identifier[0] => $identifier];
244 }
245}
diff --git a/vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php b/vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php
new file mode 100644
index 0000000..84ea490
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php
@@ -0,0 +1,189 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Cache;
8use Doctrine\ORM\Cache\Persister\Collection\CachedCollectionPersister;
9use Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister;
10use Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister;
11use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister;
12use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
13use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister;
14use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister;
15use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
16use Doctrine\ORM\Cache\Region\DefaultRegion;
17use Doctrine\ORM\Cache\Region\FileLockRegion;
18use Doctrine\ORM\Cache\Region\UpdateTimestampCache;
19use Doctrine\ORM\EntityManagerInterface;
20use Doctrine\ORM\Mapping\AssociationMapping;
21use Doctrine\ORM\Mapping\ClassMetadata;
22use Doctrine\ORM\Persisters\Collection\CollectionPersister;
23use Doctrine\ORM\Persisters\Entity\EntityPersister;
24use InvalidArgumentException;
25use LogicException;
26use Psr\Cache\CacheItemPoolInterface;
27
28use function assert;
29use function sprintf;
30
31use const DIRECTORY_SEPARATOR;
32
33class DefaultCacheFactory implements CacheFactory
34{
35 private TimestampRegion|null $timestampRegion = null;
36
37 /** @var Region[] */
38 private array $regions = [];
39
40 private string|null $fileLockRegionDirectory = null;
41
42 public function __construct(private readonly RegionsConfiguration $regionsConfig, private readonly CacheItemPoolInterface $cacheItemPool)
43 {
44 }
45
46 public function setFileLockRegionDirectory(string $fileLockRegionDirectory): void
47 {
48 $this->fileLockRegionDirectory = $fileLockRegionDirectory;
49 }
50
51 public function getFileLockRegionDirectory(): string|null
52 {
53 return $this->fileLockRegionDirectory;
54 }
55
56 public function setRegion(Region $region): void
57 {
58 $this->regions[$region->getName()] = $region;
59 }
60
61 public function setTimestampRegion(TimestampRegion $region): void
62 {
63 $this->timestampRegion = $region;
64 }
65
66 public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata): CachedEntityPersister
67 {
68 assert($metadata->cache !== null);
69 $region = $this->getRegion($metadata->cache);
70 $usage = $metadata->cache['usage'];
71
72 if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
73 return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata);
74 }
75
76 if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
77 return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
78 }
79
80 if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
81 if (! $region instanceof ConcurrentRegion) {
82 throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage));
83 }
84
85 return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
86 }
87
88 throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
89 }
90
91 public function buildCachedCollectionPersister(
92 EntityManagerInterface $em,
93 CollectionPersister $persister,
94 AssociationMapping $mapping,
95 ): CachedCollectionPersister {
96 assert(isset($mapping->cache));
97 $usage = $mapping->cache['usage'];
98 $region = $this->getRegion($mapping->cache);
99
100 if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
101 return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping);
102 }
103
104 if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
105 return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
106 }
107
108 if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
109 if (! $region instanceof ConcurrentRegion) {
110 throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage));
111 }
112
113 return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
114 }
115
116 throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
117 }
118
119 public function buildQueryCache(EntityManagerInterface $em, string|null $regionName = null): QueryCache
120 {
121 return new DefaultQueryCache(
122 $em,
123 $this->getRegion(
124 [
125 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
126 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE,
127 ],
128 ),
129 );
130 }
131
132 public function buildCollectionHydrator(EntityManagerInterface $em, AssociationMapping $mapping): CollectionHydrator
133 {
134 return new DefaultCollectionHydrator($em);
135 }
136
137 public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata): EntityHydrator
138 {
139 return new DefaultEntityHydrator($em);
140 }
141
142 /**
143 * {@inheritDoc}
144 */
145 public function getRegion(array $cache): Region
146 {
147 if (isset($this->regions[$cache['region']])) {
148 return $this->regions[$cache['region']];
149 }
150
151 $name = $cache['region'];
152 $lifetime = $this->regionsConfig->getLifetime($cache['region']);
153 $region = new DefaultRegion($name, $this->cacheItemPool, $lifetime);
154
155 if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
156 if (
157 $this->fileLockRegionDirectory === '' ||
158 $this->fileLockRegionDirectory === null
159 ) {
160 throw new LogicException(
161 'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
162 'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). ',
163 );
164 }
165
166 $directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region'];
167 $region = new FileLockRegion($region, $directory, (string) $this->regionsConfig->getLockLifetime($cache['region']));
168 }
169
170 return $this->regions[$cache['region']] = $region;
171 }
172
173 public function getTimestampRegion(): TimestampRegion
174 {
175 if ($this->timestampRegion === null) {
176 $name = Cache::DEFAULT_TIMESTAMP_REGION_NAME;
177 $lifetime = $this->regionsConfig->getLifetime($name);
178
179 $this->timestampRegion = new UpdateTimestampCache($name, $this->cacheItemPool, $lifetime);
180 }
181
182 return $this->timestampRegion;
183 }
184
185 public function createCache(EntityManagerInterface $entityManager): Cache
186 {
187 return new DefaultCache($entityManager);
188 }
189}
diff --git a/vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php b/vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php
new file mode 100644
index 0000000..249d48f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php
@@ -0,0 +1,75 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\Common\Collections\Collection;
8use Doctrine\ORM\Cache\Persister\CachedPersister;
9use Doctrine\ORM\EntityManagerInterface;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\PersistentCollection;
12use Doctrine\ORM\Query;
13use Doctrine\ORM\UnitOfWork;
14
15use function assert;
16
17/**
18 * Default hydrator cache for collections
19 */
20class DefaultCollectionHydrator implements CollectionHydrator
21{
22 private readonly UnitOfWork $uow;
23
24 /** @var array<string,mixed> */
25 private static array $hints = [Query::HINT_CACHE_ENABLED => true];
26
27 public function __construct(
28 private readonly EntityManagerInterface $em,
29 ) {
30 $this->uow = $em->getUnitOfWork();
31 }
32
33 public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array|Collection $collection): CollectionCacheEntry
34 {
35 $data = [];
36
37 foreach ($collection as $index => $entity) {
38 $data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity));
39 }
40
41 return new CollectionCacheEntry($data);
42 }
43
44 public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection): array|null
45 {
46 $assoc = $metadata->associationMappings[$key->association];
47 $targetPersister = $this->uow->getEntityPersister($assoc->targetEntity);
48 assert($targetPersister instanceof CachedPersister);
49 $targetRegion = $targetPersister->getCacheRegion();
50 $list = [];
51
52 /** @var EntityCacheEntry[]|null $entityEntries */
53 $entityEntries = $targetRegion->getMultiple($entry);
54
55 if ($entityEntries === null) {
56 return null;
57 }
58
59 foreach ($entityEntries as $index => $entityEntry) {
60 $entity = $this->uow->createEntity(
61 $entityEntry->class,
62 $entityEntry->resolveAssociationEntries($this->em),
63 self::$hints,
64 );
65
66 $collection->hydrateSet($index, $entity);
67
68 $list[$index] = $entity;
69 }
70
71 $this->uow->hydrationComplete();
72
73 return $list;
74 }
75}
diff --git a/vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php b/vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php
new file mode 100644
index 0000000..6bd1524
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php
@@ -0,0 +1,176 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\EntityManagerInterface;
8use Doctrine\ORM\Mapping\ClassMetadata;
9use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
10use Doctrine\ORM\Query;
11use Doctrine\ORM\UnitOfWork;
12use Doctrine\ORM\Utility\IdentifierFlattener;
13
14use function assert;
15use function is_array;
16use function is_object;
17use function reset;
18
19/**
20 * Default hydrator cache for entities
21 */
22class DefaultEntityHydrator implements EntityHydrator
23{
24 private readonly UnitOfWork $uow;
25 private readonly IdentifierFlattener $identifierFlattener;
26
27 /** @var array<string,mixed> */
28 private static array $hints = [Query::HINT_CACHE_ENABLED => true];
29
30 public function __construct(
31 private readonly EntityManagerInterface $em,
32 ) {
33 $this->uow = $em->getUnitOfWork();
34 $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory());
35 }
36
37 public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, object $entity): EntityCacheEntry
38 {
39 $data = $this->uow->getOriginalEntityData($entity);
40 $data = [...$data, ...$metadata->getIdentifierValues($entity)]; // why update has no identifier values ?
41
42 if ($metadata->requiresFetchAfterChange) {
43 if ($metadata->isVersioned) {
44 assert($metadata->versionField !== null);
45 $data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
46 }
47
48 foreach ($metadata->fieldMappings as $name => $fieldMapping) {
49 if (isset($fieldMapping->generated)) {
50 $data[$name] = $metadata->getFieldValue($entity, $name);
51 }
52 }
53 }
54
55 foreach ($metadata->associationMappings as $name => $assoc) {
56 if (! isset($data[$name])) {
57 continue;
58 }
59
60 if (! $assoc->isToOne()) {
61 unset($data[$name]);
62
63 continue;
64 }
65
66 if (! isset($assoc->cache)) {
67 $targetClassMetadata = $this->em->getClassMetadata($assoc->targetEntity);
68 $owningAssociation = $this->em->getMetadataFactory()->getOwningSide($assoc);
69 $associationIds = $this->identifierFlattener->flattenIdentifier(
70 $targetClassMetadata,
71 $targetClassMetadata->getIdentifierValues($data[$name]),
72 );
73
74 unset($data[$name]);
75
76 foreach ($associationIds as $fieldName => $fieldValue) {
77 if (isset($targetClassMetadata->fieldMappings[$fieldName])) {
78 assert($owningAssociation->isToOneOwningSide());
79 $fieldMapping = $targetClassMetadata->fieldMappings[$fieldName];
80
81 $data[$owningAssociation->targetToSourceKeyColumns[$fieldMapping->columnName]] = $fieldValue;
82
83 continue;
84 }
85
86 $targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
87
88 assert($assoc->isToOneOwningSide());
89 foreach ($assoc->targetToSourceKeyColumns as $referencedColumn => $localColumn) {
90 if (isset($targetAssoc->sourceToTargetKeyColumns[$referencedColumn])) {
91 $data[$localColumn] = $fieldValue;
92 }
93 }
94 }
95
96 continue;
97 }
98
99 if (! isset($assoc->id)) {
100 $targetClass = DefaultProxyClassNameResolver::getClass($data[$name]);
101 $targetId = $this->uow->getEntityIdentifier($data[$name]);
102 $data[$name] = new AssociationCacheEntry($targetClass, $targetId);
103
104 continue;
105 }
106
107 // handle association identifier
108 $targetId = is_object($data[$name]) && $this->uow->isInIdentityMap($data[$name])
109 ? $this->uow->getEntityIdentifier($data[$name])
110 : $data[$name];
111
112 // @TODO - fix it !
113 // handle UnitOfWork#createEntity hash generation
114 if (! is_array($targetId)) {
115 assert($assoc->isToOneOwningSide());
116 $data[reset($assoc->joinColumnFieldNames)] = $targetId;
117
118 $targetEntity = $this->em->getClassMetadata($assoc->targetEntity);
119 $targetId = [$targetEntity->identifier[0] => $targetId];
120 }
121
122 $data[$name] = new AssociationCacheEntry($assoc->targetEntity, $targetId);
123 }
124
125 return new EntityCacheEntry($metadata->name, $data);
126 }
127
128 public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, object|null $entity = null): object|null
129 {
130 $data = $entry->data;
131 $hints = self::$hints;
132
133 if ($entity !== null) {
134 $hints[Query::HINT_REFRESH] = true;
135 $hints[Query::HINT_REFRESH_ENTITY] = $entity;
136 }
137
138 foreach ($metadata->associationMappings as $name => $assoc) {
139 if (! isset($assoc->cache) || ! isset($data[$name])) {
140 continue;
141 }
142
143 $assocClass = $data[$name]->class;
144 $assocId = $data[$name]->identifier;
145 $isEagerLoad = ($assoc->fetch === ClassMetadata::FETCH_EAGER || ($assoc->isOneToOne() && ! $assoc->isOwningSide()));
146
147 if (! $isEagerLoad) {
148 $data[$name] = $this->em->getReference($assocClass, $assocId);
149
150 continue;
151 }
152
153 $assocMetadata = $this->em->getClassMetadata($assoc->targetEntity);
154 $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
155 $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity);
156 $assocRegion = $assocPersister->getCacheRegion();
157 $assocEntry = $assocRegion->get($assocKey);
158
159 if ($assocEntry === null) {
160 return null;
161 }
162
163 $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), $hints);
164 }
165
166 if ($entity !== null) {
167 $this->uow->registerManaged($entity, $key->identifier, $data);
168 }
169
170 $result = $this->uow->createEntity($entry->class, $data, $hints);
171
172 $this->uow->hydrationComplete();
173
174 return $result;
175 }
176}
diff --git a/vendor/doctrine/orm/src/Cache/DefaultQueryCache.php b/vendor/doctrine/orm/src/Cache/DefaultQueryCache.php
new file mode 100644
index 0000000..f3bb8ac
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/DefaultQueryCache.php
@@ -0,0 +1,414 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\Common\Collections\ArrayCollection;
8use Doctrine\ORM\Cache;
9use Doctrine\ORM\Cache\Exception\FeatureNotImplemented;
10use Doctrine\ORM\Cache\Exception\NonCacheableEntity;
11use Doctrine\ORM\Cache\Logging\CacheLogger;
12use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
13use Doctrine\ORM\EntityManagerInterface;
14use Doctrine\ORM\Mapping\AssociationMapping;
15use Doctrine\ORM\Mapping\ClassMetadata;
16use Doctrine\ORM\PersistentCollection;
17use Doctrine\ORM\Query;
18use Doctrine\ORM\Query\ResultSetMapping;
19use Doctrine\ORM\UnitOfWork;
20
21use function array_map;
22use function array_shift;
23use function array_unshift;
24use function assert;
25use function count;
26use function is_array;
27use function key;
28use function reset;
29
30/**
31 * Default query cache implementation.
32 */
33class DefaultQueryCache implements QueryCache
34{
35 private readonly UnitOfWork $uow;
36 private readonly QueryCacheValidator $validator;
37 protected CacheLogger|null $cacheLogger = null;
38
39 /** @var array<string,mixed> */
40 private static array $hints = [Query::HINT_CACHE_ENABLED => true];
41
42 public function __construct(
43 private readonly EntityManagerInterface $em,
44 private readonly Region $region,
45 ) {
46 $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
47
48 $this->uow = $em->getUnitOfWork();
49 $this->cacheLogger = $cacheConfig->getCacheLogger();
50 $this->validator = $cacheConfig->getQueryValidator();
51 }
52
53 /**
54 * {@inheritDoc}
55 */
56 public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []): array|null
57 {
58 if (! ($key->cacheMode & Cache::MODE_GET)) {
59 return null;
60 }
61
62 $cacheEntry = $this->region->get($key);
63
64 if (! $cacheEntry instanceof QueryCacheEntry) {
65 return null;
66 }
67
68 if (! $this->validator->isValid($key, $cacheEntry)) {
69 $this->region->evict($key);
70
71 return null;
72 }
73
74 $result = [];
75 $entityName = reset($rsm->aliasMap);
76 $hasRelation = ! empty($rsm->relationMap);
77 $persister = $this->uow->getEntityPersister($entityName);
78 assert($persister instanceof CachedEntityPersister);
79
80 $region = $persister->getCacheRegion();
81 $regionName = $region->getName();
82
83 $cm = $this->em->getClassMetadata($entityName);
84
85 $generateKeys = static fn (array $entry): EntityCacheKey => new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
86
87 $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
88 $entries = $region->getMultiple($cacheKeys) ?? [];
89
90 // @TODO - move to cache hydration component
91 foreach ($cacheEntry->result as $index => $entry) {
92 $entityEntry = $entries[$index] ?? null;
93
94 if (! $entityEntry instanceof EntityCacheEntry) {
95 $this->cacheLogger?->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
96
97 return null;
98 }
99
100 $this->cacheLogger?->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
101
102 if (! $hasRelation) {
103 $result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
104
105 continue;
106 }
107
108 $data = $entityEntry->data;
109
110 foreach ($entry['associations'] as $name => $assoc) {
111 $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
112 assert($assocPersister instanceof CachedEntityPersister);
113
114 $assocRegion = $assocPersister->getCacheRegion();
115 $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
116
117 if ($assoc['type'] & ClassMetadata::TO_ONE) {
118 $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']);
119 $assocEntry = $assocRegion->get($assocKey);
120
121 if ($assocEntry === null) {
122 $this->cacheLogger?->entityCacheMiss($assocRegion->getName(), $assocKey);
123
124 $this->uow->hydrationComplete();
125
126 return null;
127 }
128
129 $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
130
131 $this->cacheLogger?->entityCacheHit($assocRegion->getName(), $assocKey);
132
133 continue;
134 }
135
136 if (! isset($assoc['list']) || empty($assoc['list'])) {
137 continue;
138 }
139
140 $generateKeys = static fn (array $id): EntityCacheKey => new EntityCacheKey($assocMetadata->rootEntityName, $id);
141
142 $collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
143 $assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
144 $assocEntries = $assocRegion->getMultiple($assocKeys);
145
146 foreach ($assoc['list'] as $assocIndex => $assocId) {
147 $assocEntry = is_array($assocEntries) ? ($assocEntries[$assocIndex] ?? null) : null;
148
149 if ($assocEntry === null) {
150 $this->cacheLogger?->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
151
152 $this->uow->hydrationComplete();
153
154 return null;
155 }
156
157 $element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
158
159 $collection->hydrateSet($assocIndex, $element);
160
161 $this->cacheLogger?->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
162 }
163
164 $data[$name] = $collection;
165
166 $collection->setInitialized(true);
167 }
168
169 foreach ($data as $fieldName => $unCachedAssociationData) {
170 // In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the
171 // cache key information in `$cacheEntry` will not contain details
172 // for fields that are associations.
173 //
174 // This means that `$data` keys for some associations that may
175 // actually not be cached will not be converted to actual association
176 // data, yet they contain L2 cache AssociationCacheEntry objects.
177 //
178 // We need to unwrap those associations into proxy references,
179 // since we don't have actual data for them except for identifiers.
180 if ($unCachedAssociationData instanceof AssociationCacheEntry) {
181 $data[$fieldName] = $this->em->getReference(
182 $unCachedAssociationData->class,
183 $unCachedAssociationData->identifier,
184 );
185 }
186 }
187
188 $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
189 }
190
191 $this->uow->hydrationComplete();
192
193 return $result;
194 }
195
196 /**
197 * {@inheritDoc}
198 */
199 public function put(QueryCacheKey $key, ResultSetMapping $rsm, mixed $result, array $hints = []): bool
200 {
201 if ($rsm->scalarMappings) {
202 throw FeatureNotImplemented::scalarResults();
203 }
204
205 if (count($rsm->entityMappings) > 1) {
206 throw FeatureNotImplemented::multipleRootEntities();
207 }
208
209 if (! $rsm->isSelect) {
210 throw FeatureNotImplemented::nonSelectStatements();
211 }
212
213 if (! ($key->cacheMode & Cache::MODE_PUT)) {
214 return false;
215 }
216
217 $data = [];
218 $entityName = reset($rsm->aliasMap);
219 $rootAlias = key($rsm->aliasMap);
220 $persister = $this->uow->getEntityPersister($entityName);
221
222 if (! $persister instanceof CachedEntityPersister) {
223 throw NonCacheableEntity::fromEntity($entityName);
224 }
225
226 $region = $persister->getCacheRegion();
227
228 $cm = $this->em->getClassMetadata($entityName);
229 assert($cm instanceof ClassMetadata);
230
231 foreach ($result as $index => $entity) {
232 $identifier = $this->uow->getEntityIdentifier($entity);
233 $entityKey = new EntityCacheKey($cm->rootEntityName, $identifier);
234
235 if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
236 // Cancel put result if entity put fail
237 if (! $persister->storeEntityCache($entity, $entityKey)) {
238 return false;
239 }
240 }
241
242 $data[$index]['identifier'] = $identifier;
243 $data[$index]['associations'] = [];
244
245 // @TODO - move to cache hydration components
246 foreach ($rsm->relationMap as $alias => $name) {
247 $parentAlias = $rsm->parentAliasMap[$alias];
248 $parentClass = $rsm->aliasMap[$parentAlias];
249 $metadata = $this->em->getClassMetadata($parentClass);
250 $assoc = $metadata->associationMappings[$name];
251 $assocValue = $this->getAssociationValue($rsm, $alias, $entity);
252
253 if ($assocValue === null) {
254 continue;
255 }
256
257 // root entity association
258 if ($rootAlias === $parentAlias) {
259 // Cancel put result if association put fail
260 $assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue);
261 if ($assocInfo === null) {
262 return false;
263 }
264
265 $data[$index]['associations'][$name] = $assocInfo;
266
267 continue;
268 }
269
270 // store single nested association
271 if (! is_array($assocValue)) {
272 // Cancel put result if association put fail
273 if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
274 return false;
275 }
276
277 continue;
278 }
279
280 // store array of nested association
281 foreach ($assocValue as $aVal) {
282 // Cancel put result if association put fail
283 if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
284 return false;
285 }
286 }
287 }
288 }
289
290 return $this->region->put($key, new QueryCacheEntry($data));
291 }
292
293 /**
294 * @return mixed[]|null
295 * @psalm-return array{targetEntity: class-string, type: mixed, list?: array[], identifier?: array}|null
296 */
297 private function storeAssociationCache(QueryCacheKey $key, AssociationMapping $assoc, mixed $assocValue): array|null
298 {
299 $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity);
300 $assocMetadata = $assocPersister->getClassMetadata();
301 $assocRegion = $assocPersister->getCacheRegion();
302
303 // Handle *-to-one associations
304 if ($assoc->isToOne()) {
305 $assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
306 $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
307
308 if (! $this->uow->isUninitializedObject($assocValue) && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
309 // Entity put fail
310 if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
311 return null;
312 }
313 }
314
315 return [
316 'targetEntity' => $assocMetadata->rootEntityName,
317 'identifier' => $assocIdentifier,
318 'type' => $assoc->type(),
319 ];
320 }
321
322 // Handle *-to-many associations
323 $list = [];
324
325 foreach ($assocValue as $assocItemIndex => $assocItem) {
326 $assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
327 $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
328
329 if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
330 // Entity put fail
331 if (! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
332 return null;
333 }
334 }
335
336 $list[$assocItemIndex] = $assocIdentifier;
337 }
338
339 return [
340 'targetEntity' => $assocMetadata->rootEntityName,
341 'type' => $assoc->type(),
342 'list' => $list,
343 ];
344 }
345
346 /** @psalm-return list<mixed>|object|null */
347 private function getAssociationValue(
348 ResultSetMapping $rsm,
349 string $assocAlias,
350 object $entity,
351 ): array|object|null {
352 $path = [];
353 $alias = $assocAlias;
354
355 while (isset($rsm->parentAliasMap[$alias])) {
356 $parent = $rsm->parentAliasMap[$alias];
357 $field = $rsm->relationMap[$alias];
358 $class = $rsm->aliasMap[$parent];
359
360 array_unshift($path, [
361 'field' => $field,
362 'class' => $class,
363 ]);
364
365 $alias = $parent;
366 }
367
368 return $this->getAssociationPathValue($entity, $path);
369 }
370
371 /**
372 * @psalm-param array<array-key, array{field: string, class: string}> $path
373 *
374 * @psalm-return list<mixed>|object|null
375 */
376 private function getAssociationPathValue(mixed $value, array $path): array|object|null
377 {
378 $mapping = array_shift($path);
379 $metadata = $this->em->getClassMetadata($mapping['class']);
380 $assoc = $metadata->associationMappings[$mapping['field']];
381 $value = $metadata->getFieldValue($value, $mapping['field']);
382
383 if ($value === null) {
384 return null;
385 }
386
387 if ($path === []) {
388 return $value;
389 }
390
391 // Handle *-to-one associations
392 if ($assoc->isToOne()) {
393 return $this->getAssociationPathValue($value, $path);
394 }
395
396 $values = [];
397
398 foreach ($value as $item) {
399 $values[] = $this->getAssociationPathValue($item, $path);
400 }
401
402 return $values;
403 }
404
405 public function clear(): bool
406 {
407 return $this->region->evictAll();
408 }
409
410 public function getRegion(): Region
411 {
412 return $this->region;
413 }
414}
diff --git a/vendor/doctrine/orm/src/Cache/EntityCacheEntry.php b/vendor/doctrine/orm/src/Cache/EntityCacheEntry.php
new file mode 100644
index 0000000..69bc813
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/EntityCacheEntry.php
@@ -0,0 +1,50 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\EntityManagerInterface;
8
9use function array_map;
10
11class EntityCacheEntry implements CacheEntry
12{
13 /**
14 * @param array<string,mixed> $data The entity map data
15 * @psalm-param class-string $class The entity class name
16 */
17 public function __construct(
18 public readonly string $class,
19 public readonly array $data,
20 ) {
21 }
22
23 /**
24 * Creates a new EntityCacheEntry
25 *
26 * This method allows Doctrine\Common\Cache\PhpFileCache compatibility
27 *
28 * @param array<string,mixed> $values array containing property values
29 */
30 public static function __set_state(array $values): self
31 {
32 return new self($values['class'], $values['data']);
33 }
34
35 /**
36 * Retrieves the entity data resolving cache entries
37 *
38 * @return array<string, mixed>
39 */
40 public function resolveAssociationEntries(EntityManagerInterface $em): array
41 {
42 return array_map(static function ($value) use ($em) {
43 if (! ($value instanceof AssociationCacheEntry)) {
44 return $value;
45 }
46
47 return $em->getReference($value->class, $value->identifier);
48 }, $this->data);
49 }
50}
diff --git a/vendor/doctrine/orm/src/Cache/EntityCacheKey.php b/vendor/doctrine/orm/src/Cache/EntityCacheKey.php
new file mode 100644
index 0000000..095ddaa
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/EntityCacheKey.php
@@ -0,0 +1,38 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use function implode;
8use function ksort;
9use function str_replace;
10use function strtolower;
11
12/**
13 * Defines entity classes roles to be stored in the cache region.
14 */
15class EntityCacheKey extends CacheKey
16{
17 /**
18 * The entity identifier
19 *
20 * @var array<string, mixed>
21 */
22 public readonly array $identifier;
23
24 /**
25 * @param class-string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
26 * @param array<string, mixed> $identifier The entity identifier
27 */
28 public function __construct(
29 public readonly string $entityClass,
30 array $identifier,
31 ) {
32 ksort($identifier);
33
34 $this->identifier = $identifier;
35
36 parent::__construct(str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier)));
37 }
38}
diff --git a/vendor/doctrine/orm/src/Cache/EntityHydrator.php b/vendor/doctrine/orm/src/Cache/EntityHydrator.php
new file mode 100644
index 0000000..13cd21f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/EntityHydrator.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Mapping\ClassMetadata;
8
9/**
10 * Hydrator cache entry for entities
11 */
12interface EntityHydrator
13{
14 /**
15 * @param ClassMetadata $metadata The entity metadata.
16 * @param EntityCacheKey $key The entity cache key.
17 * @param object $entity The entity.
18 */
19 public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, object $entity): EntityCacheEntry;
20
21 /**
22 * @param ClassMetadata $metadata The entity metadata.
23 * @param EntityCacheKey $key The entity cache key.
24 * @param EntityCacheEntry $entry The entity cache entry.
25 * @param object|null $entity The entity to load the cache into. If not specified, a new entity is created.
26 */
27 public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, object|null $entity = null): object|null;
28}
diff --git a/vendor/doctrine/orm/src/Cache/Exception/CacheException.php b/vendor/doctrine/orm/src/Cache/Exception/CacheException.php
new file mode 100644
index 0000000..cae1bde
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Exception/CacheException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Exception;
6
7use Doctrine\ORM\Cache\CacheException as BaseCacheException;
8
9/**
10 * Exception for cache.
11 */
12class CacheException extends BaseCacheException
13{
14}
diff --git a/vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyCollection.php b/vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyCollection.php
new file mode 100644
index 0000000..7ecb4fe
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyCollection.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Exception;
6
7use function sprintf;
8
9class CannotUpdateReadOnlyCollection extends CacheException
10{
11 public static function fromEntityAndField(string $sourceEntity, string $fieldName): self
12 {
13 return new self(sprintf(
14 'Cannot update a readonly collection "%s#%s"',
15 $sourceEntity,
16 $fieldName,
17 ));
18 }
19}
diff --git a/vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyEntity.php b/vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyEntity.php
new file mode 100644
index 0000000..b945514
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyEntity.php
@@ -0,0 +1,15 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Exception;
6
7use function sprintf;
8
9class CannotUpdateReadOnlyEntity extends CacheException
10{
11 public static function fromEntity(string $entityName): self
12 {
13 return new self(sprintf('Cannot update a readonly entity "%s"', $entityName));
14 }
15}
diff --git a/vendor/doctrine/orm/src/Cache/Exception/FeatureNotImplemented.php b/vendor/doctrine/orm/src/Cache/Exception/FeatureNotImplemented.php
new file mode 100644
index 0000000..8767d57
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Exception/FeatureNotImplemented.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Exception;
6
7class FeatureNotImplemented extends CacheException
8{
9 public static function scalarResults(): self
10 {
11 return new self('Second level cache does not support scalar results.');
12 }
13
14 public static function multipleRootEntities(): self
15 {
16 return new self('Second level cache does not support multiple root entities.');
17 }
18
19 public static function nonSelectStatements(): self
20 {
21 return new self('Second-level cache query supports only select statements.');
22 }
23}
diff --git a/vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntity.php b/vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntity.php
new file mode 100644
index 0000000..4f5da85
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntity.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Exception;
6
7use function sprintf;
8
9class NonCacheableEntity extends CacheException
10{
11 public static function fromEntity(string $entityName): self
12 {
13 return new self(sprintf(
14 'Entity "%s" not configured as part of the second-level cache.',
15 $entityName,
16 ));
17 }
18}
diff --git a/vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntityAssociation.php b/vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntityAssociation.php
new file mode 100644
index 0000000..984286f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntityAssociation.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Exception;
6
7use function sprintf;
8
9class NonCacheableEntityAssociation extends CacheException
10{
11 public static function fromEntityAndField(string $entityName, string $field): self
12 {
13 return new self(sprintf(
14 'Entity association field "%s#%s" not configured as part of the second-level cache.',
15 $entityName,
16 $field,
17 ));
18 }
19}
diff --git a/vendor/doctrine/orm/src/Cache/Lock.php b/vendor/doctrine/orm/src/Cache/Lock.php
new file mode 100644
index 0000000..d6d60a3
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Lock.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use function time;
8use function uniqid;
9
10class Lock
11{
12 public int $time;
13
14 public function __construct(
15 public string $value,
16 int|null $time = null,
17 ) {
18 $this->time = $time ?? time();
19 }
20
21 public static function createLockRead(): Lock
22 {
23 return new self(uniqid((string) time(), true));
24 }
25}
diff --git a/vendor/doctrine/orm/src/Cache/LockException.php b/vendor/doctrine/orm/src/Cache/LockException.php
new file mode 100644
index 0000000..bb2d4ec
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/LockException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Cache\Exception\CacheException;
8
9/**
10 * Lock exception for cache.
11 */
12class LockException extends CacheException
13{
14}
diff --git a/vendor/doctrine/orm/src/Cache/Logging/CacheLogger.php b/vendor/doctrine/orm/src/Cache/Logging/CacheLogger.php
new file mode 100644
index 0000000..64c97c1
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Logging/CacheLogger.php
@@ -0,0 +1,60 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Logging;
6
7use Doctrine\ORM\Cache\CollectionCacheKey;
8use Doctrine\ORM\Cache\EntityCacheKey;
9use Doctrine\ORM\Cache\QueryCacheKey;
10
11/**
12 * Interface for logging.
13 */
14interface CacheLogger
15{
16 /**
17 * Log an entity put into second level cache.
18 */
19 public function entityCachePut(string $regionName, EntityCacheKey $key): void;
20
21 /**
22 * Log an entity get from second level cache resulted in a hit.
23 */
24 public function entityCacheHit(string $regionName, EntityCacheKey $key): void;
25
26 /**
27 * Log an entity get from second level cache resulted in a miss.
28 */
29 public function entityCacheMiss(string $regionName, EntityCacheKey $key): void;
30
31 /**
32 * Log an entity put into second level cache.
33 */
34 public function collectionCachePut(string $regionName, CollectionCacheKey $key): void;
35
36 /**
37 * Log an entity get from second level cache resulted in a hit.
38 */
39 public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void;
40
41 /**
42 * Log an entity get from second level cache resulted in a miss.
43 */
44 public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void;
45
46 /**
47 * Log a query put into the query cache.
48 */
49 public function queryCachePut(string $regionName, QueryCacheKey $key): void;
50
51 /**
52 * Log a query get from the query cache resulted in a hit.
53 */
54 public function queryCacheHit(string $regionName, QueryCacheKey $key): void;
55
56 /**
57 * Log a query get from the query cache resulted in a miss.
58 */
59 public function queryCacheMiss(string $regionName, QueryCacheKey $key): void;
60}
diff --git a/vendor/doctrine/orm/src/Cache/Logging/CacheLoggerChain.php b/vendor/doctrine/orm/src/Cache/Logging/CacheLoggerChain.php
new file mode 100644
index 0000000..8eef3b5
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Logging/CacheLoggerChain.php
@@ -0,0 +1,94 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Logging;
6
7use Doctrine\ORM\Cache\CollectionCacheKey;
8use Doctrine\ORM\Cache\EntityCacheKey;
9use Doctrine\ORM\Cache\QueryCacheKey;
10
11class CacheLoggerChain implements CacheLogger
12{
13 /** @var array<string, CacheLogger> */
14 private array $loggers = [];
15
16 public function setLogger(string $name, CacheLogger $logger): void
17 {
18 $this->loggers[$name] = $logger;
19 }
20
21 public function getLogger(string $name): CacheLogger|null
22 {
23 return $this->loggers[$name] ?? null;
24 }
25
26 /** @return array<string, CacheLogger> */
27 public function getLoggers(): array
28 {
29 return $this->loggers;
30 }
31
32 public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void
33 {
34 foreach ($this->loggers as $logger) {
35 $logger->collectionCacheHit($regionName, $key);
36 }
37 }
38
39 public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void
40 {
41 foreach ($this->loggers as $logger) {
42 $logger->collectionCacheMiss($regionName, $key);
43 }
44 }
45
46 public function collectionCachePut(string $regionName, CollectionCacheKey $key): void
47 {
48 foreach ($this->loggers as $logger) {
49 $logger->collectionCachePut($regionName, $key);
50 }
51 }
52
53 public function entityCacheHit(string $regionName, EntityCacheKey $key): void
54 {
55 foreach ($this->loggers as $logger) {
56 $logger->entityCacheHit($regionName, $key);
57 }
58 }
59
60 public function entityCacheMiss(string $regionName, EntityCacheKey $key): void
61 {
62 foreach ($this->loggers as $logger) {
63 $logger->entityCacheMiss($regionName, $key);
64 }
65 }
66
67 public function entityCachePut(string $regionName, EntityCacheKey $key): void
68 {
69 foreach ($this->loggers as $logger) {
70 $logger->entityCachePut($regionName, $key);
71 }
72 }
73
74 public function queryCacheHit(string $regionName, QueryCacheKey $key): void
75 {
76 foreach ($this->loggers as $logger) {
77 $logger->queryCacheHit($regionName, $key);
78 }
79 }
80
81 public function queryCacheMiss(string $regionName, QueryCacheKey $key): void
82 {
83 foreach ($this->loggers as $logger) {
84 $logger->queryCacheMiss($regionName, $key);
85 }
86 }
87
88 public function queryCachePut(string $regionName, QueryCacheKey $key): void
89 {
90 foreach ($this->loggers as $logger) {
91 $logger->queryCachePut($regionName, $key);
92 }
93 }
94}
diff --git a/vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php b/vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php
new file mode 100644
index 0000000..092104e
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php
@@ -0,0 +1,174 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Logging;
6
7use Doctrine\ORM\Cache\CollectionCacheKey;
8use Doctrine\ORM\Cache\EntityCacheKey;
9use Doctrine\ORM\Cache\QueryCacheKey;
10
11use function array_sum;
12
13/**
14 * Provide basic second level cache statistics.
15 */
16class StatisticsCacheLogger implements CacheLogger
17{
18 /** @var array<string, int> */
19 private array $cacheMissCountMap = [];
20
21 /** @var array<string, int> */
22 private array $cacheHitCountMap = [];
23
24 /** @var array<string, int> */
25 private array $cachePutCountMap = [];
26
27 public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void
28 {
29 $this->cacheMissCountMap[$regionName]
30 = ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
31 }
32
33 public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void
34 {
35 $this->cacheHitCountMap[$regionName]
36 = ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
37 }
38
39 public function collectionCachePut(string $regionName, CollectionCacheKey $key): void
40 {
41 $this->cachePutCountMap[$regionName]
42 = ($this->cachePutCountMap[$regionName] ?? 0) + 1;
43 }
44
45 public function entityCacheMiss(string $regionName, EntityCacheKey $key): void
46 {
47 $this->cacheMissCountMap[$regionName]
48 = ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
49 }
50
51 public function entityCacheHit(string $regionName, EntityCacheKey $key): void
52 {
53 $this->cacheHitCountMap[$regionName]
54 = ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
55 }
56
57 public function entityCachePut(string $regionName, EntityCacheKey $key): void
58 {
59 $this->cachePutCountMap[$regionName]
60 = ($this->cachePutCountMap[$regionName] ?? 0) + 1;
61 }
62
63 public function queryCacheHit(string $regionName, QueryCacheKey $key): void
64 {
65 $this->cacheHitCountMap[$regionName]
66 = ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
67 }
68
69 public function queryCacheMiss(string $regionName, QueryCacheKey $key): void
70 {
71 $this->cacheMissCountMap[$regionName]
72 = ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
73 }
74
75 public function queryCachePut(string $regionName, QueryCacheKey $key): void
76 {
77 $this->cachePutCountMap[$regionName]
78 = ($this->cachePutCountMap[$regionName] ?? 0) + 1;
79 }
80
81 /**
82 * Get the number of entries successfully retrieved from cache.
83 *
84 * @param string $regionName The name of the cache region.
85 */
86 public function getRegionHitCount(string $regionName): int
87 {
88 return $this->cacheHitCountMap[$regionName] ?? 0;
89 }
90
91 /**
92 * Get the number of cached entries *not* found in cache.
93 *
94 * @param string $regionName The name of the cache region.
95 */
96 public function getRegionMissCount(string $regionName): int
97 {
98 return $this->cacheMissCountMap[$regionName] ?? 0;
99 }
100
101 /**
102 * Get the number of cacheable entries put in cache.
103 *
104 * @param string $regionName The name of the cache region.
105 */
106 public function getRegionPutCount(string $regionName): int
107 {
108 return $this->cachePutCountMap[$regionName] ?? 0;
109 }
110
111 /** @return array<string, int> */
112 public function getRegionsMiss(): array
113 {
114 return $this->cacheMissCountMap;
115 }
116
117 /** @return array<string, int> */
118 public function getRegionsHit(): array
119 {
120 return $this->cacheHitCountMap;
121 }
122
123 /** @return array<string, int> */
124 public function getRegionsPut(): array
125 {
126 return $this->cachePutCountMap;
127 }
128
129 /**
130 * Clear region statistics
131 *
132 * @param string $regionName The name of the cache region.
133 */
134 public function clearRegionStats(string $regionName): void
135 {
136 $this->cachePutCountMap[$regionName] = 0;
137 $this->cacheHitCountMap[$regionName] = 0;
138 $this->cacheMissCountMap[$regionName] = 0;
139 }
140
141 /**
142 * Clear all statistics
143 */
144 public function clearStats(): void
145 {
146 $this->cachePutCountMap = [];
147 $this->cacheHitCountMap = [];
148 $this->cacheMissCountMap = [];
149 }
150
151 /**
152 * Get the total number of put in cache.
153 */
154 public function getPutCount(): int
155 {
156 return array_sum($this->cachePutCountMap);
157 }
158
159 /**
160 * Get the total number of entries successfully retrieved from cache.
161 */
162 public function getHitCount(): int
163 {
164 return array_sum($this->cacheHitCountMap);
165 }
166
167 /**
168 * Get the total number of cached entries *not* found in cache.
169 */
170 public function getMissCount(): int
171 {
172 return array_sum($this->cacheMissCountMap);
173 }
174}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php b/vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php
new file mode 100644
index 0000000..223692c
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister;
6
7use Doctrine\ORM\Cache\Region;
8
9/**
10 * Interface for persister that support second level cache.
11 */
12interface CachedPersister
13{
14 /**
15 * Perform whatever processing is encapsulated here after completion of the transaction.
16 */
17 public function afterTransactionComplete(): void;
18
19 /**
20 * Perform whatever processing is encapsulated here after completion of the rolled-back.
21 */
22 public function afterTransactionRolledBack(): void;
23
24 public function getCacheRegion(): Region;
25}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/AbstractCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/AbstractCollectionPersister.php
new file mode 100644
index 0000000..8c087a8
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/AbstractCollectionPersister.php
@@ -0,0 +1,168 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Collection;
6
7use Doctrine\Common\Collections\Collection;
8use Doctrine\Common\Collections\Criteria;
9use Doctrine\ORM\Cache\CollectionCacheKey;
10use Doctrine\ORM\Cache\CollectionHydrator;
11use Doctrine\ORM\Cache\Logging\CacheLogger;
12use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
13use Doctrine\ORM\Cache\Region;
14use Doctrine\ORM\EntityManagerInterface;
15use Doctrine\ORM\Mapping\AssociationMapping;
16use Doctrine\ORM\Mapping\ClassMetadata;
17use Doctrine\ORM\Mapping\ClassMetadataFactory;
18use Doctrine\ORM\PersistentCollection;
19use Doctrine\ORM\Persisters\Collection\CollectionPersister;
20use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
21use Doctrine\ORM\UnitOfWork;
22
23use function array_values;
24use function assert;
25use function count;
26
27abstract class AbstractCollectionPersister implements CachedCollectionPersister
28{
29 protected UnitOfWork $uow;
30 protected ClassMetadataFactory $metadataFactory;
31 protected ClassMetadata $sourceEntity;
32 protected ClassMetadata $targetEntity;
33
34 /** @var mixed[] */
35 protected array $queuedCache = [];
36
37 protected string $regionName;
38 protected CollectionHydrator $hydrator;
39 protected CacheLogger|null $cacheLogger;
40
41 public function __construct(
42 protected CollectionPersister $persister,
43 protected Region $region,
44 EntityManagerInterface $em,
45 protected AssociationMapping $association,
46 ) {
47 $configuration = $em->getConfiguration();
48 $cacheConfig = $configuration->getSecondLevelCacheConfiguration();
49 $cacheFactory = $cacheConfig->getCacheFactory();
50
51 $this->regionName = $region->getName();
52 $this->uow = $em->getUnitOfWork();
53 $this->metadataFactory = $em->getMetadataFactory();
54 $this->cacheLogger = $cacheConfig->getCacheLogger();
55 $this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association);
56 $this->sourceEntity = $em->getClassMetadata($association->sourceEntity);
57 $this->targetEntity = $em->getClassMetadata($association->targetEntity);
58 }
59
60 public function getCacheRegion(): Region
61 {
62 return $this->region;
63 }
64
65 public function getSourceEntityMetadata(): ClassMetadata
66 {
67 return $this->sourceEntity;
68 }
69
70 public function getTargetEntityMetadata(): ClassMetadata
71 {
72 return $this->targetEntity;
73 }
74
75 public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key): array|null
76 {
77 $cache = $this->region->get($key);
78
79 if ($cache === null) {
80 return null;
81 }
82
83 return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection);
84 }
85
86 public function storeCollectionCache(CollectionCacheKey $key, Collection|array $elements): void
87 {
88 $associationMapping = $this->sourceEntity->associationMappings[$key->association];
89 $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
90 assert($targetPersister instanceof CachedEntityPersister);
91 $targetRegion = $targetPersister->getCacheRegion();
92 $targetHydrator = $targetPersister->getEntityHydrator();
93
94 // Only preserve ordering if association configured it
95 if (! $associationMapping->isIndexed()) {
96 // Elements may be an array or a Collection
97 $elements = array_values($elements instanceof Collection ? $elements->getValues() : $elements);
98 }
99
100 $entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
101
102 foreach ($entry->identifiers as $index => $entityKey) {
103 if ($targetRegion->contains($entityKey)) {
104 continue;
105 }
106
107 $class = $this->targetEntity;
108 $className = DefaultProxyClassNameResolver::getClass($elements[$index]);
109
110 if ($className !== $this->targetEntity->name) {
111 $class = $this->metadataFactory->getMetadataFor($className);
112 }
113
114 $entity = $elements[$index];
115 $entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity);
116
117 $targetRegion->put($entityKey, $entityEntry);
118 }
119
120 if ($this->region->put($key, $entry)) {
121 $this->cacheLogger?->collectionCachePut($this->regionName, $key);
122 }
123 }
124
125 public function contains(PersistentCollection $collection, object $element): bool
126 {
127 return $this->persister->contains($collection, $element);
128 }
129
130 public function containsKey(PersistentCollection $collection, mixed $key): bool
131 {
132 return $this->persister->containsKey($collection, $key);
133 }
134
135 public function count(PersistentCollection $collection): int
136 {
137 $ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
138 $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
139 $entry = $this->region->get($key);
140
141 if ($entry !== null) {
142 return count($entry->identifiers);
143 }
144
145 return $this->persister->count($collection);
146 }
147
148 public function get(PersistentCollection $collection, mixed $index): mixed
149 {
150 return $this->persister->get($collection, $index);
151 }
152
153 /**
154 * {@inheritDoc}
155 */
156 public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array
157 {
158 return $this->persister->slice($collection, $offset, $length);
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array
165 {
166 return $this->persister->loadCriteria($collection, $criteria);
167 }
168}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php
new file mode 100644
index 0000000..6b10c80
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php
@@ -0,0 +1,36 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Collection;
6
7use Doctrine\Common\Collections\Collection;
8use Doctrine\ORM\Cache\CollectionCacheKey;
9use Doctrine\ORM\Cache\Persister\CachedPersister;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\PersistentCollection;
12use Doctrine\ORM\Persisters\Collection\CollectionPersister;
13
14/**
15 * Interface for second level cache collection persisters.
16 */
17interface CachedCollectionPersister extends CachedPersister, CollectionPersister
18{
19 public function getSourceEntityMetadata(): ClassMetadata;
20
21 public function getTargetEntityMetadata(): ClassMetadata;
22
23 /**
24 * Loads a collection from cache
25 *
26 * @return mixed[]|null
27 */
28 public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key): array|null;
29
30 /**
31 * Stores a collection into cache
32 *
33 * @param mixed[]|Collection $elements
34 */
35 public function storeCollectionCache(CollectionCacheKey $key, Collection|array $elements): void;
36}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php
new file mode 100644
index 0000000..ac861f4
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php
@@ -0,0 +1,74 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Collection;
6
7use Doctrine\ORM\Cache\CollectionCacheKey;
8use Doctrine\ORM\PersistentCollection;
9
10use function spl_object_id;
11
12class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
13{
14 public function afterTransactionComplete(): void
15 {
16 if (isset($this->queuedCache['update'])) {
17 foreach ($this->queuedCache['update'] as $item) {
18 $this->storeCollectionCache($item['key'], $item['list']);
19 }
20 }
21
22 if (isset($this->queuedCache['delete'])) {
23 foreach ($this->queuedCache['delete'] as $key) {
24 $this->region->evict($key);
25 }
26 }
27
28 $this->queuedCache = [];
29 }
30
31 public function afterTransactionRolledBack(): void
32 {
33 $this->queuedCache = [];
34 }
35
36 public function delete(PersistentCollection $collection): void
37 {
38 $ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
39 $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
40
41 $this->persister->delete($collection);
42
43 $this->queuedCache['delete'][spl_object_id($collection)] = $key;
44 }
45
46 public function update(PersistentCollection $collection): void
47 {
48 $isInitialized = $collection->isInitialized();
49 $isDirty = $collection->isDirty();
50
51 if (! $isInitialized && ! $isDirty) {
52 return;
53 }
54
55 $ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
56 $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
57
58 // Invalidate non initialized collections OR ordered collection
59 if ($isDirty && ! $isInitialized || $this->association->isOrdered()) {
60 $this->persister->update($collection);
61
62 $this->queuedCache['delete'][spl_object_id($collection)] = $key;
63
64 return;
65 }
66
67 $this->persister->update($collection);
68
69 $this->queuedCache['update'][spl_object_id($collection)] = [
70 'key' => $key,
71 'list' => $collection,
72 ];
73 }
74}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php
new file mode 100644
index 0000000..96e0a4b
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Collection;
6
7use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyCollection;
8use Doctrine\ORM\PersistentCollection;
9use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
10
11class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
12{
13 public function update(PersistentCollection $collection): void
14 {
15 if ($collection->isDirty() && $collection->getSnapshot()) {
16 throw CannotUpdateReadOnlyCollection::fromEntityAndField(
17 DefaultProxyClassNameResolver::getClass($collection->getOwner()),
18 $this->association->fieldName,
19 );
20 }
21
22 parent::update($collection);
23 }
24}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php
new file mode 100644
index 0000000..347a065
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php
@@ -0,0 +1,103 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Collection;
6
7use Doctrine\ORM\Cache\CollectionCacheKey;
8use Doctrine\ORM\Cache\ConcurrentRegion;
9use Doctrine\ORM\EntityManagerInterface;
10use Doctrine\ORM\Mapping\AssociationMapping;
11use Doctrine\ORM\PersistentCollection;
12use Doctrine\ORM\Persisters\Collection\CollectionPersister;
13
14use function spl_object_id;
15
16class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
17{
18 public function __construct(
19 CollectionPersister $persister,
20 ConcurrentRegion $region,
21 EntityManagerInterface $em,
22 AssociationMapping $association,
23 ) {
24 parent::__construct($persister, $region, $em, $association);
25 }
26
27 public function afterTransactionComplete(): void
28 {
29 if (isset($this->queuedCache['update'])) {
30 foreach ($this->queuedCache['update'] as $item) {
31 $this->region->evict($item['key']);
32 }
33 }
34
35 if (isset($this->queuedCache['delete'])) {
36 foreach ($this->queuedCache['delete'] as $item) {
37 $this->region->evict($item['key']);
38 }
39 }
40
41 $this->queuedCache = [];
42 }
43
44 public function afterTransactionRolledBack(): void
45 {
46 if (isset($this->queuedCache['update'])) {
47 foreach ($this->queuedCache['update'] as $item) {
48 $this->region->evict($item['key']);
49 }
50 }
51
52 if (isset($this->queuedCache['delete'])) {
53 foreach ($this->queuedCache['delete'] as $item) {
54 $this->region->evict($item['key']);
55 }
56 }
57
58 $this->queuedCache = [];
59 }
60
61 public function delete(PersistentCollection $collection): void
62 {
63 $ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
64 $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
65 $lock = $this->region->lock($key);
66
67 $this->persister->delete($collection);
68
69 if ($lock === null) {
70 return;
71 }
72
73 $this->queuedCache['delete'][spl_object_id($collection)] = [
74 'key' => $key,
75 'lock' => $lock,
76 ];
77 }
78
79 public function update(PersistentCollection $collection): void
80 {
81 $isInitialized = $collection->isInitialized();
82 $isDirty = $collection->isDirty();
83
84 if (! $isInitialized && ! $isDirty) {
85 return;
86 }
87
88 $this->persister->update($collection);
89
90 $ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
91 $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
92 $lock = $this->region->lock($key);
93
94 if ($lock === null) {
95 return;
96 }
97
98 $this->queuedCache['update'][spl_object_id($collection)] = [
99 'key' => $key,
100 'lock' => $lock,
101 ];
102 }
103}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php
new file mode 100644
index 0000000..9f371d8
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php
@@ -0,0 +1,557 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Entity;
6
7use Doctrine\Common\Collections\Criteria;
8use Doctrine\Common\Collections\Order;
9use Doctrine\DBAL\LockMode;
10use Doctrine\ORM\Cache;
11use Doctrine\ORM\Cache\CollectionCacheKey;
12use Doctrine\ORM\Cache\EntityCacheKey;
13use Doctrine\ORM\Cache\EntityHydrator;
14use Doctrine\ORM\Cache\Logging\CacheLogger;
15use Doctrine\ORM\Cache\Persister\CachedPersister;
16use Doctrine\ORM\Cache\QueryCacheKey;
17use Doctrine\ORM\Cache\Region;
18use Doctrine\ORM\Cache\TimestampCacheKey;
19use Doctrine\ORM\Cache\TimestampRegion;
20use Doctrine\ORM\EntityManagerInterface;
21use Doctrine\ORM\Mapping\AssociationMapping;
22use Doctrine\ORM\Mapping\ClassMetadata;
23use Doctrine\ORM\Mapping\ClassMetadataFactory;
24use Doctrine\ORM\PersistentCollection;
25use Doctrine\ORM\Persisters\Entity\EntityPersister;
26use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
27use Doctrine\ORM\Query\ResultSetMapping;
28use Doctrine\ORM\UnitOfWork;
29
30use function array_merge;
31use function assert;
32use function serialize;
33use function sha1;
34
35abstract class AbstractEntityPersister implements CachedEntityPersister
36{
37 protected UnitOfWork $uow;
38 protected ClassMetadataFactory $metadataFactory;
39
40 /** @var mixed[] */
41 protected array $queuedCache = [];
42
43 protected TimestampRegion $timestampRegion;
44 protected TimestampCacheKey $timestampKey;
45 protected EntityHydrator $hydrator;
46 protected Cache $cache;
47 protected CacheLogger|null $cacheLogger = null;
48 protected string $regionName;
49
50 /**
51 * Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
52 *
53 * @var array<string>|null
54 */
55 protected array|null $joinedAssociations = null;
56
57 public function __construct(
58 protected EntityPersister $persister,
59 protected Region $region,
60 EntityManagerInterface $em,
61 protected ClassMetadata $class,
62 ) {
63 $configuration = $em->getConfiguration();
64 $cacheConfig = $configuration->getSecondLevelCacheConfiguration();
65 $cacheFactory = $cacheConfig->getCacheFactory();
66
67 $this->cache = $em->getCache();
68 $this->regionName = $region->getName();
69 $this->uow = $em->getUnitOfWork();
70 $this->metadataFactory = $em->getMetadataFactory();
71 $this->cacheLogger = $cacheConfig->getCacheLogger();
72 $this->timestampRegion = $cacheFactory->getTimestampRegion();
73 $this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
74 $this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
75 }
76
77 public function addInsert(object $entity): void
78 {
79 $this->persister->addInsert($entity);
80 }
81
82 /**
83 * {@inheritDoc}
84 */
85 public function getInserts(): array
86 {
87 return $this->persister->getInserts();
88 }
89
90 public function getSelectSQL(
91 array|Criteria $criteria,
92 AssociationMapping|null $assoc = null,
93 LockMode|int|null $lockMode = null,
94 int|null $limit = null,
95 int|null $offset = null,
96 array|null $orderBy = null,
97 ): string {
98 return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy);
99 }
100
101 public function getCountSQL(array|Criteria $criteria = []): string
102 {
103 return $this->persister->getCountSQL($criteria);
104 }
105
106 public function getInsertSQL(): string
107 {
108 return $this->persister->getInsertSQL();
109 }
110
111 public function getResultSetMapping(): ResultSetMapping
112 {
113 return $this->persister->getResultSetMapping();
114 }
115
116 public function getSelectConditionStatementSQL(
117 string $field,
118 mixed $value,
119 AssociationMapping|null $assoc = null,
120 string|null $comparison = null,
121 ): string {
122 return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison);
123 }
124
125 public function exists(object $entity, Criteria|null $extraConditions = null): bool
126 {
127 if ($extraConditions === null) {
128 $key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity));
129
130 if ($this->region->contains($key)) {
131 return true;
132 }
133 }
134
135 return $this->persister->exists($entity, $extraConditions);
136 }
137
138 public function getCacheRegion(): Region
139 {
140 return $this->region;
141 }
142
143 public function getEntityHydrator(): EntityHydrator
144 {
145 return $this->hydrator;
146 }
147
148 public function storeEntityCache(object $entity, EntityCacheKey $key): bool
149 {
150 $class = $this->class;
151 $className = DefaultProxyClassNameResolver::getClass($entity);
152
153 if ($className !== $this->class->name) {
154 $class = $this->metadataFactory->getMetadataFor($className);
155 }
156
157 $entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
158 $cached = $this->region->put($key, $entry);
159
160 if ($cached) {
161 $this->cacheLogger?->entityCachePut($this->regionName, $key);
162 }
163
164 return $cached;
165 }
166
167 private function storeJoinedAssociations(object $entity): void
168 {
169 if ($this->joinedAssociations === null) {
170 $associations = [];
171
172 foreach ($this->class->associationMappings as $name => $assoc) {
173 if (
174 isset($assoc->cache) &&
175 ($assoc->isToOne()) &&
176 ($assoc->fetch === ClassMetadata::FETCH_EAGER || ! $assoc->isOwningSide())
177 ) {
178 $associations[] = $name;
179 }
180 }
181
182 $this->joinedAssociations = $associations;
183 }
184
185 foreach ($this->joinedAssociations as $name) {
186 $assoc = $this->class->associationMappings[$name];
187 $assocEntity = $this->class->getFieldValue($entity, $name);
188
189 if ($assocEntity === null) {
190 continue;
191 }
192
193 $assocId = $this->uow->getEntityIdentifier($assocEntity);
194 $assocMetadata = $this->metadataFactory->getMetadataFor($assoc->targetEntity);
195 $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
196 $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity);
197
198 $assocPersister->storeEntityCache($assocEntity, $assocKey);
199 }
200 }
201
202 /**
203 * Generates a string of currently query
204 *
205 * @param string[]|Criteria $criteria
206 * @param array<string, Order>|null $orderBy
207 */
208 protected function getHash(
209 string $query,
210 array|Criteria $criteria,
211 array|null $orderBy = null,
212 int|null $limit = null,
213 int|null $offset = null,
214 ): string {
215 [$params] = $criteria instanceof Criteria
216 ? $this->persister->expandCriteriaParameters($criteria)
217 : $this->persister->expandParameters($criteria);
218
219 return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
220 }
221
222 /**
223 * {@inheritDoc}
224 */
225 public function expandParameters(array $criteria): array
226 {
227 return $this->persister->expandParameters($criteria);
228 }
229
230 /**
231 * {@inheritDoc}
232 */
233 public function expandCriteriaParameters(Criteria $criteria): array
234 {
235 return $this->persister->expandCriteriaParameters($criteria);
236 }
237
238 public function getClassMetadata(): ClassMetadata
239 {
240 return $this->persister->getClassMetadata();
241 }
242
243 /**
244 * {@inheritDoc}
245 */
246 public function getManyToManyCollection(
247 AssociationMapping $assoc,
248 object $sourceEntity,
249 int|null $offset = null,
250 int|null $limit = null,
251 ): array {
252 return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit);
253 }
254
255 /**
256 * {@inheritDoc}
257 */
258 public function getOneToManyCollection(
259 AssociationMapping $assoc,
260 object $sourceEntity,
261 int|null $offset = null,
262 int|null $limit = null,
263 ): array {
264 return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit);
265 }
266
267 public function getOwningTable(string $fieldName): string
268 {
269 return $this->persister->getOwningTable($fieldName);
270 }
271
272 public function executeInserts(): void
273 {
274 // The commit order/foreign key relationships may make it necessary that multiple calls to executeInsert()
275 // are performed, so collect all the new entities.
276 $newInserts = $this->persister->getInserts();
277
278 if ($newInserts) {
279 $this->queuedCache['insert'] = array_merge($this->queuedCache['insert'] ?? [], $newInserts);
280 }
281
282 $this->persister->executeInserts();
283 }
284
285 /**
286 * {@inheritDoc}
287 */
288 public function load(
289 array $criteria,
290 object|null $entity = null,
291 AssociationMapping|null $assoc = null,
292 array $hints = [],
293 LockMode|int|null $lockMode = null,
294 int|null $limit = null,
295 array|null $orderBy = null,
296 ): object|null {
297 if ($entity !== null || $assoc !== null || $hints !== [] || $lockMode !== null) {
298 return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
299 }
300
301 //handle only EntityRepository#findOneBy
302 $query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy);
303 $hash = $this->getHash($query, $criteria);
304 $rsm = $this->getResultSetMapping();
305 $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
306 $queryCache = $this->cache->getQueryCache($this->regionName);
307 $result = $queryCache->get($queryKey, $rsm);
308
309 if ($result !== null) {
310 $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey);
311
312 return $result[0];
313 }
314
315 $result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
316
317 if ($result === null) {
318 return null;
319 }
320
321 $cached = $queryCache->put($queryKey, $rsm, [$result]);
322
323 $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey);
324
325 if ($cached) {
326 $this->cacheLogger?->queryCachePut($this->regionName, $queryKey);
327 }
328
329 return $result;
330 }
331
332 /**
333 * {@inheritDoc}
334 */
335 public function loadAll(
336 array $criteria = [],
337 array|null $orderBy = null,
338 int|null $limit = null,
339 int|null $offset = null,
340 ): array {
341 $query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
342 $hash = $this->getHash($query, $criteria);
343 $rsm = $this->getResultSetMapping();
344 $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
345 $queryCache = $this->cache->getQueryCache($this->regionName);
346 $result = $queryCache->get($queryKey, $rsm);
347
348 if ($result !== null) {
349 $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey);
350
351 return $result;
352 }
353
354 $result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
355 $cached = $queryCache->put($queryKey, $rsm, $result);
356
357 if ($result) {
358 $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey);
359 }
360
361 if ($cached) {
362 $this->cacheLogger?->queryCachePut($this->regionName, $queryKey);
363 }
364
365 return $result;
366 }
367
368 /**
369 * {@inheritDoc}
370 */
371 public function loadById(array $identifier, object|null $entity = null): object|null
372 {
373 $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier);
374 $cacheEntry = $this->region->get($cacheKey);
375 $class = $this->class;
376
377 if ($cacheEntry !== null) {
378 if ($cacheEntry->class !== $this->class->name) {
379 $class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
380 }
381
382 $cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity);
383
384 if ($cachedEntity !== null) {
385 $this->cacheLogger?->entityCacheHit($this->regionName, $cacheKey);
386
387 return $cachedEntity;
388 }
389 }
390
391 $entity = $this->persister->loadById($identifier, $entity);
392
393 if ($entity === null) {
394 return null;
395 }
396
397 $class = $this->class;
398 $className = DefaultProxyClassNameResolver::getClass($entity);
399
400 if ($className !== $this->class->name) {
401 $class = $this->metadataFactory->getMetadataFor($className);
402 }
403
404 $cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
405 $cached = $this->region->put($cacheKey, $cacheEntry);
406
407 if ($cached && ($this->joinedAssociations === null || $this->joinedAssociations)) {
408 $this->storeJoinedAssociations($entity);
409 }
410
411 if ($cached) {
412 $this->cacheLogger?->entityCachePut($this->regionName, $cacheKey);
413 }
414
415 $this->cacheLogger?->entityCacheMiss($this->regionName, $cacheKey);
416
417 return $entity;
418 }
419
420 public function count(array|Criteria $criteria = []): int
421 {
422 return $this->persister->count($criteria);
423 }
424
425 /**
426 * {@inheritDoc}
427 */
428 public function loadCriteria(Criteria $criteria): array
429 {
430 $orderBy = $criteria->orderings();
431 $limit = $criteria->getMaxResults();
432 $offset = $criteria->getFirstResult();
433 $query = $this->persister->getSelectSQL($criteria);
434 $hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset);
435 $rsm = $this->getResultSetMapping();
436 $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
437 $queryCache = $this->cache->getQueryCache($this->regionName);
438 $cacheResult = $queryCache->get($queryKey, $rsm);
439
440 if ($cacheResult !== null) {
441 $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey);
442
443 return $cacheResult;
444 }
445
446 $result = $this->persister->loadCriteria($criteria);
447 $cached = $queryCache->put($queryKey, $rsm, $result);
448
449 if ($result) {
450 $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey);
451 }
452
453 if ($cached) {
454 $this->cacheLogger?->queryCachePut($this->regionName, $queryKey);
455 }
456
457 return $result;
458 }
459
460 /**
461 * {@inheritDoc}
462 */
463 public function loadManyToManyCollection(
464 AssociationMapping $assoc,
465 object $sourceEntity,
466 PersistentCollection $collection,
467 ): array {
468 $persister = $this->uow->getCollectionPersister($assoc);
469 $hasCache = ($persister instanceof CachedPersister);
470
471 if (! $hasCache) {
472 return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection);
473 }
474
475 $ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
476 $key = $this->buildCollectionCacheKey($assoc, $ownerId);
477 $list = $persister->loadCollectionCache($collection, $key);
478
479 if ($list !== null) {
480 $this->cacheLogger?->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
481
482 return $list;
483 }
484
485 $list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection);
486
487 $persister->storeCollectionCache($key, $list);
488
489 $this->cacheLogger?->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
490
491 return $list;
492 }
493
494 public function loadOneToManyCollection(
495 AssociationMapping $assoc,
496 object $sourceEntity,
497 PersistentCollection $collection,
498 ): mixed {
499 $persister = $this->uow->getCollectionPersister($assoc);
500 $hasCache = ($persister instanceof CachedPersister);
501
502 if (! $hasCache) {
503 return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection);
504 }
505
506 $ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
507 $key = $this->buildCollectionCacheKey($assoc, $ownerId);
508 $list = $persister->loadCollectionCache($collection, $key);
509
510 if ($list !== null) {
511 $this->cacheLogger?->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
512
513 return $list;
514 }
515
516 $list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection);
517
518 $persister->storeCollectionCache($key, $list);
519
520 $this->cacheLogger?->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
521
522 return $list;
523 }
524
525 /**
526 * {@inheritDoc}
527 */
528 public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null
529 {
530 return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
531 }
532
533 /**
534 * {@inheritDoc}
535 */
536 public function lock(array $criteria, LockMode|int $lockMode): void
537 {
538 $this->persister->lock($criteria, $lockMode);
539 }
540
541 /**
542 * {@inheritDoc}
543 */
544 public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void
545 {
546 $this->persister->refresh($id, $entity, $lockMode);
547 }
548
549 /** @param array<string, mixed> $ownerId */
550 protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId): CollectionCacheKey
551 {
552 $metadata = $this->metadataFactory->getMetadataFor($association->sourceEntity);
553 assert($metadata instanceof ClassMetadata);
554
555 return new CollectionCacheKey($metadata->rootEntityName, $association->fieldName, $ownerId);
556 }
557}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php
new file mode 100644
index 0000000..5fba56f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Entity;
6
7use Doctrine\ORM\Cache\EntityCacheKey;
8use Doctrine\ORM\Cache\EntityHydrator;
9use Doctrine\ORM\Cache\Persister\CachedPersister;
10use Doctrine\ORM\Persisters\Entity\EntityPersister;
11
12/**
13 * Interface for second level cache entity persisters.
14 */
15interface CachedEntityPersister extends CachedPersister, EntityPersister
16{
17 public function getEntityHydrator(): EntityHydrator;
18
19 public function storeEntityCache(object $entity, EntityCacheKey $key): bool;
20}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php
new file mode 100644
index 0000000..43c76ab
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php
@@ -0,0 +1,85 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Entity;
6
7use Doctrine\ORM\Cache\EntityCacheKey;
8
9/**
10 * Specific non-strict read/write cached entity persister
11 */
12class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
13{
14 public function afterTransactionComplete(): void
15 {
16 $isChanged = false;
17
18 if (isset($this->queuedCache['insert'])) {
19 foreach ($this->queuedCache['insert'] as $entity) {
20 $isChanged = $this->updateCache($entity, $isChanged);
21 }
22 }
23
24 if (isset($this->queuedCache['update'])) {
25 foreach ($this->queuedCache['update'] as $entity) {
26 $isChanged = $this->updateCache($entity, $isChanged);
27 }
28 }
29
30 if (isset($this->queuedCache['delete'])) {
31 foreach ($this->queuedCache['delete'] as $key) {
32 $this->region->evict($key);
33
34 $isChanged = true;
35 }
36 }
37
38 if ($isChanged) {
39 $this->timestampRegion->update($this->timestampKey);
40 }
41
42 $this->queuedCache = [];
43 }
44
45 public function afterTransactionRolledBack(): void
46 {
47 $this->queuedCache = [];
48 }
49
50 public function delete(object $entity): bool
51 {
52 $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
53 $deleted = $this->persister->delete($entity);
54
55 if ($deleted) {
56 $this->region->evict($key);
57 }
58
59 $this->queuedCache['delete'][] = $key;
60
61 return $deleted;
62 }
63
64 public function update(object $entity): void
65 {
66 $this->persister->update($entity);
67
68 $this->queuedCache['update'][] = $entity;
69 }
70
71 private function updateCache(object $entity, bool $isChanged): bool
72 {
73 $class = $this->metadataFactory->getMetadataFor($entity::class);
74 $key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
75 $entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
76 $cached = $this->region->put($key, $entry);
77 $isChanged = $isChanged || $cached;
78
79 if ($cached) {
80 $this->cacheLogger?->entityCachePut($this->regionName, $key);
81 }
82
83 return $isChanged;
84 }
85}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php
new file mode 100644
index 0000000..4cd1784
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Entity;
6
7use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity;
8use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
9
10/**
11 * Specific read-only region entity persister
12 */
13class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
14{
15 public function update(object $entity): void
16 {
17 throw CannotUpdateReadOnlyEntity::fromEntity(DefaultProxyClassNameResolver::getClass($entity));
18 }
19}
diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php
new file mode 100644
index 0000000..a1ea0dc
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php
@@ -0,0 +1,105 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Persister\Entity;
6
7use Doctrine\ORM\Cache\ConcurrentRegion;
8use Doctrine\ORM\Cache\EntityCacheKey;
9use Doctrine\ORM\EntityManagerInterface;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\Persisters\Entity\EntityPersister;
12
13/**
14 * Specific read-write entity persister
15 */
16class ReadWriteCachedEntityPersister extends AbstractEntityPersister
17{
18 public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class)
19 {
20 parent::__construct($persister, $region, $em, $class);
21 }
22
23 public function afterTransactionComplete(): void
24 {
25 $isChanged = true;
26
27 if (isset($this->queuedCache['update'])) {
28 foreach ($this->queuedCache['update'] as $item) {
29 $this->region->evict($item['key']);
30
31 $isChanged = true;
32 }
33 }
34
35 if (isset($this->queuedCache['delete'])) {
36 foreach ($this->queuedCache['delete'] as $item) {
37 $this->region->evict($item['key']);
38
39 $isChanged = true;
40 }
41 }
42
43 if ($isChanged) {
44 $this->timestampRegion->update($this->timestampKey);
45 }
46
47 $this->queuedCache = [];
48 }
49
50 public function afterTransactionRolledBack(): void
51 {
52 if (isset($this->queuedCache['update'])) {
53 foreach ($this->queuedCache['update'] as $item) {
54 $this->region->evict($item['key']);
55 }
56 }
57
58 if (isset($this->queuedCache['delete'])) {
59 foreach ($this->queuedCache['delete'] as $item) {
60 $this->region->evict($item['key']);
61 }
62 }
63
64 $this->queuedCache = [];
65 }
66
67 public function delete(object $entity): bool
68 {
69 $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
70 $lock = $this->region->lock($key);
71 $deleted = $this->persister->delete($entity);
72
73 if ($deleted) {
74 $this->region->evict($key);
75 }
76
77 if ($lock === null) {
78 return $deleted;
79 }
80
81 $this->queuedCache['delete'][] = [
82 'lock' => $lock,
83 'key' => $key,
84 ];
85
86 return $deleted;
87 }
88
89 public function update(object $entity): void
90 {
91 $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
92 $lock = $this->region->lock($key);
93
94 $this->persister->update($entity);
95
96 if ($lock === null) {
97 return;
98 }
99
100 $this->queuedCache['update'][] = [
101 'lock' => $lock,
102 'key' => $key,
103 ];
104 }
105}
diff --git a/vendor/doctrine/orm/src/Cache/QueryCache.php b/vendor/doctrine/orm/src/Cache/QueryCache.php
new file mode 100644
index 0000000..e697680
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/QueryCache.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Query\ResultSetMapping;
8
9/**
10 * Defines the contract for caches capable of storing query results.
11 * These caches should only concern themselves with storing the matching result ids.
12 */
13interface QueryCache
14{
15 public function clear(): bool;
16
17 /** @param mixed[] $hints */
18 public function put(QueryCacheKey $key, ResultSetMapping $rsm, mixed $result, array $hints = []): bool;
19
20 /**
21 * @param mixed[] $hints
22 *
23 * @return mixed[]|null
24 */
25 public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []): array|null;
26
27 public function getRegion(): Region;
28}
diff --git a/vendor/doctrine/orm/src/Cache/QueryCacheEntry.php b/vendor/doctrine/orm/src/Cache/QueryCacheEntry.php
new file mode 100644
index 0000000..1e39262
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/QueryCacheEntry.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use function microtime;
8
9class QueryCacheEntry implements CacheEntry
10{
11 /**
12 * Time creation of this cache entry
13 */
14 public readonly float $time;
15
16 /** @param array<string, mixed> $result List of entity identifiers */
17 public function __construct(
18 public readonly array $result,
19 float|null $time = null,
20 ) {
21 $this->time = $time ?: microtime(true);
22 }
23
24 /** @param array<string, mixed> $values */
25 public static function __set_state(array $values): self
26 {
27 return new self($values['result'], $values['time']);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Cache/QueryCacheKey.php b/vendor/doctrine/orm/src/Cache/QueryCacheKey.php
new file mode 100644
index 0000000..2372e5a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/QueryCacheKey.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Cache;
8
9/**
10 * A cache key that identifies a particular query.
11 */
12class QueryCacheKey extends CacheKey
13{
14 /** @param Cache::MODE_* $cacheMode */
15 public function __construct(
16 string $cacheId,
17 public readonly int $lifetime = 0,
18 public readonly int $cacheMode = Cache::MODE_NORMAL,
19 public readonly TimestampCacheKey|null $timestampKey = null,
20 ) {
21 parent::__construct($cacheId);
22 }
23}
diff --git a/vendor/doctrine/orm/src/Cache/QueryCacheValidator.php b/vendor/doctrine/orm/src/Cache/QueryCacheValidator.php
new file mode 100644
index 0000000..8a0d39f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/QueryCacheValidator.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7/**
8 * Cache query validator interface.
9 */
10interface QueryCacheValidator
11{
12 /**
13 * Checks if the query entry is valid
14 */
15 public function isValid(QueryCacheKey $key, QueryCacheEntry $entry): bool;
16}
diff --git a/vendor/doctrine/orm/src/Cache/Region.php b/vendor/doctrine/orm/src/Cache/Region.php
new file mode 100644
index 0000000..f7a1b26
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Region.php
@@ -0,0 +1,73 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use Doctrine\ORM\Cache\Exception\CacheException;
8
9/**
10 * Defines a contract for accessing a particular named region.
11 */
12interface Region
13{
14 /**
15 * Retrieve the name of this region.
16 */
17 public function getName(): string;
18
19 /**
20 * Determine whether this region contains data for the given key.
21 *
22 * @param CacheKey $key The cache key
23 */
24 public function contains(CacheKey $key): bool;
25
26 /**
27 * Get an item from the cache.
28 *
29 * @param CacheKey $key The key of the item to be retrieved.
30 *
31 * @return CacheEntry|null The cached entry or NULL
32 *
33 * @throws CacheException Indicates a problem accessing the item or region.
34 */
35 public function get(CacheKey $key): CacheEntry|null;
36
37 /**
38 * Get all items from the cache identified by $keys.
39 * It returns NULL if some elements can not be found.
40 *
41 * @param CollectionCacheEntry $collection The collection of the items to be retrieved.
42 *
43 * @return CacheEntry[]|null The cached entries or NULL if one or more entries can not be found
44 */
45 public function getMultiple(CollectionCacheEntry $collection): array|null;
46
47 /**
48 * Put an item into the cache.
49 *
50 * @param CacheKey $key The key under which to cache the item.
51 * @param CacheEntry $entry The entry to cache.
52 * @param Lock|null $lock The lock previously obtained.
53 *
54 * @throws CacheException Indicates a problem accessing the region.
55 */
56 public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool;
57
58 /**
59 * Remove an item from the cache.
60 *
61 * @param CacheKey $key The key under which to cache the item.
62 *
63 * @throws CacheException Indicates a problem accessing the region.
64 */
65 public function evict(CacheKey $key): bool;
66
67 /**
68 * Remove all contents of this particular cache region.
69 *
70 * @throws CacheException Indicates problem accessing the region.
71 */
72 public function evictAll(): bool;
73}
diff --git a/vendor/doctrine/orm/src/Cache/Region/DefaultRegion.php b/vendor/doctrine/orm/src/Cache/Region/DefaultRegion.php
new file mode 100644
index 0000000..0576195
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Region/DefaultRegion.php
@@ -0,0 +1,113 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Region;
6
7use Doctrine\ORM\Cache\CacheEntry;
8use Doctrine\ORM\Cache\CacheKey;
9use Doctrine\ORM\Cache\CollectionCacheEntry;
10use Doctrine\ORM\Cache\Lock;
11use Doctrine\ORM\Cache\Region;
12use Psr\Cache\CacheItemInterface;
13use Psr\Cache\CacheItemPoolInterface;
14use Traversable;
15
16use function array_map;
17use function iterator_to_array;
18use function strtr;
19
20/**
21 * The simplest cache region compatible with all doctrine-cache drivers.
22 */
23class DefaultRegion implements Region
24{
25 private const REGION_KEY_SEPARATOR = '_';
26 private const REGION_PREFIX = 'DC2_REGION_';
27
28 public function __construct(
29 private readonly string $name,
30 private readonly CacheItemPoolInterface $cacheItemPool,
31 private readonly int $lifetime = 0,
32 ) {
33 }
34
35 public function getName(): string
36 {
37 return $this->name;
38 }
39
40 public function contains(CacheKey $key): bool
41 {
42 return $this->cacheItemPool->hasItem($this->getCacheEntryKey($key));
43 }
44
45 public function get(CacheKey $key): CacheEntry|null
46 {
47 $item = $this->cacheItemPool->getItem($this->getCacheEntryKey($key));
48 $entry = $item->isHit() ? $item->get() : null;
49
50 if (! $entry instanceof CacheEntry) {
51 return null;
52 }
53
54 return $entry;
55 }
56
57 public function getMultiple(CollectionCacheEntry $collection): array|null
58 {
59 $keys = array_map(
60 $this->getCacheEntryKey(...),
61 $collection->identifiers,
62 );
63 /** @var iterable<string, CacheItemInterface> $items */
64 $items = $this->cacheItemPool->getItems($keys);
65 if ($items instanceof Traversable) {
66 $items = iterator_to_array($items);
67 }
68
69 $result = [];
70 foreach ($keys as $arrayKey => $cacheKey) {
71 if (! isset($items[$cacheKey]) || ! $items[$cacheKey]->isHit()) {
72 return null;
73 }
74
75 $entry = $items[$cacheKey]->get();
76 if (! $entry instanceof CacheEntry) {
77 return null;
78 }
79
80 $result[$arrayKey] = $entry;
81 }
82
83 return $result;
84 }
85
86 public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool
87 {
88 $item = $this->cacheItemPool
89 ->getItem($this->getCacheEntryKey($key))
90 ->set($entry);
91
92 if ($this->lifetime > 0) {
93 $item->expiresAfter($this->lifetime);
94 }
95
96 return $this->cacheItemPool->save($item);
97 }
98
99 public function evict(CacheKey $key): bool
100 {
101 return $this->cacheItemPool->deleteItem($this->getCacheEntryKey($key));
102 }
103
104 public function evictAll(): bool
105 {
106 return $this->cacheItemPool->clear(self::REGION_PREFIX . $this->name);
107 }
108
109 private function getCacheEntryKey(CacheKey $key): string
110 {
111 return self::REGION_PREFIX . $this->name . self::REGION_KEY_SEPARATOR . strtr($key->hash, '{}()/\@:', '________');
112 }
113}
diff --git a/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php b/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php
new file mode 100644
index 0000000..bedd6a6
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php
@@ -0,0 +1,194 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Region;
6
7use Doctrine\ORM\Cache\CacheEntry;
8use Doctrine\ORM\Cache\CacheKey;
9use Doctrine\ORM\Cache\CollectionCacheEntry;
10use Doctrine\ORM\Cache\ConcurrentRegion;
11use Doctrine\ORM\Cache\Lock;
12use Doctrine\ORM\Cache\Region;
13use InvalidArgumentException;
14
15use function array_filter;
16use function array_map;
17use function chmod;
18use function file_get_contents;
19use function file_put_contents;
20use function fileatime;
21use function glob;
22use function is_dir;
23use function is_file;
24use function is_writable;
25use function mkdir;
26use function sprintf;
27use function time;
28use function unlink;
29
30use const DIRECTORY_SEPARATOR;
31use const LOCK_EX;
32
33/**
34 * Very naive concurrent region, based on file locks.
35 */
36class FileLockRegion implements ConcurrentRegion
37{
38 final public const LOCK_EXTENSION = 'lock';
39
40 /**
41 * @param numeric-string|int $lockLifetime
42 *
43 * @throws InvalidArgumentException
44 */
45 public function __construct(
46 private readonly Region $region,
47 private readonly string $directory,
48 private readonly string|int $lockLifetime,
49 ) {
50 if (! is_dir($directory) && ! @mkdir($directory, 0775, true)) {
51 throw new InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
52 }
53
54 if (! is_writable($directory)) {
55 throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
56 }
57 }
58
59 private function isLocked(CacheKey $key, Lock|null $lock = null): bool
60 {
61 $filename = $this->getLockFileName($key);
62
63 if (! is_file($filename)) {
64 return false;
65 }
66
67 $time = $this->getLockTime($filename);
68 $content = $this->getLockContent($filename);
69
70 if ($content === false || $time === false) {
71 @unlink($filename);
72
73 return false;
74 }
75
76 if ($lock && $content === $lock->value) {
77 return false;
78 }
79
80 // outdated lock
81 if ($time + $this->lockLifetime <= time()) {
82 @unlink($filename);
83
84 return false;
85 }
86
87 return true;
88 }
89
90 private function getLockFileName(CacheKey $key): string
91 {
92 return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
93 }
94
95 private function getLockContent(string $filename): string|false
96 {
97 return @file_get_contents($filename);
98 }
99
100 private function getLockTime(string $filename): int|false
101 {
102 return @fileatime($filename);
103 }
104
105 public function getName(): string
106 {
107 return $this->region->getName();
108 }
109
110 public function contains(CacheKey $key): bool
111 {
112 if ($this->isLocked($key)) {
113 return false;
114 }
115
116 return $this->region->contains($key);
117 }
118
119 public function get(CacheKey $key): CacheEntry|null
120 {
121 if ($this->isLocked($key)) {
122 return null;
123 }
124
125 return $this->region->get($key);
126 }
127
128 public function getMultiple(CollectionCacheEntry $collection): array|null
129 {
130 if (array_filter(array_map($this->isLocked(...), $collection->identifiers))) {
131 return null;
132 }
133
134 return $this->region->getMultiple($collection);
135 }
136
137 public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool
138 {
139 if ($this->isLocked($key, $lock)) {
140 return false;
141 }
142
143 return $this->region->put($key, $entry);
144 }
145
146 public function evict(CacheKey $key): bool
147 {
148 if ($this->isLocked($key)) {
149 @unlink($this->getLockFileName($key));
150 }
151
152 return $this->region->evict($key);
153 }
154
155 public function evictAll(): bool
156 {
157 // The check below is necessary because on some platforms glob returns false
158 // when nothing matched (even though no errors occurred)
159 $filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)) ?: [];
160
161 foreach ($filenames as $filename) {
162 @unlink($filename);
163 }
164
165 return $this->region->evictAll();
166 }
167
168 public function lock(CacheKey $key): Lock|null
169 {
170 if ($this->isLocked($key)) {
171 return null;
172 }
173
174 $lock = Lock::createLockRead();
175 $filename = $this->getLockFileName($key);
176
177 if (@file_put_contents($filename, $lock->value, LOCK_EX) === false) {
178 return null;
179 }
180
181 chmod($filename, 0664);
182
183 return $lock;
184 }
185
186 public function unlock(CacheKey $key, Lock $lock): bool
187 {
188 if ($this->isLocked($key, $lock)) {
189 return false;
190 }
191
192 return @unlink($this->getLockFileName($key));
193 }
194}
diff --git a/vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php b/vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php
new file mode 100644
index 0000000..aa75a90
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Region;
6
7use Doctrine\ORM\Cache\CacheKey;
8use Doctrine\ORM\Cache\TimestampCacheEntry;
9use Doctrine\ORM\Cache\TimestampRegion;
10
11/**
12 * Tracks the timestamps of the most recent updates to particular keys.
13 */
14class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
15{
16 public function update(CacheKey $key): void
17 {
18 $this->put($key, new TimestampCacheEntry());
19 }
20}
diff --git a/vendor/doctrine/orm/src/Cache/RegionsConfiguration.php b/vendor/doctrine/orm/src/Cache/RegionsConfiguration.php
new file mode 100644
index 0000000..a852831
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/RegionsConfiguration.php
@@ -0,0 +1,63 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7/**
8 * Cache regions configuration
9 */
10class RegionsConfiguration
11{
12 /** @var array<string,int> */
13 private array $lifetimes = [];
14
15 /** @var array<string,int> */
16 private array $lockLifetimes = [];
17
18 public function __construct(
19 private int $defaultLifetime = 3600,
20 private int $defaultLockLifetime = 60,
21 ) {
22 }
23
24 public function getDefaultLifetime(): int
25 {
26 return $this->defaultLifetime;
27 }
28
29 public function setDefaultLifetime(int $defaultLifetime): void
30 {
31 $this->defaultLifetime = $defaultLifetime;
32 }
33
34 public function getDefaultLockLifetime(): int
35 {
36 return $this->defaultLockLifetime;
37 }
38
39 public function setDefaultLockLifetime(int $defaultLockLifetime): void
40 {
41 $this->defaultLockLifetime = $defaultLockLifetime;
42 }
43
44 public function getLifetime(string $regionName): int
45 {
46 return $this->lifetimes[$regionName] ?? $this->defaultLifetime;
47 }
48
49 public function setLifetime(string $name, int $lifetime): void
50 {
51 $this->lifetimes[$name] = $lifetime;
52 }
53
54 public function getLockLifetime(string $regionName): int
55 {
56 return $this->lockLifetimes[$regionName] ?? $this->defaultLockLifetime;
57 }
58
59 public function setLockLifetime(string $name, int $lifetime): void
60 {
61 $this->lockLifetimes[$name] = $lifetime;
62 }
63}
diff --git a/vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php b/vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php
new file mode 100644
index 0000000..60c9175
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use function microtime;
8
9class TimestampCacheEntry implements CacheEntry
10{
11 public readonly float $time;
12
13 public function __construct(float|null $time = null)
14 {
15 $this->time = $time ?? microtime(true);
16 }
17
18 /**
19 * Creates a new TimestampCacheEntry
20 *
21 * This method allow Doctrine\Common\Cache\PhpFileCache compatibility
22 *
23 * @param array<string,float> $values array containing property values
24 */
25 public static function __set_state(array $values): TimestampCacheEntry
26 {
27 return new self($values['time']);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Cache/TimestampCacheKey.php b/vendor/doctrine/orm/src/Cache/TimestampCacheKey.php
new file mode 100644
index 0000000..5aef4c5
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/TimestampCacheKey.php
@@ -0,0 +1,17 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7/**
8 * A key that identifies a timestamped space.
9 */
10class TimestampCacheKey extends CacheKey
11{
12 /** @param string $space Result cache id */
13 public function __construct(string $space)
14 {
15 parent::__construct($space);
16 }
17}
diff --git a/vendor/doctrine/orm/src/Cache/TimestampQueryCacheValidator.php b/vendor/doctrine/orm/src/Cache/TimestampQueryCacheValidator.php
new file mode 100644
index 0000000..9824088
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/TimestampQueryCacheValidator.php
@@ -0,0 +1,38 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7use function microtime;
8
9class TimestampQueryCacheValidator implements QueryCacheValidator
10{
11 public function __construct(private readonly TimestampRegion $timestampRegion)
12 {
13 }
14
15 public function isValid(QueryCacheKey $key, QueryCacheEntry $entry): bool
16 {
17 if ($this->regionUpdated($key, $entry)) {
18 return false;
19 }
20
21 if ($key->lifetime === 0) {
22 return true;
23 }
24
25 return $entry->time + $key->lifetime > microtime(true);
26 }
27
28 private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry): bool
29 {
30 if ($key->timestampKey === null) {
31 return false;
32 }
33
34 $timestamp = $this->timestampRegion->get($key->timestampKey);
35
36 return $timestamp && $timestamp->time > $entry->time;
37 }
38}
diff --git a/vendor/doctrine/orm/src/Cache/TimestampRegion.php b/vendor/doctrine/orm/src/Cache/TimestampRegion.php
new file mode 100644
index 0000000..b74fa8d
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/TimestampRegion.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache;
6
7/**
8 * Defines the contract for a cache region which will specifically be used to store entity "update timestamps".
9 */
10interface TimestampRegion extends Region
11{
12 /**
13 * Update a specific key into the cache region.
14 *
15 * @throws LockException Indicates a problem accessing the region.
16 */
17 public function update(CacheKey $key): void;
18}