<?php/* * This file is part of the API Platform project. * * (c) Kévin Dunglas <dunglas@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */declare(strict_types=1);namespace ApiPlatform\Core\Api;use ApiPlatform\Core\Util\ResourceClassInfoTrait;use Psr\Cache\CacheException;use Psr\Cache\CacheItemPoolInterface;use Symfony\Component\PropertyAccess\PropertyAccess;use Symfony\Component\PropertyAccess\PropertyAccessorInterface;/** * {@inheritdoc} * * @author Antoine Bluchet <soyuka@gmail.com> */final class CachedIdentifiersExtractor implements IdentifiersExtractorInterface{ use ResourceClassInfoTrait; public const CACHE_KEY_PREFIX = 'iri_identifiers'; private $cacheItemPool; private $propertyAccessor; private $decorated; private $localCache = []; private $localResourceCache = []; public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null) { $this->cacheItemPool = $cacheItemPool; $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); $this->decorated = $decorated; $this->resourceClassResolver = $resourceClassResolver; if (null === $this->resourceClassResolver) { @trigger_error(sprintf('Not injecting %s in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), \E_USER_DEPRECATED); } } /** * {@inheritdoc} */ public function getIdentifiersFromResourceClass(string $resourceClass): array { if (isset($this->localResourceCache[$resourceClass])) { return $this->localResourceCache[$resourceClass]; } return $this->localResourceCache[$resourceClass] = $this->decorated->getIdentifiersFromResourceClass($resourceClass); } /** * {@inheritdoc} */ public function getIdentifiersFromItem($item): array { $keys = $this->getKeys($item, function ($item) use (&$identifiers) { return $identifiers = $this->decorated->getIdentifiersFromItem($item); }); if (null !== $identifiers) { return $identifiers; } $identifiers = []; foreach ($keys as $propertyName) { $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); if (!\is_object($identifiers[$propertyName])) { continue; } if (null === $relatedResourceClass = $this->getResourceClass($identifiers[$propertyName])) { continue; } if (!$relatedIdentifiers = $this->localCache[$relatedResourceClass] ?? false) { $relatedCacheKey = self::CACHE_KEY_PREFIX.md5($relatedResourceClass); try { $relatedCacheItem = $this->cacheItemPool->getItem($relatedCacheKey); if (!$relatedCacheItem->isHit()) { return $this->decorated->getIdentifiersFromItem($item); } } catch (CacheException $e) { return $this->decorated->getIdentifiersFromItem($item); } $relatedIdentifiers = $relatedCacheItem->get(); } $identifiers[$propertyName] = $this->propertyAccessor->getValue($identifiers[$propertyName], $relatedIdentifiers[0]); } return $identifiers; } private function getKeys($item, callable $retriever): array { $resourceClass = $this->getObjectClass($item); if (isset($this->localCache[$resourceClass])) { return $this->localCache[$resourceClass]; } try { $cacheItem = $this->cacheItemPool->getItem(self::CACHE_KEY_PREFIX.md5($resourceClass)); } catch (CacheException $e) { return $this->localCache[$resourceClass] = array_keys($retriever($item)); } if ($cacheItem->isHit()) { return $this->localCache[$resourceClass] = $cacheItem->get(); } $keys = array_keys($retriever($item)); $cacheItem->set($keys); $this->cacheItemPool->save($cacheItem); return $this->localCache[$resourceClass] = $keys; }}