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 0ba00bd21f..1847e13964 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; @@ -83,4 +84,18 @@ protected function getNormalizedPostEntity() { ]; } + /** + * {@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 ef33817ce8..699d2c2a0f 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; @@ -128,11 +129,18 @@ public function getResponseFormat(RouteMatchInterface $route_match, Request $req /** * 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 prior versions of Drupal 8, we allowed implicit bubbling of cacheability + * metadata 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 +160,25 @@ protected function renderResponseBody(Request $request, ResourceResponseInterfac // If there is data to send, serialize and set it as the response body. if ($data !== NULL) { + $serialization_context = [ + 'request' => $request, + 'cacheability' => new CacheableMetadata(), + ]; + + // @deprecated In Drupal 8.4.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.4.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling.', E_USER_DEPRECATED); + $response->addCacheableDependency($context->pop()); + } + $response->addCacheableDependency($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 9b7f1bcc91..341017514e 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 @@ protected function getNormalizedPostEntity() { ]; } + /** + * {@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/PrimitiveDataNormalizer.php b/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php index cce108cacf..86d92531ed 100644 --- a/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php +++ b/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php @@ -2,6 +2,7 @@ namespace Drupal\serialization\Normalizer; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\TypedData\PrimitiveInterface; /** @@ -26,7 +27,16 @@ public function normalize($object, $format = NULL, array $context = []) { // getCastedValue would return NULL, but as typed data is not aware of real // optional values on the primitive level, we implement our own optional // value normalization here. - return $object->getValue() === NULL ? NULL : $object->getCastedValue(); + $value = $object->getValue(); + if ($value === NULL) { + return NULL; + } + else { + if ($value instanceof CacheableDependencyInterface && isset($context['cacheability'])) { + $context['cacheability']->addCacheableDependency($value); + } + return $object->getCastedValue(); + } } } diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/DataType/ComputedString.php b/core/modules/system/tests/modules/entity_test/src/Plugin/DataType/ComputedString.php index a3b77aeed9..afa4d10992 100644 --- a/core/modules/system/tests/modules/entity_test/src/Plugin/DataType/ComputedString.php +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/DataType/ComputedString.php @@ -2,6 +2,8 @@ namespace Drupal\entity_test\Plugin\DataType; +use Drupal\Core\Render\BubbleableMetadata; + /** * Computed string for testing. * @@ -9,7 +11,7 @@ * * @see \Drupal\entity_test\Plugin\DataType\ComputedStringData */ -class ComputedString { +class ComputedString extends BubbleableMetadata { /** * The string value before computation. @@ -26,6 +28,8 @@ class ComputedString { */ public function __construct($not_computed_value) { $this->sourceString = $not_computed_value; + $this->cacheContexts = ['request_format']; + $this->cacheTags = ['you_are_it', 'no_tag_backs']; } /**