From 52a598e2bf739b39709bd1fd77237e99e8572017 Mon Sep 17 00:00:00 2001 From: Lee Rowlands Date: Wed, 8 Nov 2017 12:01:36 +1000 Subject: [PATCH] Issue #2910211 by tedbow, Wim Leers: Allow computed exposed properties in ComplexData to support cacheability --- ...tHalJsonInternalPropertyNormalizerTest.php | 15 ++++++ .../ResourceResponseSubscriber.php | 49 +++++++++++++++---- ...TestJsonInternalPropertyNormalizerTest.php | 15 ++++++ .../src/Normalizer/NormalizerBase.php | 16 ++++++ .../src/Normalizer/TypedDataNormalizer.php | 1 + .../src/TypedData/ComputedString.php | 25 +++++++++- 6 files changed, 110 insertions(+), 11 deletions(-) diff --git a/core/modules/hal/tests/src/Functional/EntityResource/EntityTest/EntityTestHalJsonInternalPropertyNormalizerTest.php b/core/modules/hal/tests/src/Functional/EntityResource/EntityTest/EntityTestHalJsonInternalPropertyNormalizerTest.php index 8ee32155712..819a125f82a 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/EntityTest/EntityTestHalJsonInternalPropertyNormalizerTest.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/EntityTest/EntityTestHalJsonInternalPropertyNormalizerTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\hal\Functional\EntityResource\EntityTest; +use Drupal\Core\Cache\Cache; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait; @@ -82,4 +83,18 @@ class EntityTestHalJsonInternalPropertyNormalizerTest extends EntityTestHalJsonA ]; } + /** + * {@inheritdoc} + */ + protected function getExpectedCacheContexts() { + return Cache::mergeContexts(parent::getExpectedCacheContexts(), ['request_format']); + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheTags() { + return Cache::mergeTags(parent::getExpectedCacheTags(), ['you_are_it', 'no_tag_backs']); + } + } diff --git a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php index ef33817ce85..babb238b456 100644 --- a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php +++ b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php @@ -2,6 +2,7 @@ namespace Drupal\rest\EventSubscriber; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheableResponse; use Drupal\Core\Cache\CacheableResponseInterface; use Drupal\Core\Render\RenderContext; @@ -20,6 +21,15 @@ use Symfony\Component\Serializer\SerializerInterface; */ class ResourceResponseSubscriber implements EventSubscriberInterface { + /** + * Name of key for bubbling cacheability metadata via serialization context. + * + * @see \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() + * @see \Symfony\Component\Serializer\SerializerInterface::serialize() + * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::renderResponseBody() + */ + const SERIALIZATION_CONTEXT_CACHEABILITY = 'cacheability'; + /** * The serializer. * @@ -128,11 +138,19 @@ class ResourceResponseSubscriber implements EventSubscriberInterface { /** * Renders a resource response body. * - * Serialization can invoke rendering (e.g., generating URLs), but the - * serialization API does not provide a mechanism to collect the - * bubbleable metadata associated with that (e.g., language and other - * contexts), so instead, allow those to "leak" and collect them here in - * a render context. + * During serialization, encoders and normalizers are able to explicitly + * bubble cacheability metadata via the 'cacheability' key-value pair in the + * received context. This bubbled cacheability metadata will be applied to the + * the response. + * + * In versions of Drupal prior to 8.5, implicit bubbling of cacheability + * metadata was allowed because there was no explicit cacheability metadata + * bubbling API. To maintain backwards compatibility, we continue to support + * this, but support for this will be dropped in Drupal 9.0.0. This is + * especially useful when interacting with APIs that implicitly invoke + * rendering (for example: generating URLs): this allows those to "leak", and + * we collect their bubbled cacheability metadata automatically in a render + * context. * * @param \Symfony\Component\HttpFoundation\Request $request * The request object. @@ -152,14 +170,25 @@ class ResourceResponseSubscriber implements EventSubscriberInterface { // If there is data to send, serialize and set it as the response body. if ($data !== NULL) { + $serialization_context = [ + 'request' => $request, + static::SERIALIZATION_CONTEXT_CACHEABILITY => new CacheableMetadata(), + ]; + + // @deprecated In Drupal 8.5.0, will be removed before Drupal 9.0.0. Use + // explicit cacheability metadata bubbling instead. (The wrapping call to + // executeInRenderContext() will be removed before Drupal 9.0.0.) $context = new RenderContext(); $output = $this->renderer - ->executeInRenderContext($context, function () use ($serializer, $data, $format) { - return $serializer->serialize($data, $format); + ->executeInRenderContext($context, function () use ($serializer, $data, $format, $serialization_context) { + return $serializer->serialize($data, $format, $serialization_context); }); - - if ($response instanceof CacheableResponseInterface && !$context->isEmpty()) { - $response->addCacheableDependency($context->pop()); + if ($response instanceof CacheableResponseInterface) { + if (!$context->isEmpty()) { + @trigger_error('Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.5.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling. See https://www.drupal.org/node/2918937', E_USER_DEPRECATED); + $response->addCacheableDependency($context->pop()); + } + $response->addCacheableDependency($serialization_context[static::SERIALIZATION_CONTEXT_CACHEABILITY]); } $response->setContent($output); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonInternalPropertyNormalizerTest.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonInternalPropertyNormalizerTest.php index c369e17050f..1944718cf71 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonInternalPropertyNormalizerTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonInternalPropertyNormalizerTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest; +use Drupal\Core\Cache\Cache; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\rest\Functional\AnonResourceTestTrait; @@ -84,4 +85,18 @@ class EntityTestJsonInternalPropertyNormalizerTest extends EntityTestResourceTes ]; } + /** + * {@inheritdoc} + */ + protected function getExpectedCacheContexts() { + return Cache::mergeContexts(parent::getExpectedCacheContexts(), ['request_format']); + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheTags() { + return Cache::mergeTags(parent::getExpectedCacheTags(), ['you_are_it', 'no_tag_backs']); + } + } diff --git a/core/modules/serialization/src/Normalizer/NormalizerBase.php b/core/modules/serialization/src/Normalizer/NormalizerBase.php index 5e829f65e70..958aaf2f4c0 100644 --- a/core/modules/serialization/src/Normalizer/NormalizerBase.php +++ b/core/modules/serialization/src/Normalizer/NormalizerBase.php @@ -2,6 +2,8 @@ namespace Drupal\serialization\Normalizer; +use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\rest\EventSubscriber\ResourceResponseSubscriber; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer; @@ -81,4 +83,18 @@ abstract class NormalizerBase extends SerializerAwareNormalizer implements Norma return in_array($format, (array) $this->format, TRUE); } + /** + * Adds cacheability if applicable. + * + * @param array $context + * Context options for the normalizer. + * @param $data + * The data that might have cacheability information. + */ + protected function addCacheableDependency(array $context, $data) { + if ($data instanceof CacheableDependencyInterface && isset($context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY])) { + $context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY]->addCacheableDependency($data); + } + } + } diff --git a/core/modules/serialization/src/Normalizer/TypedDataNormalizer.php b/core/modules/serialization/src/Normalizer/TypedDataNormalizer.php index 60ce7d08f69..958b987dc7d 100644 --- a/core/modules/serialization/src/Normalizer/TypedDataNormalizer.php +++ b/core/modules/serialization/src/Normalizer/TypedDataNormalizer.php @@ -18,6 +18,7 @@ class TypedDataNormalizer extends NormalizerBase { * {@inheritdoc} */ public function normalize($object, $format = NULL, array $context = []) { + $this->addCacheableDependency($context, $object); return $object->getValue(); } diff --git a/core/modules/system/tests/modules/entity_test/src/TypedData/ComputedString.php b/core/modules/system/tests/modules/entity_test/src/TypedData/ComputedString.php index 121699c8071..a8175263b37 100644 --- a/core/modules/system/tests/modules/entity_test/src/TypedData/ComputedString.php +++ b/core/modules/system/tests/modules/entity_test/src/TypedData/ComputedString.php @@ -2,12 +2,14 @@ namespace Drupal\entity_test\TypedData; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\TypedData\TypedData; /** * A computed property for test strings. */ -class ComputedString extends TypedData { +class ComputedString extends TypedData implements CacheableDependencyInterface { /** * {@inheritdoc} @@ -27,4 +29,25 @@ class ComputedString extends TypedData { return $this->getString(); } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return ['you_are_it', 'no_tag_backs']; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['request_format']; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + }