class ResourceObjectNormalizationCacher

Same name in other branches
  1. 9 core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php \Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher
  2. 8.9.x core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php \Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher
  3. 10 core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php \Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher

Caches entity normalizations after the response has been sent.

@internal

Hierarchy

Expanded class hierarchy of ResourceObjectNormalizationCacher

See also

\Drupal\jsonapi\Normalizer\ResourceObjectNormalizer::getNormalization()

2 files declare their use of ResourceObjectNormalizationCacher
ResourceObjectNormalizer.php in core/modules/jsonapi/src/Normalizer/ResourceObjectNormalizer.php
ResourceObjectNormalizerCacherTest.php in core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php
1 string reference to 'ResourceObjectNormalizationCacher'
jsonapi.services.yml in core/modules/jsonapi/jsonapi.services.yml
core/modules/jsonapi/jsonapi.services.yml
1 service uses ResourceObjectNormalizationCacher
jsonapi.normalization_cacher in core/modules/jsonapi/jsonapi.services.yml
Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher

File

core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php, line 20

Namespace

Drupal\jsonapi\EventSubscriber
View source
class ResourceObjectNormalizationCacher implements EventSubscriberInterface {
    
    /**
     * Key for the base subset.
     *
     * The base subset contains the parts of the normalization that are always
     * present. The presence or absence of these are not affected by the requested
     * sparse field sets. This typically includes the resource type name, and the
     * resource ID.
     */
    const RESOURCE_CACHE_SUBSET_BASE = 'base';
    
    /**
     * Key for the fields subset.
     *
     * The fields subset contains the parts of the normalization that can appear
     * in a normalization based on the selected field set. This subset is
     * incrementally built across different requests for the same resource object.
     * A given field is normalized and put into the cache whenever there is a
     * cache miss for that field.
     */
    const RESOURCE_CACHE_SUBSET_FIELDS = 'fields';
    
    /**
     * The variation cache.
     *
     * @var \Drupal\Core\Cache\VariationCacheInterface
     */
    protected $variationCache;
    
    /**
     * The request stack.
     *
     * @var \Symfony\Component\HttpFoundation\RequestStack
     */
    protected $requestStack;
    
    /**
     * The things to cache after the response has been sent.
     *
     * @var array
     */
    protected $toCache = [];
    
    /**
     * Sets the variation cache.
     *
     * @param \Drupal\Core\Cache\VariationCacheInterface $variation_cache
     *   The variation cache.
     */
    public function setVariationCache(VariationCacheInterface $variation_cache) {
        $this->variationCache = $variation_cache;
    }
    
    /**
     * Sets the request stack.
     *
     * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
     *   The request stack.
     */
    public function setRequestStack(RequestStack $request_stack) {
        $this->requestStack = $request_stack;
    }
    
    /**
     * Reads an entity normalization from cache.
     *
     * The returned normalization may only be a partial normalization because it
     * was previously normalized with a sparse fieldset.
     *
     * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object
     *   The resource object for which to generate a cache item.
     *
     * @return array|false
     *   The cached normalization parts, or FALSE if not yet cached.
     *
     * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::renderArrayToResponse()
     */
    public function get(ResourceObject $object) {
        // @todo Investigate whether to cache POST and PATCH requests.
        // @todo Follow up on https://www.drupal.org/project/drupal/issues/3381898.
        if (!$this->requestStack
            ->getCurrentRequest()
            ->isMethodCacheable()) {
            return FALSE;
        }
        $cached = $this->variationCache
            ->get($this->generateCacheKeys($object), new CacheableMetadata());
        if (!$cached) {
            return FALSE;
        }
        // When a cache hit occurs, we first calculate the remaining time before the
        // cached record expires, ensuring that we do not reset the expiration with
        // one that might have been generated on an earlier timestamp. This is done
        // by subtracting the current timestamp from the cached record's expiration
        // timestamp. If the max-age is set, we adjust it by merging the calculated
        // remaining time with the original max-age of the cached item, ensuring
        // that the expiration remains accurate based on the current cache state
        // and timestamp.
        $normalizer_values = $cached->data;
        assert(is_array($normalizer_values));
        if ($cached->expire >= 0) {
            $max_age = max($cached->expire - $this->requestStack
                ->getCurrentRequest()->server
                ->get('REQUEST_TIME'), 0);
            $cacheability = new CacheableMetadata();
            $cacheability->setCacheMaxAge($max_age);
            $subsets = [
                ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_BASE,
                ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_FIELDS,
            ];
            foreach ($subsets as $subset) {
                foreach ($normalizer_values[$subset] as $name => $normalization) {
                    assert($normalization instanceof CacheableNormalization);
                    if ($normalization->getCacheMaxAge() > 0) {
                        $normalizer_values[$subset][$name] = $normalization->withCacheableDependency($cacheability);
                    }
                }
            }
        }
        return $normalizer_values;
    }
    
    /**
     * Adds a normalization to be cached after the response has been sent.
     *
     * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object
     *   The resource object for which to generate a cache item.
     * @param array $normalization_parts
     *   The normalization parts to cache.
     */
    public function saveOnTerminate(ResourceObject $object, array $normalization_parts) {
        assert(array_keys($normalization_parts) === [
            static::RESOURCE_CACHE_SUBSET_BASE,
            static::RESOURCE_CACHE_SUBSET_FIELDS,
        ]);
        $resource_type = $object->getResourceType();
        $key = $resource_type->getTypeName() . ':' . $object->getId();
        $this->toCache[$key] = [
            $object,
            $normalization_parts,
        ];
    }
    
    /**
     * Writes normalizations of entities to cache, if any were created.
     *
     * @param \Symfony\Component\HttpKernel\Event\TerminateEvent $event
     *   The Event to process.
     */
    public function onTerminate(TerminateEvent $event) {
        foreach ($this->toCache as $value) {
            [
                $object,
                $normalization_parts,
            ] = $value;
            $this->set($object, $normalization_parts);
        }
    }
    
    /**
     * Writes a normalization to cache.
     *
     * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object
     *   The resource object for which to generate a cache item.
     * @param array $normalization_parts
     *   The normalization parts to cache.
     */
    protected function set(ResourceObject $object, array $normalization_parts) {
        // @todo Investigate whether to cache POST and PATCH requests.
        // @todo Follow up on https://www.drupal.org/project/drupal/issues/3381898.
        if (!$this->requestStack
            ->getCurrentRequest()
            ->isMethodCacheable()) {
            return;
        }
        // Merge the entity's cacheability metadata with that of the normalization
        // parts, so that VariationCache can take care of cache redirects for us.
        $cacheability = CacheableMetadata::createFromObject($object)->merge(static::mergeCacheableDependencies($normalization_parts[static::RESOURCE_CACHE_SUBSET_BASE]))
            ->merge(static::mergeCacheableDependencies($normalization_parts[static::RESOURCE_CACHE_SUBSET_FIELDS]));
        $this->variationCache
            ->set($this->generateCacheKeys($object), $normalization_parts, $cacheability, new CacheableMetadata());
    }
    
    /**
     * Generates the cache keys for a normalization.
     *
     * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object
     *   The resource object for which to generate the cache keys.
     *
     * @return string[]
     *   The cache keys to pass to the variation cache.
     *
     * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::$dynamicPageCacheRedirectRenderArray
     */
    protected static function generateCacheKeys(ResourceObject $object) {
        return [
            $object->getResourceType()
                ->getTypeName(),
            $object->getId(),
            $object->getLanguage()
                ->getId(),
        ];
    }
    
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() : array {
        $events[KernelEvents::TERMINATE][] = [
            'onTerminate',
        ];
        return $events;
    }
    
    /**
     * Determines the joint cacheability of all provided dependencies.
     *
     * @param \Drupal\Core\Cache\CacheableDependencyInterface|object[] $dependencies
     *   The dependencies.
     *
     * @return \Drupal\Core\Cache\CacheableMetadata
     *   The cacheability of all dependencies.
     *
     * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface::addCacheableDependency()
     */
    protected static function mergeCacheableDependencies(array $dependencies) {
        $merged_cacheability = new CacheableMetadata();
        array_walk($dependencies, function ($dependency) use ($merged_cacheability) {
            $merged_cacheability->addCacheableDependency($dependency);
        });
        return $merged_cacheability;
    }

}

Members

Title Sort descending Modifiers Object type Summary
ResourceObjectNormalizationCacher::$requestStack protected property The request stack.
ResourceObjectNormalizationCacher::$toCache protected property The things to cache after the response has been sent.
ResourceObjectNormalizationCacher::$variationCache protected property The variation cache.
ResourceObjectNormalizationCacher::generateCacheKeys protected static function Generates the cache keys for a normalization.
ResourceObjectNormalizationCacher::get public function Reads an entity normalization from cache.
ResourceObjectNormalizationCacher::getSubscribedEvents public static function
ResourceObjectNormalizationCacher::mergeCacheableDependencies protected static function Determines the joint cacheability of all provided dependencies.
ResourceObjectNormalizationCacher::onTerminate public function Writes normalizations of entities to cache, if any were created.
ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_BASE constant Key for the base subset.
ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_FIELDS constant Key for the fields subset.
ResourceObjectNormalizationCacher::saveOnTerminate public function Adds a normalization to be cached after the response has been sent.
ResourceObjectNormalizationCacher::set protected function Writes a normalization to cache.
ResourceObjectNormalizationCacher::setRequestStack public function Sets the request stack.
ResourceObjectNormalizationCacher::setVariationCache public function Sets the variation cache.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.