From bf6655a534a6775d30cafa67bd801276bda1d98d Mon Sep 17 00:00:00 2001 From: polo Date: Tue, 13 Aug 2024 23:45:21 +0200 Subject: =?UTF-8?q?VERSION=200.2=20doctrine=20ORM=20et=20entit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../orm/src/Cache/AssociationCacheEntry.php | 30 ++ .../doctrine/orm/src/Cache/CacheConfiguration.php | 60 +++ vendor/doctrine/orm/src/Cache/CacheEntry.php | 16 + vendor/doctrine/orm/src/Cache/CacheException.php | 26 + vendor/doctrine/orm/src/Cache/CacheFactory.php | 64 +++ vendor/doctrine/orm/src/Cache/CacheKey.php | 16 + .../orm/src/Cache/CollectionCacheEntry.php | 25 + .../doctrine/orm/src/Cache/CollectionCacheKey.php | 39 ++ .../doctrine/orm/src/Cache/CollectionHydrator.php | 21 + vendor/doctrine/orm/src/Cache/ConcurrentRegion.php | 36 ++ vendor/doctrine/orm/src/Cache/DefaultCache.php | 245 +++++++++ .../doctrine/orm/src/Cache/DefaultCacheFactory.php | 189 +++++++ .../orm/src/Cache/DefaultCollectionHydrator.php | 75 +++ .../orm/src/Cache/DefaultEntityHydrator.php | 176 +++++++ .../doctrine/orm/src/Cache/DefaultQueryCache.php | 414 +++++++++++++++ vendor/doctrine/orm/src/Cache/EntityCacheEntry.php | 50 ++ vendor/doctrine/orm/src/Cache/EntityCacheKey.php | 38 ++ vendor/doctrine/orm/src/Cache/EntityHydrator.php | 28 ++ .../orm/src/Cache/Exception/CacheException.php | 14 + .../Exception/CannotUpdateReadOnlyCollection.php | 19 + .../Cache/Exception/CannotUpdateReadOnlyEntity.php | 15 + .../src/Cache/Exception/FeatureNotImplemented.php | 23 + .../orm/src/Cache/Exception/NonCacheableEntity.php | 18 + .../Exception/NonCacheableEntityAssociation.php | 19 + vendor/doctrine/orm/src/Cache/Lock.php | 25 + vendor/doctrine/orm/src/Cache/LockException.php | 14 + .../doctrine/orm/src/Cache/Logging/CacheLogger.php | 60 +++ .../orm/src/Cache/Logging/CacheLoggerChain.php | 94 ++++ .../src/Cache/Logging/StatisticsCacheLogger.php | 174 +++++++ .../orm/src/Cache/Persister/CachedPersister.php | 25 + .../Collection/AbstractCollectionPersister.php | 168 +++++++ .../Collection/CachedCollectionPersister.php | 36 ++ ...NonStrictReadWriteCachedCollectionPersister.php | 74 +++ .../ReadOnlyCachedCollectionPersister.php | 24 + .../ReadWriteCachedCollectionPersister.php | 103 ++++ .../Persister/Entity/AbstractEntityPersister.php | 557 +++++++++++++++++++++ .../Persister/Entity/CachedEntityPersister.php | 20 + .../NonStrictReadWriteCachedEntityPersister.php | 85 ++++ .../Entity/ReadOnlyCachedEntityPersister.php | 19 + .../Entity/ReadWriteCachedEntityPersister.php | 105 ++++ vendor/doctrine/orm/src/Cache/QueryCache.php | 28 ++ vendor/doctrine/orm/src/Cache/QueryCacheEntry.php | 29 ++ vendor/doctrine/orm/src/Cache/QueryCacheKey.php | 23 + .../doctrine/orm/src/Cache/QueryCacheValidator.php | 16 + vendor/doctrine/orm/src/Cache/Region.php | 73 +++ .../orm/src/Cache/Region/DefaultRegion.php | 113 +++++ .../orm/src/Cache/Region/FileLockRegion.php | 194 +++++++ .../orm/src/Cache/Region/UpdateTimestampCache.php | 20 + .../orm/src/Cache/RegionsConfiguration.php | 63 +++ .../doctrine/orm/src/Cache/TimestampCacheEntry.php | 29 ++ .../doctrine/orm/src/Cache/TimestampCacheKey.php | 17 + .../orm/src/Cache/TimestampQueryCacheValidator.php | 38 ++ vendor/doctrine/orm/src/Cache/TimestampRegion.php | 18 + 53 files changed, 3900 insertions(+) create mode 100644 vendor/doctrine/orm/src/Cache/AssociationCacheEntry.php create mode 100644 vendor/doctrine/orm/src/Cache/CacheConfiguration.php create mode 100644 vendor/doctrine/orm/src/Cache/CacheEntry.php create mode 100644 vendor/doctrine/orm/src/Cache/CacheException.php create mode 100644 vendor/doctrine/orm/src/Cache/CacheFactory.php create mode 100644 vendor/doctrine/orm/src/Cache/CacheKey.php create mode 100644 vendor/doctrine/orm/src/Cache/CollectionCacheEntry.php create mode 100644 vendor/doctrine/orm/src/Cache/CollectionCacheKey.php create mode 100644 vendor/doctrine/orm/src/Cache/CollectionHydrator.php create mode 100644 vendor/doctrine/orm/src/Cache/ConcurrentRegion.php create mode 100644 vendor/doctrine/orm/src/Cache/DefaultCache.php create mode 100644 vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php create mode 100644 vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php create mode 100644 vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php create mode 100644 vendor/doctrine/orm/src/Cache/DefaultQueryCache.php create mode 100644 vendor/doctrine/orm/src/Cache/EntityCacheEntry.php create mode 100644 vendor/doctrine/orm/src/Cache/EntityCacheKey.php create mode 100644 vendor/doctrine/orm/src/Cache/EntityHydrator.php create mode 100644 vendor/doctrine/orm/src/Cache/Exception/CacheException.php create mode 100644 vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyCollection.php create mode 100644 vendor/doctrine/orm/src/Cache/Exception/CannotUpdateReadOnlyEntity.php create mode 100644 vendor/doctrine/orm/src/Cache/Exception/FeatureNotImplemented.php create mode 100644 vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntity.php create mode 100644 vendor/doctrine/orm/src/Cache/Exception/NonCacheableEntityAssociation.php create mode 100644 vendor/doctrine/orm/src/Cache/Lock.php create mode 100644 vendor/doctrine/orm/src/Cache/LockException.php create mode 100644 vendor/doctrine/orm/src/Cache/Logging/CacheLogger.php create mode 100644 vendor/doctrine/orm/src/Cache/Logging/CacheLoggerChain.php create mode 100644 vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Collection/AbstractCollectionPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php create mode 100644 vendor/doctrine/orm/src/Cache/QueryCache.php create mode 100644 vendor/doctrine/orm/src/Cache/QueryCacheEntry.php create mode 100644 vendor/doctrine/orm/src/Cache/QueryCacheKey.php create mode 100644 vendor/doctrine/orm/src/Cache/QueryCacheValidator.php create mode 100644 vendor/doctrine/orm/src/Cache/Region.php create mode 100644 vendor/doctrine/orm/src/Cache/Region/DefaultRegion.php create mode 100644 vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php create mode 100644 vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php create mode 100644 vendor/doctrine/orm/src/Cache/RegionsConfiguration.php create mode 100644 vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php create mode 100644 vendor/doctrine/orm/src/Cache/TimestampCacheKey.php create mode 100644 vendor/doctrine/orm/src/Cache/TimestampQueryCacheValidator.php create mode 100644 vendor/doctrine/orm/src/Cache/TimestampRegion.php (limited to 'vendor/doctrine/orm/src/Cache') 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 @@ + $identifier The entity identifier. + * @param class-string $class The entity class name + */ + public function __construct( + public readonly string $class, + public readonly array $identifier, + ) { + } + + /** + * Creates a new AssociationCacheEntry + * + * This method allow Doctrine\Common\Cache\PhpFileCache compatibility + * + * @param array $values array containing property values + */ + public static function __set_state(array $values): self + { + return new self($values['class'], $values['identifier']); + } +} 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 @@ +cacheFactory; + } + + public function setCacheFactory(CacheFactory $factory): void + { + $this->cacheFactory = $factory; + } + + public function getCacheLogger(): CacheLogger|null + { + return $this->cacheLogger; + } + + public function setCacheLogger(CacheLogger $logger): void + { + $this->cacheLogger = $logger; + } + + public function getRegionsConfiguration(): RegionsConfiguration + { + return $this->regionsConfig ??= new RegionsConfiguration(); + } + + public function setRegionsConfiguration(RegionsConfiguration $regionsConfig): void + { + $this->regionsConfig = $regionsConfig; + } + + public function getQueryValidator(): QueryCacheValidator + { + return $this->queryValidator ??= new TimestampQueryCacheValidator( + $this->cacheFactory->getTimestampRegion(), + ); + } + + public function setQueryValidator(QueryCacheValidator $validator): void + { + $this->queryValidator = $validator; + } +} 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 @@ +IMPORTANT NOTE: + * + * Fields of classes that implement CacheEntry are public for performance reason. + */ +interface CacheEntry +{ +} 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 @@ + $cache The cache configuration. + */ + public function getRegion(array $cache): Region; + + /** + * Build timestamp cache region + */ + public function getTimestampRegion(): TimestampRegion; + + /** + * Build \Doctrine\ORM\Cache + */ + public function createCache(EntityManagerInterface $entityManager): Cache; +} 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 @@ + $values array containing property values + */ + public static function __set_state(array $values): CollectionCacheEntry + { + return new self($values['identifiers']); + } +} 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 @@ + + */ + public readonly array $ownerIdentifier; + + /** + * @param array $ownerIdentifier The identifier of the owning entity. + * @param class-string $entityClass The owner entity class + */ + public function __construct( + public readonly string $entityClass, + public readonly string $association, + array $ownerIdentifier, + ) { + ksort($ownerIdentifier); + + $this->ownerIdentifier = $ownerIdentifier; + + parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association); + } +} 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 @@ + + */ + private array $queryCaches = []; + + private QueryCache|null $defaultQueryCache = null; + + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->uow = $em->getUnitOfWork(); + $this->cacheFactory = $em->getConfiguration() + ->getSecondLevelCacheConfiguration() + ->getCacheFactory(); + } + + public function getEntityCacheRegion(string $className): Region|null + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return null; + } + + return $persister->getCacheRegion(); + } + + public function getCollectionCacheRegion(string $className, string $association): Region|null + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return null; + } + + return $persister->getCacheRegion(); + } + + public function containsEntity(string $className, mixed $identifier): bool + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return false; + } + + return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier)); + } + + public function evictEntity(string $className, mixed $identifier): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier)); + } + + public function evictEntityRegion(string $className): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evictAll(); + } + + public function evictEntityRegions(): void + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + continue; + } + + $persister->getCacheRegion()->evictAll(); + } + } + + public function containsCollection(string $className, string $association, mixed $ownerIdentifier): bool + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return false; + } + + return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier)); + } + + public function evictCollection(string $className, string $association, mixed $ownerIdentifier): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier)); + } + + public function evictCollectionRegion(string $className, string $association): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evictAll(); + } + + public function evictCollectionRegions(): void + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + foreach ($metadata->associationMappings as $association) { + if (! $association->isToMany()) { + continue; + } + + $persister = $this->uow->getCollectionPersister($association); + + if (! ($persister instanceof CachedPersister)) { + continue; + } + + $persister->getCacheRegion()->evictAll(); + } + } + } + + public function containsQuery(string $regionName): bool + { + return isset($this->queryCaches[$regionName]); + } + + public function evictQueryRegion(string|null $regionName = null): void + { + if ($regionName === null && $this->defaultQueryCache !== null) { + $this->defaultQueryCache->clear(); + + return; + } + + if (isset($this->queryCaches[$regionName])) { + $this->queryCaches[$regionName]->clear(); + } + } + + public function evictQueryRegions(): void + { + $this->getQueryCache()->clear(); + + foreach ($this->queryCaches as $queryCache) { + $queryCache->clear(); + } + } + + public function getQueryCache(string|null $regionName = null): QueryCache + { + if ($regionName === null) { + return $this->defaultQueryCache ??= $this->cacheFactory->buildQueryCache($this->em); + } + + return $this->queryCaches[$regionName] ??= $this->cacheFactory->buildQueryCache($this->em, $regionName); + } + + private function buildEntityCacheKey(ClassMetadata $metadata, mixed $identifier): EntityCacheKey + { + if (! is_array($identifier)) { + $identifier = $this->toIdentifierArray($metadata, $identifier); + } + + return new EntityCacheKey($metadata->rootEntityName, $identifier); + } + + private function buildCollectionCacheKey( + ClassMetadata $metadata, + string $association, + mixed $ownerIdentifier, + ): CollectionCacheKey { + if (! is_array($ownerIdentifier)) { + $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier); + } + + return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); + } + + /** @return array */ + private function toIdentifierArray(ClassMetadata $metadata, mixed $identifier): array + { + if (is_object($identifier)) { + $class = DefaultProxyClassNameResolver::getClass($identifier); + if ($this->em->getMetadataFactory()->hasMetadataFor($class)) { + $identifier = $this->uow->getSingleIdentifierValue($identifier) + ?? throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class); + } + } + + return [$metadata->identifier[0] => $identifier]; + } +} 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 @@ +fileLockRegionDirectory = $fileLockRegionDirectory; + } + + public function getFileLockRegionDirectory(): string|null + { + return $this->fileLockRegionDirectory; + } + + public function setRegion(Region $region): void + { + $this->regions[$region->getName()] = $region; + } + + public function setTimestampRegion(TimestampRegion $region): void + { + $this->timestampRegion = $region; + } + + public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata): CachedEntityPersister + { + assert($metadata->cache !== null); + $region = $this->getRegion($metadata->cache); + $usage = $metadata->cache['usage']; + + if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { + return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata); + } + + if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata); + } + + if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { + if (! $region instanceof ConcurrentRegion) { + throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage)); + } + + return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata); + } + + throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage)); + } + + public function buildCachedCollectionPersister( + EntityManagerInterface $em, + CollectionPersister $persister, + AssociationMapping $mapping, + ): CachedCollectionPersister { + assert(isset($mapping->cache)); + $usage = $mapping->cache['usage']; + $region = $this->getRegion($mapping->cache); + + if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { + return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping); + } + + if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); + } + + if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { + if (! $region instanceof ConcurrentRegion) { + throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage)); + } + + return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); + } + + throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage)); + } + + public function buildQueryCache(EntityManagerInterface $em, string|null $regionName = null): QueryCache + { + return new DefaultQueryCache( + $em, + $this->getRegion( + [ + 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME, + 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE, + ], + ), + ); + } + + public function buildCollectionHydrator(EntityManagerInterface $em, AssociationMapping $mapping): CollectionHydrator + { + return new DefaultCollectionHydrator($em); + } + + public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata): EntityHydrator + { + return new DefaultEntityHydrator($em); + } + + /** + * {@inheritDoc} + */ + public function getRegion(array $cache): Region + { + if (isset($this->regions[$cache['region']])) { + return $this->regions[$cache['region']]; + } + + $name = $cache['region']; + $lifetime = $this->regionsConfig->getLifetime($cache['region']); + $region = new DefaultRegion($name, $this->cacheItemPool, $lifetime); + + if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) { + if ( + $this->fileLockRegionDirectory === '' || + $this->fileLockRegionDirectory === null + ) { + throw new LogicException( + 'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' . + '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(). ', + ); + } + + $directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region']; + $region = new FileLockRegion($region, $directory, (string) $this->regionsConfig->getLockLifetime($cache['region'])); + } + + return $this->regions[$cache['region']] = $region; + } + + public function getTimestampRegion(): TimestampRegion + { + if ($this->timestampRegion === null) { + $name = Cache::DEFAULT_TIMESTAMP_REGION_NAME; + $lifetime = $this->regionsConfig->getLifetime($name); + + $this->timestampRegion = new UpdateTimestampCache($name, $this->cacheItemPool, $lifetime); + } + + return $this->timestampRegion; + } + + public function createCache(EntityManagerInterface $entityManager): Cache + { + return new DefaultCache($entityManager); + } +} 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 @@ + */ + private static array $hints = [Query::HINT_CACHE_ENABLED => true]; + + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->uow = $em->getUnitOfWork(); + } + + public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array|Collection $collection): CollectionCacheEntry + { + $data = []; + + foreach ($collection as $index => $entity) { + $data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity)); + } + + return new CollectionCacheEntry($data); + } + + public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection): array|null + { + $assoc = $metadata->associationMappings[$key->association]; + $targetPersister = $this->uow->getEntityPersister($assoc->targetEntity); + assert($targetPersister instanceof CachedPersister); + $targetRegion = $targetPersister->getCacheRegion(); + $list = []; + + /** @var EntityCacheEntry[]|null $entityEntries */ + $entityEntries = $targetRegion->getMultiple($entry); + + if ($entityEntries === null) { + return null; + } + + foreach ($entityEntries as $index => $entityEntry) { + $entity = $this->uow->createEntity( + $entityEntry->class, + $entityEntry->resolveAssociationEntries($this->em), + self::$hints, + ); + + $collection->hydrateSet($index, $entity); + + $list[$index] = $entity; + } + + $this->uow->hydrationComplete(); + + return $list; + } +} 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 @@ + */ + private static array $hints = [Query::HINT_CACHE_ENABLED => true]; + + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->uow = $em->getUnitOfWork(); + $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); + } + + public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, object $entity): EntityCacheEntry + { + $data = $this->uow->getOriginalEntityData($entity); + $data = [...$data, ...$metadata->getIdentifierValues($entity)]; // why update has no identifier values ? + + if ($metadata->requiresFetchAfterChange) { + if ($metadata->isVersioned) { + assert($metadata->versionField !== null); + $data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField); + } + + foreach ($metadata->fieldMappings as $name => $fieldMapping) { + if (isset($fieldMapping->generated)) { + $data[$name] = $metadata->getFieldValue($entity, $name); + } + } + } + + foreach ($metadata->associationMappings as $name => $assoc) { + if (! isset($data[$name])) { + continue; + } + + if (! $assoc->isToOne()) { + unset($data[$name]); + + continue; + } + + if (! isset($assoc->cache)) { + $targetClassMetadata = $this->em->getClassMetadata($assoc->targetEntity); + $owningAssociation = $this->em->getMetadataFactory()->getOwningSide($assoc); + $associationIds = $this->identifierFlattener->flattenIdentifier( + $targetClassMetadata, + $targetClassMetadata->getIdentifierValues($data[$name]), + ); + + unset($data[$name]); + + foreach ($associationIds as $fieldName => $fieldValue) { + if (isset($targetClassMetadata->fieldMappings[$fieldName])) { + assert($owningAssociation->isToOneOwningSide()); + $fieldMapping = $targetClassMetadata->fieldMappings[$fieldName]; + + $data[$owningAssociation->targetToSourceKeyColumns[$fieldMapping->columnName]] = $fieldValue; + + continue; + } + + $targetAssoc = $targetClassMetadata->associationMappings[$fieldName]; + + assert($assoc->isToOneOwningSide()); + foreach ($assoc->targetToSourceKeyColumns as $referencedColumn => $localColumn) { + if (isset($targetAssoc->sourceToTargetKeyColumns[$referencedColumn])) { + $data[$localColumn] = $fieldValue; + } + } + } + + continue; + } + + if (! isset($assoc->id)) { + $targetClass = DefaultProxyClassNameResolver::getClass($data[$name]); + $targetId = $this->uow->getEntityIdentifier($data[$name]); + $data[$name] = new AssociationCacheEntry($targetClass, $targetId); + + continue; + } + + // handle association identifier + $targetId = is_object($data[$name]) && $this->uow->isInIdentityMap($data[$name]) + ? $this->uow->getEntityIdentifier($data[$name]) + : $data[$name]; + + // @TODO - fix it ! + // handle UnitOfWork#createEntity hash generation + if (! is_array($targetId)) { + assert($assoc->isToOneOwningSide()); + $data[reset($assoc->joinColumnFieldNames)] = $targetId; + + $targetEntity = $this->em->getClassMetadata($assoc->targetEntity); + $targetId = [$targetEntity->identifier[0] => $targetId]; + } + + $data[$name] = new AssociationCacheEntry($assoc->targetEntity, $targetId); + } + + return new EntityCacheEntry($metadata->name, $data); + } + + public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, object|null $entity = null): object|null + { + $data = $entry->data; + $hints = self::$hints; + + if ($entity !== null) { + $hints[Query::HINT_REFRESH] = true; + $hints[Query::HINT_REFRESH_ENTITY] = $entity; + } + + foreach ($metadata->associationMappings as $name => $assoc) { + if (! isset($assoc->cache) || ! isset($data[$name])) { + continue; + } + + $assocClass = $data[$name]->class; + $assocId = $data[$name]->identifier; + $isEagerLoad = ($assoc->fetch === ClassMetadata::FETCH_EAGER || ($assoc->isOneToOne() && ! $assoc->isOwningSide())); + + if (! $isEagerLoad) { + $data[$name] = $this->em->getReference($assocClass, $assocId); + + continue; + } + + $assocMetadata = $this->em->getClassMetadata($assoc->targetEntity); + $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId); + $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity); + $assocRegion = $assocPersister->getCacheRegion(); + $assocEntry = $assocRegion->get($assocKey); + + if ($assocEntry === null) { + return null; + } + + $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), $hints); + } + + if ($entity !== null) { + $this->uow->registerManaged($entity, $key->identifier, $data); + } + + $result = $this->uow->createEntity($entry->class, $data, $hints); + + $this->uow->hydrationComplete(); + + return $result; + } +} 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 @@ + */ + private static array $hints = [Query::HINT_CACHE_ENABLED => true]; + + public function __construct( + private readonly EntityManagerInterface $em, + private readonly Region $region, + ) { + $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration(); + + $this->uow = $em->getUnitOfWork(); + $this->cacheLogger = $cacheConfig->getCacheLogger(); + $this->validator = $cacheConfig->getQueryValidator(); + } + + /** + * {@inheritDoc} + */ + public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []): array|null + { + if (! ($key->cacheMode & Cache::MODE_GET)) { + return null; + } + + $cacheEntry = $this->region->get($key); + + if (! $cacheEntry instanceof QueryCacheEntry) { + return null; + } + + if (! $this->validator->isValid($key, $cacheEntry)) { + $this->region->evict($key); + + return null; + } + + $result = []; + $entityName = reset($rsm->aliasMap); + $hasRelation = ! empty($rsm->relationMap); + $persister = $this->uow->getEntityPersister($entityName); + assert($persister instanceof CachedEntityPersister); + + $region = $persister->getCacheRegion(); + $regionName = $region->getName(); + + $cm = $this->em->getClassMetadata($entityName); + + $generateKeys = static fn (array $entry): EntityCacheKey => new EntityCacheKey($cm->rootEntityName, $entry['identifier']); + + $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result)); + $entries = $region->getMultiple($cacheKeys) ?? []; + + // @TODO - move to cache hydration component + foreach ($cacheEntry->result as $index => $entry) { + $entityEntry = $entries[$index] ?? null; + + if (! $entityEntry instanceof EntityCacheEntry) { + $this->cacheLogger?->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]); + + return null; + } + + $this->cacheLogger?->entityCacheHit($regionName, $cacheKeys->identifiers[$index]); + + if (! $hasRelation) { + $result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints); + + continue; + } + + $data = $entityEntry->data; + + foreach ($entry['associations'] as $name => $assoc) { + $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); + assert($assocPersister instanceof CachedEntityPersister); + + $assocRegion = $assocPersister->getCacheRegion(); + $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); + + if ($assoc['type'] & ClassMetadata::TO_ONE) { + $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']); + $assocEntry = $assocRegion->get($assocKey); + + if ($assocEntry === null) { + $this->cacheLogger?->entityCacheMiss($assocRegion->getName(), $assocKey); + + $this->uow->hydrationComplete(); + + return null; + } + + $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); + + $this->cacheLogger?->entityCacheHit($assocRegion->getName(), $assocKey); + + continue; + } + + if (! isset($assoc['list']) || empty($assoc['list'])) { + continue; + } + + $generateKeys = static fn (array $id): EntityCacheKey => new EntityCacheKey($assocMetadata->rootEntityName, $id); + + $collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection()); + $assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list'])); + $assocEntries = $assocRegion->getMultiple($assocKeys); + + foreach ($assoc['list'] as $assocIndex => $assocId) { + $assocEntry = is_array($assocEntries) ? ($assocEntries[$assocIndex] ?? null) : null; + + if ($assocEntry === null) { + $this->cacheLogger?->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); + + $this->uow->hydrationComplete(); + + return null; + } + + $element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); + + $collection->hydrateSet($assocIndex, $element); + + $this->cacheLogger?->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); + } + + $data[$name] = $collection; + + $collection->setInitialized(true); + } + + foreach ($data as $fieldName => $unCachedAssociationData) { + // In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the + // cache key information in `$cacheEntry` will not contain details + // for fields that are associations. + // + // This means that `$data` keys for some associations that may + // actually not be cached will not be converted to actual association + // data, yet they contain L2 cache AssociationCacheEntry objects. + // + // We need to unwrap those associations into proxy references, + // since we don't have actual data for them except for identifiers. + if ($unCachedAssociationData instanceof AssociationCacheEntry) { + $data[$fieldName] = $this->em->getReference( + $unCachedAssociationData->class, + $unCachedAssociationData->identifier, + ); + } + } + + $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints); + } + + $this->uow->hydrationComplete(); + + return $result; + } + + /** + * {@inheritDoc} + */ + public function put(QueryCacheKey $key, ResultSetMapping $rsm, mixed $result, array $hints = []): bool + { + if ($rsm->scalarMappings) { + throw FeatureNotImplemented::scalarResults(); + } + + if (count($rsm->entityMappings) > 1) { + throw FeatureNotImplemented::multipleRootEntities(); + } + + if (! $rsm->isSelect) { + throw FeatureNotImplemented::nonSelectStatements(); + } + + if (! ($key->cacheMode & Cache::MODE_PUT)) { + return false; + } + + $data = []; + $entityName = reset($rsm->aliasMap); + $rootAlias = key($rsm->aliasMap); + $persister = $this->uow->getEntityPersister($entityName); + + if (! $persister instanceof CachedEntityPersister) { + throw NonCacheableEntity::fromEntity($entityName); + } + + $region = $persister->getCacheRegion(); + + $cm = $this->em->getClassMetadata($entityName); + assert($cm instanceof ClassMetadata); + + foreach ($result as $index => $entity) { + $identifier = $this->uow->getEntityIdentifier($entity); + $entityKey = new EntityCacheKey($cm->rootEntityName, $identifier); + + if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) { + // Cancel put result if entity put fail + if (! $persister->storeEntityCache($entity, $entityKey)) { + return false; + } + } + + $data[$index]['identifier'] = $identifier; + $data[$index]['associations'] = []; + + // @TODO - move to cache hydration components + foreach ($rsm->relationMap as $alias => $name) { + $parentAlias = $rsm->parentAliasMap[$alias]; + $parentClass = $rsm->aliasMap[$parentAlias]; + $metadata = $this->em->getClassMetadata($parentClass); + $assoc = $metadata->associationMappings[$name]; + $assocValue = $this->getAssociationValue($rsm, $alias, $entity); + + if ($assocValue === null) { + continue; + } + + // root entity association + if ($rootAlias === $parentAlias) { + // Cancel put result if association put fail + $assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue); + if ($assocInfo === null) { + return false; + } + + $data[$index]['associations'][$name] = $assocInfo; + + continue; + } + + // store single nested association + if (! is_array($assocValue)) { + // Cancel put result if association put fail + if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) { + return false; + } + + continue; + } + + // store array of nested association + foreach ($assocValue as $aVal) { + // Cancel put result if association put fail + if ($this->storeAssociationCache($key, $assoc, $aVal) === null) { + return false; + } + } + } + } + + return $this->region->put($key, new QueryCacheEntry($data)); + } + + /** + * @return mixed[]|null + * @psalm-return array{targetEntity: class-string, type: mixed, list?: array[], identifier?: array}|null + */ + private function storeAssociationCache(QueryCacheKey $key, AssociationMapping $assoc, mixed $assocValue): array|null + { + $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity); + $assocMetadata = $assocPersister->getClassMetadata(); + $assocRegion = $assocPersister->getCacheRegion(); + + // Handle *-to-one associations + if ($assoc->isToOne()) { + $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); + $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); + + if (! $this->uow->isUninitializedObject($assocValue) && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { + // Entity put fail + if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) { + return null; + } + } + + return [ + 'targetEntity' => $assocMetadata->rootEntityName, + 'identifier' => $assocIdentifier, + 'type' => $assoc->type(), + ]; + } + + // Handle *-to-many associations + $list = []; + + foreach ($assocValue as $assocItemIndex => $assocItem) { + $assocIdentifier = $this->uow->getEntityIdentifier($assocItem); + $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); + + if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { + // Entity put fail + if (! $assocPersister->storeEntityCache($assocItem, $entityKey)) { + return null; + } + } + + $list[$assocItemIndex] = $assocIdentifier; + } + + return [ + 'targetEntity' => $assocMetadata->rootEntityName, + 'type' => $assoc->type(), + 'list' => $list, + ]; + } + + /** @psalm-return list|object|null */ + private function getAssociationValue( + ResultSetMapping $rsm, + string $assocAlias, + object $entity, + ): array|object|null { + $path = []; + $alias = $assocAlias; + + while (isset($rsm->parentAliasMap[$alias])) { + $parent = $rsm->parentAliasMap[$alias]; + $field = $rsm->relationMap[$alias]; + $class = $rsm->aliasMap[$parent]; + + array_unshift($path, [ + 'field' => $field, + 'class' => $class, + ]); + + $alias = $parent; + } + + return $this->getAssociationPathValue($entity, $path); + } + + /** + * @psalm-param array $path + * + * @psalm-return list|object|null + */ + private function getAssociationPathValue(mixed $value, array $path): array|object|null + { + $mapping = array_shift($path); + $metadata = $this->em->getClassMetadata($mapping['class']); + $assoc = $metadata->associationMappings[$mapping['field']]; + $value = $metadata->getFieldValue($value, $mapping['field']); + + if ($value === null) { + return null; + } + + if ($path === []) { + return $value; + } + + // Handle *-to-one associations + if ($assoc->isToOne()) { + return $this->getAssociationPathValue($value, $path); + } + + $values = []; + + foreach ($value as $item) { + $values[] = $this->getAssociationPathValue($item, $path); + } + + return $values; + } + + public function clear(): bool + { + return $this->region->evictAll(); + } + + public function getRegion(): Region + { + return $this->region; + } +} 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 @@ + $data The entity map data + * @psalm-param class-string $class The entity class name + */ + public function __construct( + public readonly string $class, + public readonly array $data, + ) { + } + + /** + * Creates a new EntityCacheEntry + * + * This method allows Doctrine\Common\Cache\PhpFileCache compatibility + * + * @param array $values array containing property values + */ + public static function __set_state(array $values): self + { + return new self($values['class'], $values['data']); + } + + /** + * Retrieves the entity data resolving cache entries + * + * @return array + */ + public function resolveAssociationEntries(EntityManagerInterface $em): array + { + return array_map(static function ($value) use ($em) { + if (! ($value instanceof AssociationCacheEntry)) { + return $value; + } + + return $em->getReference($value->class, $value->identifier); + }, $this->data); + } +} 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 @@ + + */ + public readonly array $identifier; + + /** + * @param class-string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class. + * @param array $identifier The entity identifier + */ + public function __construct( + public readonly string $entityClass, + array $identifier, + ) { + ksort($identifier); + + $this->identifier = $identifier; + + parent::__construct(str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier))); + } +} 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 @@ +time = $time ?? time(); + } + + public static function createLockRead(): Lock + { + return new self(uniqid((string) time(), true)); + } +} 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 @@ + */ + private array $loggers = []; + + public function setLogger(string $name, CacheLogger $logger): void + { + $this->loggers[$name] = $logger; + } + + public function getLogger(string $name): CacheLogger|null + { + return $this->loggers[$name] ?? null; + } + + /** @return array */ + public function getLoggers(): array + { + return $this->loggers; + } + + public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->collectionCacheHit($regionName, $key); + } + } + + public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->collectionCacheMiss($regionName, $key); + } + } + + public function collectionCachePut(string $regionName, CollectionCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->collectionCachePut($regionName, $key); + } + } + + public function entityCacheHit(string $regionName, EntityCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->entityCacheHit($regionName, $key); + } + } + + public function entityCacheMiss(string $regionName, EntityCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->entityCacheMiss($regionName, $key); + } + } + + public function entityCachePut(string $regionName, EntityCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->entityCachePut($regionName, $key); + } + } + + public function queryCacheHit(string $regionName, QueryCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->queryCacheHit($regionName, $key); + } + } + + public function queryCacheMiss(string $regionName, QueryCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->queryCacheMiss($regionName, $key); + } + } + + public function queryCachePut(string $regionName, QueryCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->queryCachePut($regionName, $key); + } + } +} 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 @@ + */ + private array $cacheMissCountMap = []; + + /** @var array */ + private array $cacheHitCountMap = []; + + /** @var array */ + private array $cachePutCountMap = []; + + public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void + { + $this->cacheMissCountMap[$regionName] + = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; + } + + public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void + { + $this->cacheHitCountMap[$regionName] + = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; + } + + public function collectionCachePut(string $regionName, CollectionCacheKey $key): void + { + $this->cachePutCountMap[$regionName] + = ($this->cachePutCountMap[$regionName] ?? 0) + 1; + } + + public function entityCacheMiss(string $regionName, EntityCacheKey $key): void + { + $this->cacheMissCountMap[$regionName] + = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; + } + + public function entityCacheHit(string $regionName, EntityCacheKey $key): void + { + $this->cacheHitCountMap[$regionName] + = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; + } + + public function entityCachePut(string $regionName, EntityCacheKey $key): void + { + $this->cachePutCountMap[$regionName] + = ($this->cachePutCountMap[$regionName] ?? 0) + 1; + } + + public function queryCacheHit(string $regionName, QueryCacheKey $key): void + { + $this->cacheHitCountMap[$regionName] + = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; + } + + public function queryCacheMiss(string $regionName, QueryCacheKey $key): void + { + $this->cacheMissCountMap[$regionName] + = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; + } + + public function queryCachePut(string $regionName, QueryCacheKey $key): void + { + $this->cachePutCountMap[$regionName] + = ($this->cachePutCountMap[$regionName] ?? 0) + 1; + } + + /** + * Get the number of entries successfully retrieved from cache. + * + * @param string $regionName The name of the cache region. + */ + public function getRegionHitCount(string $regionName): int + { + return $this->cacheHitCountMap[$regionName] ?? 0; + } + + /** + * Get the number of cached entries *not* found in cache. + * + * @param string $regionName The name of the cache region. + */ + public function getRegionMissCount(string $regionName): int + { + return $this->cacheMissCountMap[$regionName] ?? 0; + } + + /** + * Get the number of cacheable entries put in cache. + * + * @param string $regionName The name of the cache region. + */ + public function getRegionPutCount(string $regionName): int + { + return $this->cachePutCountMap[$regionName] ?? 0; + } + + /** @return array */ + public function getRegionsMiss(): array + { + return $this->cacheMissCountMap; + } + + /** @return array */ + public function getRegionsHit(): array + { + return $this->cacheHitCountMap; + } + + /** @return array */ + public function getRegionsPut(): array + { + return $this->cachePutCountMap; + } + + /** + * Clear region statistics + * + * @param string $regionName The name of the cache region. + */ + public function clearRegionStats(string $regionName): void + { + $this->cachePutCountMap[$regionName] = 0; + $this->cacheHitCountMap[$regionName] = 0; + $this->cacheMissCountMap[$regionName] = 0; + } + + /** + * Clear all statistics + */ + public function clearStats(): void + { + $this->cachePutCountMap = []; + $this->cacheHitCountMap = []; + $this->cacheMissCountMap = []; + } + + /** + * Get the total number of put in cache. + */ + public function getPutCount(): int + { + return array_sum($this->cachePutCountMap); + } + + /** + * Get the total number of entries successfully retrieved from cache. + */ + public function getHitCount(): int + { + return array_sum($this->cacheHitCountMap); + } + + /** + * Get the total number of cached entries *not* found in cache. + */ + public function getMissCount(): int + { + return array_sum($this->cacheMissCountMap); + } +} 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 @@ +getConfiguration(); + $cacheConfig = $configuration->getSecondLevelCacheConfiguration(); + $cacheFactory = $cacheConfig->getCacheFactory(); + + $this->regionName = $region->getName(); + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->cacheLogger = $cacheConfig->getCacheLogger(); + $this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association); + $this->sourceEntity = $em->getClassMetadata($association->sourceEntity); + $this->targetEntity = $em->getClassMetadata($association->targetEntity); + } + + public function getCacheRegion(): Region + { + return $this->region; + } + + public function getSourceEntityMetadata(): ClassMetadata + { + return $this->sourceEntity; + } + + public function getTargetEntityMetadata(): ClassMetadata + { + return $this->targetEntity; + } + + public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key): array|null + { + $cache = $this->region->get($key); + + if ($cache === null) { + return null; + } + + return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection); + } + + public function storeCollectionCache(CollectionCacheKey $key, Collection|array $elements): void + { + $associationMapping = $this->sourceEntity->associationMappings[$key->association]; + $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); + assert($targetPersister instanceof CachedEntityPersister); + $targetRegion = $targetPersister->getCacheRegion(); + $targetHydrator = $targetPersister->getEntityHydrator(); + + // Only preserve ordering if association configured it + if (! $associationMapping->isIndexed()) { + // Elements may be an array or a Collection + $elements = array_values($elements instanceof Collection ? $elements->getValues() : $elements); + } + + $entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements); + + foreach ($entry->identifiers as $index => $entityKey) { + if ($targetRegion->contains($entityKey)) { + continue; + } + + $class = $this->targetEntity; + $className = DefaultProxyClassNameResolver::getClass($elements[$index]); + + if ($className !== $this->targetEntity->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $entity = $elements[$index]; + $entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity); + + $targetRegion->put($entityKey, $entityEntry); + } + + if ($this->region->put($key, $entry)) { + $this->cacheLogger?->collectionCachePut($this->regionName, $key); + } + } + + public function contains(PersistentCollection $collection, object $element): bool + { + return $this->persister->contains($collection, $element); + } + + public function containsKey(PersistentCollection $collection, mixed $key): bool + { + return $this->persister->containsKey($collection, $key); + } + + public function count(PersistentCollection $collection): int + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + $entry = $this->region->get($key); + + if ($entry !== null) { + return count($entry->identifiers); + } + + return $this->persister->count($collection); + } + + public function get(PersistentCollection $collection, mixed $index): mixed + { + return $this->persister->get($collection, $index); + } + + /** + * {@inheritDoc} + */ + public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array + { + return $this->persister->slice($collection, $offset, $length); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array + { + return $this->persister->loadCriteria($collection, $criteria); + } +} 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 @@ +queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->storeCollectionCache($item['key'], $item['list']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $key) { + $this->region->evict($key); + } + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + $this->queuedCache = []; + } + + public function delete(PersistentCollection $collection): void + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + + $this->persister->delete($collection); + + $this->queuedCache['delete'][spl_object_id($collection)] = $key; + } + + public function update(PersistentCollection $collection): void + { + $isInitialized = $collection->isInitialized(); + $isDirty = $collection->isDirty(); + + if (! $isInitialized && ! $isDirty) { + return; + } + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + + // Invalidate non initialized collections OR ordered collection + if ($isDirty && ! $isInitialized || $this->association->isOrdered()) { + $this->persister->update($collection); + + $this->queuedCache['delete'][spl_object_id($collection)] = $key; + + return; + } + + $this->persister->update($collection); + + $this->queuedCache['update'][spl_object_id($collection)] = [ + 'key' => $key, + 'list' => $collection, + ]; + } +} 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 @@ +isDirty() && $collection->getSnapshot()) { + throw CannotUpdateReadOnlyCollection::fromEntityAndField( + DefaultProxyClassNameResolver::getClass($collection->getOwner()), + $this->association->fieldName, + ); + } + + parent::update($collection); + } +} 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 @@ +queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = []; + } + + public function delete(PersistentCollection $collection): void + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + $lock = $this->region->lock($key); + + $this->persister->delete($collection); + + if ($lock === null) { + return; + } + + $this->queuedCache['delete'][spl_object_id($collection)] = [ + 'key' => $key, + 'lock' => $lock, + ]; + } + + public function update(PersistentCollection $collection): void + { + $isInitialized = $collection->isInitialized(); + $isDirty = $collection->isDirty(); + + if (! $isInitialized && ! $isDirty) { + return; + } + + $this->persister->update($collection); + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + $lock = $this->region->lock($key); + + if ($lock === null) { + return; + } + + $this->queuedCache['update'][spl_object_id($collection)] = [ + 'key' => $key, + 'lock' => $lock, + ]; + } +} 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 @@ +|null + */ + protected array|null $joinedAssociations = null; + + public function __construct( + protected EntityPersister $persister, + protected Region $region, + EntityManagerInterface $em, + protected ClassMetadata $class, + ) { + $configuration = $em->getConfiguration(); + $cacheConfig = $configuration->getSecondLevelCacheConfiguration(); + $cacheFactory = $cacheConfig->getCacheFactory(); + + $this->cache = $em->getCache(); + $this->regionName = $region->getName(); + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->cacheLogger = $cacheConfig->getCacheLogger(); + $this->timestampRegion = $cacheFactory->getTimestampRegion(); + $this->hydrator = $cacheFactory->buildEntityHydrator($em, $class); + $this->timestampKey = new TimestampCacheKey($this->class->rootEntityName); + } + + public function addInsert(object $entity): void + { + $this->persister->addInsert($entity); + } + + /** + * {@inheritDoc} + */ + public function getInserts(): array + { + return $this->persister->getInserts(); + } + + public function getSelectSQL( + array|Criteria $criteria, + AssociationMapping|null $assoc = null, + LockMode|int|null $lockMode = null, + int|null $limit = null, + int|null $offset = null, + array|null $orderBy = null, + ): string { + return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy); + } + + public function getCountSQL(array|Criteria $criteria = []): string + { + return $this->persister->getCountSQL($criteria); + } + + public function getInsertSQL(): string + { + return $this->persister->getInsertSQL(); + } + + public function getResultSetMapping(): ResultSetMapping + { + return $this->persister->getResultSetMapping(); + } + + public function getSelectConditionStatementSQL( + string $field, + mixed $value, + AssociationMapping|null $assoc = null, + string|null $comparison = null, + ): string { + return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison); + } + + public function exists(object $entity, Criteria|null $extraConditions = null): bool + { + if ($extraConditions === null) { + $key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity)); + + if ($this->region->contains($key)) { + return true; + } + } + + return $this->persister->exists($entity, $extraConditions); + } + + public function getCacheRegion(): Region + { + return $this->region; + } + + public function getEntityHydrator(): EntityHydrator + { + return $this->hydrator; + } + + public function storeEntityCache(object $entity, EntityCacheKey $key): bool + { + $class = $this->class; + $className = DefaultProxyClassNameResolver::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $entry = $this->hydrator->buildCacheEntry($class, $key, $entity); + $cached = $this->region->put($key, $entry); + + if ($cached) { + $this->cacheLogger?->entityCachePut($this->regionName, $key); + } + + return $cached; + } + + private function storeJoinedAssociations(object $entity): void + { + if ($this->joinedAssociations === null) { + $associations = []; + + foreach ($this->class->associationMappings as $name => $assoc) { + if ( + isset($assoc->cache) && + ($assoc->isToOne()) && + ($assoc->fetch === ClassMetadata::FETCH_EAGER || ! $assoc->isOwningSide()) + ) { + $associations[] = $name; + } + } + + $this->joinedAssociations = $associations; + } + + foreach ($this->joinedAssociations as $name) { + $assoc = $this->class->associationMappings[$name]; + $assocEntity = $this->class->getFieldValue($entity, $name); + + if ($assocEntity === null) { + continue; + } + + $assocId = $this->uow->getEntityIdentifier($assocEntity); + $assocMetadata = $this->metadataFactory->getMetadataFor($assoc->targetEntity); + $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId); + $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity); + + $assocPersister->storeEntityCache($assocEntity, $assocKey); + } + } + + /** + * Generates a string of currently query + * + * @param string[]|Criteria $criteria + * @param array|null $orderBy + */ + protected function getHash( + string $query, + array|Criteria $criteria, + array|null $orderBy = null, + int|null $limit = null, + int|null $offset = null, + ): string { + [$params] = $criteria instanceof Criteria + ? $this->persister->expandCriteriaParameters($criteria) + : $this->persister->expandParameters($criteria); + + return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset); + } + + /** + * {@inheritDoc} + */ + public function expandParameters(array $criteria): array + { + return $this->persister->expandParameters($criteria); + } + + /** + * {@inheritDoc} + */ + public function expandCriteriaParameters(Criteria $criteria): array + { + return $this->persister->expandCriteriaParameters($criteria); + } + + public function getClassMetadata(): ClassMetadata + { + return $this->persister->getClassMetadata(); + } + + /** + * {@inheritDoc} + */ + public function getManyToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array { + return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit); + } + + /** + * {@inheritDoc} + */ + public function getOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array { + return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit); + } + + public function getOwningTable(string $fieldName): string + { + return $this->persister->getOwningTable($fieldName); + } + + public function executeInserts(): void + { + // The commit order/foreign key relationships may make it necessary that multiple calls to executeInsert() + // are performed, so collect all the new entities. + $newInserts = $this->persister->getInserts(); + + if ($newInserts) { + $this->queuedCache['insert'] = array_merge($this->queuedCache['insert'] ?? [], $newInserts); + } + + $this->persister->executeInserts(); + } + + /** + * {@inheritDoc} + */ + public function load( + array $criteria, + object|null $entity = null, + AssociationMapping|null $assoc = null, + array $hints = [], + LockMode|int|null $lockMode = null, + int|null $limit = null, + array|null $orderBy = null, + ): object|null { + if ($entity !== null || $assoc !== null || $hints !== [] || $lockMode !== null) { + return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + } + + //handle only EntityRepository#findOneBy + $query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy); + $hash = $this->getHash($query, $criteria); + $rsm = $this->getResultSetMapping(); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); + $queryCache = $this->cache->getQueryCache($this->regionName); + $result = $queryCache->get($queryKey, $rsm); + + if ($result !== null) { + $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey); + + return $result[0]; + } + + $result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + + if ($result === null) { + return null; + } + + $cached = $queryCache->put($queryKey, $rsm, [$result]); + + $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey); + + if ($cached) { + $this->cacheLogger?->queryCachePut($this->regionName, $queryKey); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function loadAll( + array $criteria = [], + array|null $orderBy = null, + int|null $limit = null, + int|null $offset = null, + ): array { + $query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); + $hash = $this->getHash($query, $criteria); + $rsm = $this->getResultSetMapping(); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); + $queryCache = $this->cache->getQueryCache($this->regionName); + $result = $queryCache->get($queryKey, $rsm); + + if ($result !== null) { + $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey); + + return $result; + } + + $result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset); + $cached = $queryCache->put($queryKey, $rsm, $result); + + if ($result) { + $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey); + } + + if ($cached) { + $this->cacheLogger?->queryCachePut($this->regionName, $queryKey); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function loadById(array $identifier, object|null $entity = null): object|null + { + $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); + $cacheEntry = $this->region->get($cacheKey); + $class = $this->class; + + if ($cacheEntry !== null) { + if ($cacheEntry->class !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($cacheEntry->class); + } + + $cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity); + + if ($cachedEntity !== null) { + $this->cacheLogger?->entityCacheHit($this->regionName, $cacheKey); + + return $cachedEntity; + } + } + + $entity = $this->persister->loadById($identifier, $entity); + + if ($entity === null) { + return null; + } + + $class = $this->class; + $className = DefaultProxyClassNameResolver::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity); + $cached = $this->region->put($cacheKey, $cacheEntry); + + if ($cached && ($this->joinedAssociations === null || $this->joinedAssociations)) { + $this->storeJoinedAssociations($entity); + } + + if ($cached) { + $this->cacheLogger?->entityCachePut($this->regionName, $cacheKey); + } + + $this->cacheLogger?->entityCacheMiss($this->regionName, $cacheKey); + + return $entity; + } + + public function count(array|Criteria $criteria = []): int + { + return $this->persister->count($criteria); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(Criteria $criteria): array + { + $orderBy = $criteria->orderings(); + $limit = $criteria->getMaxResults(); + $offset = $criteria->getFirstResult(); + $query = $this->persister->getSelectSQL($criteria); + $hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset); + $rsm = $this->getResultSetMapping(); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); + $queryCache = $this->cache->getQueryCache($this->regionName); + $cacheResult = $queryCache->get($queryKey, $rsm); + + if ($cacheResult !== null) { + $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey); + + return $cacheResult; + } + + $result = $this->persister->loadCriteria($criteria); + $cached = $queryCache->put($queryKey, $rsm, $result); + + if ($result) { + $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey); + } + + if ($cached) { + $this->cacheLogger?->queryCachePut($this->regionName, $queryKey); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function loadManyToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + PersistentCollection $collection, + ): array { + $persister = $this->uow->getCollectionPersister($assoc); + $hasCache = ($persister instanceof CachedPersister); + + if (! $hasCache) { + return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection); + } + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = $this->buildCollectionCacheKey($assoc, $ownerId); + $list = $persister->loadCollectionCache($collection, $key); + + if ($list !== null) { + $this->cacheLogger?->collectionCacheHit($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + $list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection); + + $persister->storeCollectionCache($key, $list); + + $this->cacheLogger?->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + public function loadOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + PersistentCollection $collection, + ): mixed { + $persister = $this->uow->getCollectionPersister($assoc); + $hasCache = ($persister instanceof CachedPersister); + + if (! $hasCache) { + return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection); + } + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = $this->buildCollectionCacheKey($assoc, $ownerId); + $list = $persister->loadCollectionCache($collection, $key); + + if ($list !== null) { + $this->cacheLogger?->collectionCacheHit($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + $list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection); + + $persister->storeCollectionCache($key, $list); + + $this->cacheLogger?->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + /** + * {@inheritDoc} + */ + public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null + { + return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier); + } + + /** + * {@inheritDoc} + */ + public function lock(array $criteria, LockMode|int $lockMode): void + { + $this->persister->lock($criteria, $lockMode); + } + + /** + * {@inheritDoc} + */ + public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void + { + $this->persister->refresh($id, $entity, $lockMode); + } + + /** @param array $ownerId */ + protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId): CollectionCacheKey + { + $metadata = $this->metadataFactory->getMetadataFor($association->sourceEntity); + assert($metadata instanceof ClassMetadata); + + return new CollectionCacheKey($metadata->rootEntityName, $association->fieldName, $ownerId); + } +} 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 @@ +queuedCache['insert'])) { + foreach ($this->queuedCache['insert'] as $entity) { + $isChanged = $this->updateCache($entity, $isChanged); + } + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $entity) { + $isChanged = $this->updateCache($entity, $isChanged); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $key) { + $this->region->evict($key); + + $isChanged = true; + } + } + + if ($isChanged) { + $this->timestampRegion->update($this->timestampKey); + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + $this->queuedCache = []; + } + + public function delete(object $entity): bool + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $deleted = $this->persister->delete($entity); + + if ($deleted) { + $this->region->evict($key); + } + + $this->queuedCache['delete'][] = $key; + + return $deleted; + } + + public function update(object $entity): void + { + $this->persister->update($entity); + + $this->queuedCache['update'][] = $entity; + } + + private function updateCache(object $entity, bool $isChanged): bool + { + $class = $this->metadataFactory->getMetadataFor($entity::class); + $key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $entry = $this->hydrator->buildCacheEntry($class, $key, $entity); + $cached = $this->region->put($key, $entry); + $isChanged = $isChanged || $cached; + + if ($cached) { + $this->cacheLogger?->entityCachePut($this->regionName, $key); + } + + return $isChanged; + } +} 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 @@ +queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + + $isChanged = true; + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + + $isChanged = true; + } + } + + if ($isChanged) { + $this->timestampRegion->update($this->timestampKey); + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = []; + } + + public function delete(object $entity): bool + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = $this->region->lock($key); + $deleted = $this->persister->delete($entity); + + if ($deleted) { + $this->region->evict($key); + } + + if ($lock === null) { + return $deleted; + } + + $this->queuedCache['delete'][] = [ + 'lock' => $lock, + 'key' => $key, + ]; + + return $deleted; + } + + public function update(object $entity): void + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = $this->region->lock($key); + + $this->persister->update($entity); + + if ($lock === null) { + return; + } + + $this->queuedCache['update'][] = [ + 'lock' => $lock, + 'key' => $key, + ]; + } +} 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 @@ + $result List of entity identifiers */ + public function __construct( + public readonly array $result, + float|null $time = null, + ) { + $this->time = $time ?: microtime(true); + } + + /** @param array $values */ + public static function __set_state(array $values): self + { + return new self($values['result'], $values['time']); + } +} 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 @@ +name; + } + + public function contains(CacheKey $key): bool + { + return $this->cacheItemPool->hasItem($this->getCacheEntryKey($key)); + } + + public function get(CacheKey $key): CacheEntry|null + { + $item = $this->cacheItemPool->getItem($this->getCacheEntryKey($key)); + $entry = $item->isHit() ? $item->get() : null; + + if (! $entry instanceof CacheEntry) { + return null; + } + + return $entry; + } + + public function getMultiple(CollectionCacheEntry $collection): array|null + { + $keys = array_map( + $this->getCacheEntryKey(...), + $collection->identifiers, + ); + /** @var iterable $items */ + $items = $this->cacheItemPool->getItems($keys); + if ($items instanceof Traversable) { + $items = iterator_to_array($items); + } + + $result = []; + foreach ($keys as $arrayKey => $cacheKey) { + if (! isset($items[$cacheKey]) || ! $items[$cacheKey]->isHit()) { + return null; + } + + $entry = $items[$cacheKey]->get(); + if (! $entry instanceof CacheEntry) { + return null; + } + + $result[$arrayKey] = $entry; + } + + return $result; + } + + public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool + { + $item = $this->cacheItemPool + ->getItem($this->getCacheEntryKey($key)) + ->set($entry); + + if ($this->lifetime > 0) { + $item->expiresAfter($this->lifetime); + } + + return $this->cacheItemPool->save($item); + } + + public function evict(CacheKey $key): bool + { + return $this->cacheItemPool->deleteItem($this->getCacheEntryKey($key)); + } + + public function evictAll(): bool + { + return $this->cacheItemPool->clear(self::REGION_PREFIX . $this->name); + } + + private function getCacheEntryKey(CacheKey $key): string + { + return self::REGION_PREFIX . $this->name . self::REGION_KEY_SEPARATOR . strtr($key->hash, '{}()/\@:', '________'); + } +} 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 @@ +getLockFileName($key); + + if (! is_file($filename)) { + return false; + } + + $time = $this->getLockTime($filename); + $content = $this->getLockContent($filename); + + if ($content === false || $time === false) { + @unlink($filename); + + return false; + } + + if ($lock && $content === $lock->value) { + return false; + } + + // outdated lock + if ($time + $this->lockLifetime <= time()) { + @unlink($filename); + + return false; + } + + return true; + } + + private function getLockFileName(CacheKey $key): string + { + return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION; + } + + private function getLockContent(string $filename): string|false + { + return @file_get_contents($filename); + } + + private function getLockTime(string $filename): int|false + { + return @fileatime($filename); + } + + public function getName(): string + { + return $this->region->getName(); + } + + public function contains(CacheKey $key): bool + { + if ($this->isLocked($key)) { + return false; + } + + return $this->region->contains($key); + } + + public function get(CacheKey $key): CacheEntry|null + { + if ($this->isLocked($key)) { + return null; + } + + return $this->region->get($key); + } + + public function getMultiple(CollectionCacheEntry $collection): array|null + { + if (array_filter(array_map($this->isLocked(...), $collection->identifiers))) { + return null; + } + + return $this->region->getMultiple($collection); + } + + public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool + { + if ($this->isLocked($key, $lock)) { + return false; + } + + return $this->region->put($key, $entry); + } + + public function evict(CacheKey $key): bool + { + if ($this->isLocked($key)) { + @unlink($this->getLockFileName($key)); + } + + return $this->region->evict($key); + } + + public function evictAll(): bool + { + // The check below is necessary because on some platforms glob returns false + // when nothing matched (even though no errors occurred) + $filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)) ?: []; + + foreach ($filenames as $filename) { + @unlink($filename); + } + + return $this->region->evictAll(); + } + + public function lock(CacheKey $key): Lock|null + { + if ($this->isLocked($key)) { + return null; + } + + $lock = Lock::createLockRead(); + $filename = $this->getLockFileName($key); + + if (@file_put_contents($filename, $lock->value, LOCK_EX) === false) { + return null; + } + + chmod($filename, 0664); + + return $lock; + } + + public function unlock(CacheKey $key, Lock $lock): bool + { + if ($this->isLocked($key, $lock)) { + return false; + } + + return @unlink($this->getLockFileName($key)); + } +} 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 @@ +put($key, new TimestampCacheEntry()); + } +} 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 @@ + */ + private array $lifetimes = []; + + /** @var array */ + private array $lockLifetimes = []; + + public function __construct( + private int $defaultLifetime = 3600, + private int $defaultLockLifetime = 60, + ) { + } + + public function getDefaultLifetime(): int + { + return $this->defaultLifetime; + } + + public function setDefaultLifetime(int $defaultLifetime): void + { + $this->defaultLifetime = $defaultLifetime; + } + + public function getDefaultLockLifetime(): int + { + return $this->defaultLockLifetime; + } + + public function setDefaultLockLifetime(int $defaultLockLifetime): void + { + $this->defaultLockLifetime = $defaultLockLifetime; + } + + public function getLifetime(string $regionName): int + { + return $this->lifetimes[$regionName] ?? $this->defaultLifetime; + } + + public function setLifetime(string $name, int $lifetime): void + { + $this->lifetimes[$name] = $lifetime; + } + + public function getLockLifetime(string $regionName): int + { + return $this->lockLifetimes[$regionName] ?? $this->defaultLockLifetime; + } + + public function setLockLifetime(string $name, int $lifetime): void + { + $this->lockLifetimes[$name] = $lifetime; + } +} 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 @@ +time = $time ?? microtime(true); + } + + /** + * Creates a new TimestampCacheEntry + * + * This method allow Doctrine\Common\Cache\PhpFileCache compatibility + * + * @param array $values array containing property values + */ + public static function __set_state(array $values): TimestampCacheEntry + { + return new self($values['time']); + } +} 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 @@ +regionUpdated($key, $entry)) { + return false; + } + + if ($key->lifetime === 0) { + return true; + } + + return $entry->time + $key->lifetime > microtime(true); + } + + private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry): bool + { + if ($key->timestampKey === null) { + return false; + } + + $timestamp = $this->timestampRegion->get($key->timestampKey); + + return $timestamp && $timestamp->time > $entry->time; + } +} 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 @@ +