diff --git a/core/modules/hal/src/LinkManager/LinkManagerBase.php b/core/modules/hal/src/LinkManager/LinkManagerBase.php index 20adc0802ec29ace3a418f57a60ac43c1723ce88..b6af1622d7aeb62dc4188dbf3ad276c0d2bcaae6 100644 --- a/core/modules/hal/src/LinkManager/LinkManagerBase.php +++ b/core/modules/hal/src/LinkManager/LinkManagerBase.php @@ -2,6 +2,8 @@ namespace Drupal\hal\LinkManager; +use Drupal\rest\EventSubscriber\ResourceResponseSubscriber; + /** * Defines an abstract base-class for HAL link manager objects. */ @@ -39,17 +41,32 @@ public function setLinkDomain($domain) { /** * Gets the link domain. * + * @param array $context + * Normalization/serialization context. + * * @return string * The link domain. + * + * @see \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() + * @see \Symfony\Component\Serializer\SerializerInterface::serialize() + * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY */ - protected function getLinkDomain() { + protected function getLinkDomain(array $context = []) { if (empty($this->linkDomain)) { if ($domain = $this->configFactory->get('hal.settings')->get('link_domain')) { - $this->linkDomain = rtrim($domain, '/'); + // Bubble the appropriate cacheability metadata whenever possible. + if (isset($context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY])) { + $context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY]->addCacheableDependency($this->configFactory->get('hal.settings')); + } + return rtrim($domain, '/'); } else { + // Bubble the relevant cacheability metadata whenever possible. + if (isset($context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY])) { + $context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY]->addCacheContexts(['url.site']); + } $request = $this->requestStack->getCurrentRequest(); - $this->linkDomain = $request->getSchemeAndHttpHost() . $request->getBasePath(); + return $request->getSchemeAndHttpHost() . $request->getBasePath(); } } return $this->linkDomain; diff --git a/core/modules/hal/src/LinkManager/RelationLinkManager.php b/core/modules/hal/src/LinkManager/RelationLinkManager.php index a33b6c32bbd3fad06d602e88cf233457a3bd18ca..ec631416c9bc823fa3d53ca7d5254d21536efb2f 100644 --- a/core/modules/hal/src/LinkManager/RelationLinkManager.php +++ b/core/modules/hal/src/LinkManager/RelationLinkManager.php @@ -68,7 +68,7 @@ public function getRelationUri($entity_type, $bundle, $field_name, $context = [] // module is installed that adds such content, but requires this URL to be // different (e.g., include a language prefix), then the module must also // override the RelationLinkManager class/service to return the desired URL. - $uri = $this->getLinkDomain() . "/rest/relation/$entity_type/$bundle/$field_name"; + $uri = $this->getLinkDomain($context) . "/rest/relation/$entity_type/$bundle/$field_name"; $this->moduleHandler->alter('hal_relation_uri', $uri, $context); // @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This // hook is invoked to maintain backwards compatibility diff --git a/core/modules/hal/src/LinkManager/TypeLinkManager.php b/core/modules/hal/src/LinkManager/TypeLinkManager.php index 51b2de5032c5ed7f94e8cb025aad9bb1a1606dc8..47206d8afe880b9cf393463dd8d3acf4a5831cec 100644 --- a/core/modules/hal/src/LinkManager/TypeLinkManager.php +++ b/core/modules/hal/src/LinkManager/TypeLinkManager.php @@ -69,7 +69,7 @@ public function getTypeUri($entity_type, $bundle, $context = []) { // installed that adds such content, but requires this URL to be different // (e.g., include a language prefix), then the module must also override the // TypeLinkManager class/service to return the desired URL. - $uri = $this->getLinkDomain() . "/rest/type/$entity_type/$bundle"; + $uri = $this->getLinkDomain($context) . "/rest/type/$entity_type/$bundle"; $this->moduleHandler->alter('hal_type_uri', $uri, $context); // @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This // hook is invoked to maintain backwards compatibility diff --git a/core/modules/hal/tests/src/Kernel/HalLinkManagerTest.php b/core/modules/hal/tests/src/Kernel/HalLinkManagerTest.php index def2789443543879e230918c46ff2bebce4d31bf..1387149c4ef8e6bae1e2e346cf342a05fd092a9c 100644 --- a/core/modules/hal/tests/src/Kernel/HalLinkManagerTest.php +++ b/core/modules/hal/tests/src/Kernel/HalLinkManagerTest.php @@ -2,11 +2,13 @@ namespace Drupal\Tests\hal\Kernel; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Url; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\KernelTests\KernelTestBase; use Drupal\node\Entity\NodeType; +use Drupal\rest\EventSubscriber\ResourceResponseSubscriber; /** * @coversDefaultClass \Drupal\hal\LinkManager\LinkManager @@ -67,6 +69,13 @@ public function testGetTypeUri($link_domain, $entity_type, $bundle, array $conte } public function providerTestGetTypeUri() { + $serialization_context_collecting_cacheability = [ + ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY => new CacheableMetadata() + ]; + $expected_serialization_context_cacheability_url_site = [ + ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY => (new CacheableMetadata())->setCacheContexts(['url.site']) + ]; + $base_test_case = [ 'link_domain' => NULL, 'entity_type' => 'node', @@ -80,18 +89,35 @@ public function providerTestGetTypeUri() { 'expected return' => 'BASE_URL/rest/type/node/page', 'expected context' => [], ], + 'site URL, with optional context to collect cacheability metadata' => $base_test_case + [ + 'context' => $serialization_context_collecting_cacheability, + 'expected return' => 'BASE_URL/rest/type/node/page', + 'expected context' => $expected_serialization_context_cacheability_url_site, + ], // Test hook_hal_type_uri_alter(). 'site URL, with optional context, to test hook_hal_type_uri_alter()' => $base_test_case + [ 'context' => ['hal_test' => TRUE], 'expected return' => 'hal_test_type', 'expected context' => ['hal_test' => TRUE], ], + 'site URL, with optional context, to test hook_hal_type_uri_alter(), and collecting cacheability metadata' => $base_test_case + [ + 'context' => ['hal_test' => TRUE] + $serialization_context_collecting_cacheability, + 'expected return' => 'hal_test_type', + // No cacheability metadata bubbled. + 'expected context' => ['hal_test' => TRUE] + $serialization_context_collecting_cacheability, + ], // Test hook_rest_type_uri_alter() — for backwards compatibility. 'site URL, with optional context, to test hook_rest_type_uri_alter()' => $base_test_case + [ 'context' => ['rest_test' => TRUE], 'expected return' => 'rest_test_type', 'expected context' => ['rest_test' => TRUE], ], + 'site URL, with optional context, to test hook_rest_type_uri_alter(), and collecting cacheability metadata' => $base_test_case + [ + 'context' => ['rest_test' => TRUE] + $serialization_context_collecting_cacheability, + 'expected return' => 'rest_test_type', + // No cacheability metadata bubbled. + 'expected context' => ['rest_test' => TRUE] + $serialization_context_collecting_cacheability, + ], 'configured URL' => [ 'link_domain' => 'http://llamas-rock.com/for-real/', 'entity_type' => 'node', @@ -100,6 +126,16 @@ public function providerTestGetTypeUri() { 'expected return' => 'http://llamas-rock.com/for-real/rest/type/node/page', 'expected context' => [], ], + 'configured URL, with optional context to collect cacheability metadata' => [ + 'link_domain' => 'http://llamas-rock.com/for-real/', + 'entity_type' => 'node', + 'bundle' => 'page', + 'context' => $serialization_context_collecting_cacheability, + 'expected return' => 'http://llamas-rock.com/for-real/rest/type/node/page', + 'expected context' => [ + ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY => (new CacheableMetadata())->setCacheTags(['config:hal.settings']), + ], + ], ]; } @@ -126,6 +162,13 @@ public function testGetRelationUri($link_domain, $entity_type, $bundle, $field_n } public function providerTestGetRelationUri() { + $serialization_context_collecting_cacheability = [ + ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY => new CacheableMetadata() + ]; + $expected_serialization_context_cacheability_url_site = [ + ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY => (new CacheableMetadata())->setCacheContexts(['url.site']) + ]; + $field_name = $this->randomMachineName(); $base_test_case = [ 'link_domain' => NULL, @@ -141,18 +184,35 @@ public function providerTestGetRelationUri() { 'expected return' => 'BASE_URL/rest/relation/node/page/' . $field_name, 'expected context' => [], ], + 'site URL, with optional context to collect cacheability metadata' => $base_test_case + [ + 'context' => $serialization_context_collecting_cacheability, + 'expected return' => 'BASE_URL/rest/relation/node/page/' . $field_name, + 'expected context' => $expected_serialization_context_cacheability_url_site, + ], // Test hook_hal_relation_uri_alter(). 'site URL, with optional context, to test hook_hal_relation_uri_alter()' => $base_test_case + [ 'context' => ['hal_test' => TRUE], 'expected return' => 'hal_test_relation', 'expected context' => ['hal_test' => TRUE], ], + 'site URL, with optional context, to test hook_hal_relation_uri_alter(), and collecting cacheability metadata' => $base_test_case + [ + 'context' => ['hal_test' => TRUE] + $serialization_context_collecting_cacheability, + 'expected return' => 'hal_test_relation', + // No cacheability metadata bubbled. + 'expected context' => ['hal_test' => TRUE] + $serialization_context_collecting_cacheability, + ], // Test hook_rest_relation_uri_alter() — for backwards compatibility. 'site URL, with optional context, to test hook_rest_relation_uri_alter()' => $base_test_case + [ 'context' => ['rest_test' => TRUE], 'expected return' => 'rest_test_relation', 'expected context' => ['rest_test' => TRUE], ], + 'site URL, with optional context, to test hook_rest_relation_uri_alter(), and collecting cacheability metadata' => $base_test_case + [ + 'context' => ['rest_test' => TRUE] + $serialization_context_collecting_cacheability, + 'expected return' => 'rest_test_relation', + // No cacheability metadata bubbled. + 'expected context' => ['rest_test' => TRUE] + $serialization_context_collecting_cacheability, + ], 'configured URL' => [ 'link_domain' => 'http://llamas-rock.com/for-real/', 'entity_type' => 'node', @@ -162,6 +222,17 @@ public function providerTestGetRelationUri() { 'expected return' => 'http://llamas-rock.com/for-real/rest/relation/node/page/' . $field_name, 'expected context' => [], ], + 'configured URL, with optional context to collect cacheability metadata' => [ + 'link_domain' => 'http://llamas-rock.com/for-real/', + 'entity_type' => 'node', + 'bundle' => 'page', + 'field_name' => $field_name, + 'context' => $serialization_context_collecting_cacheability, + 'expected return' => 'http://llamas-rock.com/for-real/rest/relation/node/page/' . $field_name, + 'expected context' => [ + ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY => (new CacheableMetadata())->setCacheTags(['config:hal.settings']), + ], + ], ]; } @@ -186,13 +257,19 @@ public function testGetRelationInternalIds() { * @covers ::setLinkDomain */ public function testHalLinkManagersSetLinkDomain() { + $serialization_context = [ + ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY => new CacheableMetadata() + ]; + /* @var \Drupal\rest\LinkManager\LinkManager $link_manager */ $link_manager = \Drupal::service('hal.link_manager'); $link_manager->setLinkDomain('http://example.com/'); - $link = $link_manager->getTypeUri('node', 'page'); + $link = $link_manager->getTypeUri('node', 'page', $serialization_context); $this->assertEqual($link, 'http://example.com/rest/type/node/page'); - $link = $link_manager->getRelationUri('node', 'page', 'field_ref'); + $this->assertEqual(new CacheableMetadata(), $serialization_context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY]); + $link = $link_manager->getRelationUri('node', 'page', 'field_ref', $serialization_context); $this->assertEqual($link, 'http://example.com/rest/relation/node/page/field_ref'); + $this->assertEqual(new CacheableMetadata(), $serialization_context[ResourceResponseSubscriber::SERIALIZATION_CONTEXT_CACHEABILITY]); } }