welcome back to dyb-tech

This commit is contained in:
Daniel Guzman
2024-05-18 02:28:01 +02:00
parent 9513cdba09
commit 9f30bc98c7
6149 changed files with 668407 additions and 0 deletions
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister;
use Doctrine\ORM\Cache\Region;
/**
* Interface for persister that support second level cache.
*/
interface CachedPersister
{
/**
* Perform whatever processing is encapsulated here after completion of the transaction.
*
* @return void
*/
public function afterTransactionComplete();
/**
* Perform whatever processing is encapsulated here after completion of the rolled-back.
*
* @return void
*/
public function afterTransactionRolledBack();
/**
* Gets the The region access.
*
* @return Region
*/
public function getCacheRegion();
}
@@ -0,0 +1,282 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\CollectionHydrator;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\UnitOfWork;
use function array_values;
use function assert;
use function count;
/** @psalm-import-type AssociationMapping from ClassMetadata */
abstract class AbstractCollectionPersister implements CachedCollectionPersister
{
/** @var UnitOfWork */
protected $uow;
/** @var ClassMetadataFactory */
protected $metadataFactory;
/** @var CollectionPersister */
protected $persister;
/** @var ClassMetadata */
protected $sourceEntity;
/** @var ClassMetadata */
protected $targetEntity;
/** @var mixed[] */
protected $association;
/** @var mixed[] */
protected $queuedCache = [];
/** @var Region */
protected $region;
/** @var string */
protected $regionName;
/** @var CollectionHydrator */
protected $hydrator;
/** @var CacheLogger|null */
protected $cacheLogger;
/**
* @param CollectionPersister $persister The collection persister that will be cached.
* @param Region $region The collection region.
* @param EntityManagerInterface $em The entity manager.
* @param AssociationMapping $association The association mapping.
*/
public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association)
{
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->region = $region;
$this->persister = $persister;
$this->association = $association;
$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']);
}
/**
* {@inheritDoc}
*/
public function getCacheRegion()
{
return $this->region;
}
/**
* {@inheritDoc}
*/
public function getSourceEntityMetadata()
{
return $this->sourceEntity;
}
/**
* {@inheritDoc}
*/
public function getTargetEntityMetadata()
{
return $this->targetEntity;
}
/**
* {@inheritDoc}
*/
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key)
{
$cache = $this->region->get($key);
if ($cache === null) {
return null;
}
return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection);
}
/**
* {@inheritDoc}
*/
public function storeCollectionCache(CollectionCacheKey $key, $elements)
{
$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 (! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
// 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);
}
$cached = $this->region->put($key, $entry);
if ($this->cacheLogger && $cached) {
$this->cacheLogger->collectionCachePut($this->regionName, $key);
}
}
/**
* {@inheritDoc}
*/
public function contains(PersistentCollection $collection, $element)
{
return $this->persister->contains($collection, $element);
}
/**
* {@inheritDoc}
*/
public function containsKey(PersistentCollection $collection, $key)
{
return $this->persister->containsKey($collection, $key);
}
/**
* {@inheritDoc}
*/
public function count(PersistentCollection $collection)
{
$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);
}
/**
* {@inheritDoc}
*/
public function get(PersistentCollection $collection, $index)
{
return $this->persister->get($collection, $index);
}
/**
* {@inheritDoc}
*/
public function slice(PersistentCollection $collection, $offset, $length = null)
{
return $this->persister->slice($collection, $offset, $length);
}
/**
* {@inheritDoc}
*/
public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
{
return $this->persister->loadCriteria($collection, $criteria);
}
/**
* Clears cache entries related to the current collection
*
* @deprecated This method is not used anymore.
*
* @return void
*/
protected function evictCollectionCache(PersistentCollection $collection)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9512',
'The method %s() is deprecated and will be removed without replacement.'
);
$key = new CollectionCacheKey(
$this->sourceEntity->rootEntityName,
$this->association['fieldName'],
$this->uow->getEntityIdentifier($collection->getOwner())
);
$this->region->evict($key);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCachePut($this->regionName, $key);
}
}
/**
* @deprecated This method is not used anymore.
*
* @param string $targetEntity
* @param object $element
* @psalm-param class-string $targetEntity
*
* @return void
*/
protected function evictElementCache($targetEntity, $element)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9512',
'The method %s() is deprecated and will be removed without replacement.'
);
$targetPersister = $this->uow->getEntityPersister($targetEntity);
assert($targetPersister instanceof CachedEntityPersister);
$targetRegion = $targetPersister->getCacheRegion();
$key = new EntityCacheKey($targetEntity, $this->uow->getEntityIdentifier($element));
$targetRegion->evict($key);
if ($this->cacheLogger) {
$this->cacheLogger->entityCachePut($targetRegion->getName(), $key);
}
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
/**
* Interface for second level cache collection persisters.
*/
interface CachedCollectionPersister extends CachedPersister, CollectionPersister
{
/** @return ClassMetadata */
public function getSourceEntityMetadata();
/** @return ClassMetadata */
public function getTargetEntityMetadata();
/**
* Loads a collection from cache
*
* @return mixed[]|null
*/
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key);
/**
* Stores a collection into cache
*
* @param mixed[]|Collection $elements
*
* @return void
*/
public function storeCollectionCache(CollectionCacheKey $key, $elements);
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\PersistentCollection;
use function spl_object_id;
class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
if (isset($this->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 = [];
}
/**
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
$this->queuedCache = [];
}
/**
* {@inheritDoc}
*/
public function delete(PersistentCollection $collection)
{
$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;
}
/**
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{
$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 || isset($this->association['orderBy'])) {
$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,
];
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyCollection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
{
/**
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{
if ($collection->isDirty() && $collection->getSnapshot()) {
throw CannotUpdateReadOnlyCollection::fromEntityAndField(
DefaultProxyClassNameResolver::getClass($collection->getOwner()),
$this->association['fieldName']
);
}
parent::update($collection);
}
}
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use function spl_object_id;
/** @psalm-import-type AssociationMapping from ClassMetadata */
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/** @param AssociationMapping $association The association mapping. */
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
{
parent::__construct($persister, $region, $em, $association);
}
/**
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
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 = [];
}
/**
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
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 = [];
}
/**
* {@inheritDoc}
*/
public function delete(PersistentCollection $collection)
{
$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,
];
}
/**
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{
$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,
];
}
}
@@ -0,0 +1,619 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\EntityHydrator;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\UnitOfWork;
use function array_merge;
use function assert;
use function serialize;
use function sha1;
abstract class AbstractEntityPersister implements CachedEntityPersister
{
/** @var UnitOfWork */
protected $uow;
/** @var ClassMetadataFactory */
protected $metadataFactory;
/** @var EntityPersister */
protected $persister;
/** @var ClassMetadata */
protected $class;
/** @var mixed[] */
protected $queuedCache = [];
/** @var Region */
protected $region;
/** @var TimestampRegion */
protected $timestampRegion;
/** @var TimestampCacheKey */
protected $timestampKey;
/** @var EntityHydrator */
protected $hydrator;
/** @var Cache */
protected $cache;
/** @var CacheLogger|null */
protected $cacheLogger;
/** @var string */
protected $regionName;
/**
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
*
* @var array<string>|null
*/
protected $joinedAssociations;
/**
* @param EntityPersister $persister The entity persister to cache.
* @param Region $region The entity cache region.
* @param EntityManagerInterface $em The entity manager.
* @param ClassMetadata $class The entity metadata.
*/
public function __construct(EntityPersister $persister, Region $region, EntityManagerInterface $em, ClassMetadata $class)
{
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->class = $class;
$this->region = $region;
$this->persister = $persister;
$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);
}
/**
* {@inheritDoc}
*/
public function addInsert($entity)
{
$this->persister->addInsert($entity);
}
/**
* {@inheritDoc}
*/
public function getInserts()
{
return $this->persister->getInserts();
}
/**
* {@inheritDoc}
*/
public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, ?array $orderBy = null)
{
return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy);
}
/**
* {@inheritDoc}
*/
public function getCountSQL($criteria = [])
{
return $this->persister->getCountSQL($criteria);
}
/**
* {@inheritDoc}
*/
public function getInsertSQL()
{
return $this->persister->getInsertSQL();
}
/**
* {@inheritDoc}
*/
public function getResultSetMapping()
{
return $this->persister->getResultSetMapping();
}
/**
* {@inheritDoc}
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison);
}
/**
* {@inheritDoc}
*/
public function exists($entity, ?Criteria $extraConditions = null)
{
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);
}
/**
* {@inheritDoc}
*/
public function getCacheRegion()
{
return $this->region;
}
/** @return EntityHydrator */
public function getEntityHydrator()
{
return $this->hydrator;
}
/**
* {@inheritDoc}
*/
public function storeEntityCache($entity, EntityCacheKey $key)
{
$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 ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);
}
return $cached;
}
/** @param object $entity */
private function storeJoinedAssociations($entity): void
{
if ($this->joinedAssociations === null) {
$associations = [];
foreach ($this->class->associationMappings as $name => $assoc) {
if (
isset($assoc['cache']) &&
($assoc['type'] & ClassMetadata::TO_ONE) &&
($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 $query
* @param string[]|Criteria $criteria
* @param string[]|null $orderBy
* @param int|null $limit
* @param int|null $offset
*
* @return string
*/
protected function getHash($query, $criteria, ?array $orderBy = null, $limit = null, $offset = null)
{
[$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($criteria)
{
return $this->persister->expandParameters($criteria);
}
/**
* {@inheritDoc}
*/
public function expandCriteriaParameters(Criteria $criteria)
{
return $this->persister->expandCriteriaParameters($criteria);
}
/**
* {@inheritDoc}
*/
public function getClassMetadata()
{
return $this->persister->getClassMetadata();
}
/**
* {@inheritDoc}
*/
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit);
}
/**
* {@inheritDoc}
*/
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit);
}
/**
* {@inheritDoc}
*/
public function getOwningTable($fieldName)
{
return $this->persister->getOwningTable($fieldName);
}
/**
* {@inheritDoc}
*/
public function executeInserts()
{
// 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);
}
return $this->persister->executeInserts();
}
/**
* {@inheritDoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = 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, null, null, null);
$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) {
if ($this->cacheLogger) {
$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]);
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
}
}
return $result;
}
/**
* {@inheritDoc}
*/
public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null)
{
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null);
$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) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
}
return $result;
}
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
$cached = $queryCache->put($queryKey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
}
}
return $result;
}
/**
* {@inheritDoc}
*/
public function loadById(array $identifier, $entity = 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) {
if ($this->cacheLogger) {
$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 ($this->cacheLogger) {
if ($cached) {
$this->cacheLogger->entityCachePut($this->regionName, $cacheKey);
}
$this->cacheLogger->entityCacheMiss($this->regionName, $cacheKey);
}
return $entity;
}
/**
* {@inheritDoc}
*/
public function count($criteria = [])
{
return $this->persister->count($criteria);
}
/**
* {@inheritDoc}
*/
public function loadCriteria(Criteria $criteria)
{
$orderBy = $criteria->getOrderings();
$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) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
}
return $cacheResult;
}
$result = $this->persister->loadCriteria($criteria);
$cached = $queryCache->put($queryKey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
}
}
return $result;
}
/**
* {@inheritDoc}
*/
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection)
{
$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) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
return $list;
}
$list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection);
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
return $list;
}
/**
* {@inheritDoc}
*/
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection)
{
$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) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
return $list;
}
$list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection);
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
return $list;
}
/**
* {@inheritDoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
{
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
}
/**
* {@inheritDoc}
*/
public function lock(array $criteria, $lockMode)
{
$this->persister->lock($criteria, $lockMode);
}
/**
* {@inheritDoc}
*/
public function refresh(array $id, $entity, $lockMode = null)
{
$this->persister->refresh($id, $entity, $lockMode);
}
/**
* @param array<string, mixed> $association
* @param array<string, mixed> $ownerId
*
* @return CollectionCacheKey
*/
protected function buildCollectionCacheKey(array $association, $ownerId)
{
$metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']);
assert($metadata instanceof ClassMetadata);
return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\EntityHydrator;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* Interface for second level cache entity persisters.
*/
interface CachedEntityPersister extends CachedPersister, EntityPersister
{
/** @return EntityHydrator */
public function getEntityHydrator();
/**
* @param object $entity
*
* @return bool
*/
public function storeEntityCache($entity, EntityCacheKey $key);
}
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\EntityCacheKey;
use function get_class;
/**
* Specific non-strict read/write cached entity persister
*/
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
{
/**
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
$isChanged = false;
if (isset($this->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 = [];
}
/**
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
$this->queuedCache = [];
}
/**
* {@inheritDoc}
*/
public function delete($entity)
{
$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;
}
/**
* {@inheritDoc}
*/
public function update($entity)
{
$this->persister->update($entity);
$this->queuedCache['update'][] = $entity;
}
/** @param object $entity */
private function updateCache($entity, bool $isChanged): bool
{
$class = $this->metadataFactory->getMetadataFor(get_class($entity));
$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 ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);
}
return $isChanged;
}
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
/**
* Specific read-only region entity persister
*/
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
{
/**
* {@inheritDoc}
*/
public function update($entity)
{
throw CannotUpdateReadOnlyEntity::fromEntity(DefaultProxyClassNameResolver::getClass($entity));
}
}
@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* Specific read-write entity persister
*/
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
{
public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class)
{
parent::__construct($persister, $region, $em, $class);
}
/**
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
$isChanged = true;
if (isset($this->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 = [];
}
/**
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
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 = [];
}
/**
* {@inheritDoc}
*/
public function delete($entity)
{
$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;
}
/**
* {@inheritDoc}
*/
public function update($entity)
{
$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,
];
}
}