diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php index 0d446ee7f19a9c7c28c91f213879fd73ec9b7d4b..baf916193ca1b5e7de5de9c7298888953c52c06d 100644 --- a/core/lib/Drupal/Core/Access/AccessManager.php +++ b/core/lib/Drupal/Core/Access/AccessManager.php @@ -104,7 +104,7 @@ public function checkNamedRoute($route_name, array $parameters = array(), Accoun catch (ParamNotConvertedException $e) { // Uncacheable because conversion of the parameter may not have been // possible due to dynamic circumstances. - $result = AccessResult::forbidden()->setCacheable(FALSE); + $result = AccessResult::forbidden()->setCacheMaxAge(0); return $return_as_object ? $result : $result->isAllowed(); } } diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php index bb7682101ee425f3370c3812b50dd267207a4c7e..6188b3407d18a7cba7e8ac4f206fe282145b458b 100644 --- a/core/lib/Drupal/Core/Access/AccessResult.php +++ b/core/lib/Drupal/Core/Access/AccessResult.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Access; use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\CacheableInterface; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Config\ConfigBase; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Session\AccountInterface; @@ -26,14 +26,7 @@ * When using ::orIf() and ::andIf(), cacheability metadata will be merged * accordingly as well. */ -abstract class AccessResult implements AccessResultInterface, CacheableInterface { - - /** - * Whether the access result is cacheable. - * - * @var bool - */ - protected $isCacheable; +abstract class AccessResult implements AccessResultInterface, CacheableDependencyInterface { /** * The cache context IDs (to vary a cache item ID based on active contexts). @@ -63,9 +56,9 @@ abstract class AccessResult implements AccessResultInterface, CacheableInterface * Constructs a new AccessResult object. */ public function __construct() { - $this->setCacheable(TRUE) - ->resetCacheContexts() + $this->resetCacheContexts() ->resetCacheTags() + // Max-age must be non-zero for an access result to be cacheable. // Typically, cache items are invalidated via associated cache tags, not // via a maximum age. ->setCacheMaxAge(Cache::PERMANENT); @@ -215,13 +208,6 @@ public function isNeutral() { return FALSE; } - /** - * {@inheritdoc} - */ - public function getCacheKeys() { - return []; - } - /** * {@inheritdoc} */ @@ -237,16 +223,6 @@ public function getCacheTags() { return $this->tags; } - /** - * {@inheritdoc} - * - * It's not very useful to cache individual access results, but the interface - * forces us to implement this method, so just use the default cache bin. - */ - public function getCacheBin() { - return 'default'; - } - /** * {@inheritdoc} */ @@ -254,26 +230,6 @@ public function getCacheMaxAge() { return $this->maxAge; } - /** - * {@inheritdoc} - */ - public function isCacheable() { - return $this->isCacheable; - } - - /** - * Sets whether this access result is cacheable. It is cacheable by default. - * - * @param bool $is_cacheable - * Whether this access result is cacheable. - * - * @return $this - */ - public function setCacheable($is_cacheable) { - $this->isCacheable = $is_cacheable; - return $this; - } - /** * Adds cache contexts associated with the access result. * @@ -392,19 +348,19 @@ public function orIf(AccessResultInterface $other) { $merge_other = FALSE; if ($this->isForbidden() || $other->isForbidden()) { $result = static::forbidden(); - if (!$this->isForbidden() || (!$this->isCacheable() && $other->isForbidden())) { + if (!$this->isForbidden() || ($this->getCacheMaxAge() === 0 && $other->isForbidden())) { $merge_other = TRUE; } } elseif ($this->isAllowed() || $other->isAllowed()) { $result = static::allowed(); - if (!$this->isAllowed() || (!$this->isCacheable() && $other->isAllowed())) { + if (!$this->isAllowed() || ($this->getCacheMaxAge() === 0 && $other->isAllowed())) { $merge_other = TRUE; } } else { $result = static::neutral(); - if (!$this->isNeutral() || (!$this->isCacheable() && $other->isNeutral())) { + if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral())) { $merge_other = TRUE; } } @@ -446,8 +402,8 @@ public function andIf(AccessResultInterface $other) { // result must also not be cacheable, except if the other access result // has isForbidden() === TRUE. isForbidden() access results are contagious // in that they propagate regardless of the other value. - if (!$this->isCacheable() && !$result->isForbidden()) { - $result->setCacheable(FALSE); + if ($this->getCacheMaxAge() === 0 && !$result->isForbidden()) { + $result->setCacheMaxAge(0); } } return $result; @@ -462,17 +418,21 @@ public function andIf(AccessResultInterface $other) { * @return $this */ public function inheritCacheability(AccessResultInterface $other) { - if ($other instanceof CacheableInterface) { - $this->setCacheable($other->isCacheable()); + if ($other instanceof CacheableDependencyInterface) { + if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) { + $this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge())); + } + else { + $this->setCacheMaxAge($other->getCacheMaxAge()); + } $this->addCacheContexts($other->getCacheContexts()); $this->addCacheTags($other->getCacheTags()); - $this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge())); } // If any of the access results don't provide cacheability metadata, then // we cannot cache the combined access result, for we may not make // assumptions. else { - $this->setCacheable(FALSE); + $this->setCacheMaxAge(0); } return $this; } diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php index 0331df963dca6b6ceae2553b1f0ebcd58e897e03..61b882def7cec2103cbe5a1458b1c27f8ad30a22 100644 --- a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php @@ -66,7 +66,7 @@ public function access(Route $route, Request $request, RouteMatchInterface $rout $result = AccessResult::forbidden(); } // Not cacheable because the CSRF token is highly dynamic. - return $result->setCacheable(FALSE); + return $result->setCacheMaxAge(0); } } diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php index 8bf86c50ec5c37a35e17b8e465895361a0c9972f..bf349cdfadebafbc1e6b9b7b1b63e4b2e0373717 100644 --- a/core/lib/Drupal/Core/Block/BlockBase.php +++ b/core/lib/Drupal/Core/Block/BlockBase.php @@ -131,7 +131,7 @@ public function access(AccountInterface $account, $return_as_object = FALSE) { $access = AccessResult::forbidden(); } - $access->setCacheable(FALSE); + $access->setCacheMaxAge(0); return $return_as_object ? $access : $access->isAllowed(); } diff --git a/core/lib/Drupal/Core/Cache/CacheableDependencyInterface.php b/core/lib/Drupal/Core/Cache/CacheableDependencyInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1d447a11b5ec75a38ef1264cd41d2865a6e303cc --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheableDependencyInterface.php @@ -0,0 +1,56 @@ +<?php +/** + * @file + * Contains \Drupal\Core\CacheableDependencyInterface + */ + +namespace Drupal\Core\Cache; + +/** + * Defines an interface for objects which may be used by other cached objects. + * + * All cacheability metadata exposed in this interface is bubbled to parent + * objects when they are cached: if a child object needs to be varied by certain + * cache contexts, invalidated by certain cache tags, expire after a certain + * maximum age, then so should any parent object. + * + * @ingroup cache + */ +interface CacheableDependencyInterface { + + /** + * The cache contexts associated with this object. + * + * These identify a specific variation/representation of the object. + * + * Cache contexts are tokens: placeholders that are converted to cache keys by + * the @cache_contexts service. The replacement value depends on the request + * context (the current URL, language, and so on). They're converted before + * storing an object in cache. + * + * @return string[] + * An array of cache context tokens, used to generate a cache ID. + * + * @see \Drupal\Core\Cache\CacheContexts::convertTokensToKeys() + */ + public function getCacheContexts(); + + /** + * The cache tags associated with this object. + * + * When this object is modified, these cache tags will be invalidated. + * + * @return string[] + * A set of cache tags. + */ + public function getCacheTags(); + + /** + * The maximum age for which this object may be cached. + * + * @return int + * The maximum time in seconds that this object may be cached. + */ + public function getCacheMaxAge(); + +} diff --git a/core/lib/Drupal/Core/Cache/CacheableInterface.php b/core/lib/Drupal/Core/Cache/CacheableInterface.php index 70b0a8733b71b78fd6382cd9092205ab4ff7189a..f57f23644af67af85232caea0c02ad774430bdba 100644 --- a/core/lib/Drupal/Core/Cache/CacheableInterface.php +++ b/core/lib/Drupal/Core/Cache/CacheableInterface.php @@ -20,7 +20,7 @@ * * @ingroup cache */ -interface CacheableInterface { +interface CacheableInterface extends CacheableDependencyInterface { /** * The cache keys associated with this potentially cacheable object. @@ -32,39 +32,6 @@ interface CacheableInterface { */ public function getCacheKeys(); - /** - * The cache contexts associated with this potentially cacheable object. - * - * These identify a specific variation/representation of the object. - * - * Cache contexts are tokens: placeholders that are converted to cache keys by - * the @cache_contexts service. The replacement value depends on the request - * context (the current URL, language, and so on). They're converted before - * storing an object in cache. - * - * @return string[] - * An array of cache context tokens, used to generate a cache ID. - * - * @see \Drupal\Core\Cache\CacheContexts::convertTokensToKeys() - */ - public function getCacheContexts(); - - /** - * The cache tags associated with this potentially cacheable object. - * - * @return string[] - * An array of cache tags. - */ - public function getCacheTags(); - - /** - * The maximum age for which this object may be cached. - * - * @return int - * The maximum time in seconds that this object may be cached. - */ - public function getCacheMaxAge(); - /** * Indicates whether this object is cacheable. * diff --git a/core/lib/Drupal/Core/Cache/LanguagesCacheContext.php b/core/lib/Drupal/Core/Cache/LanguagesCacheContext.php index 2ed15177672d09ef2942321c13ad69bdf8384bea..b9b888345d014dbdfd7258d210b9f79b3a74214a 100644 --- a/core/lib/Drupal/Core/Cache/LanguagesCacheContext.php +++ b/core/lib/Drupal/Core/Cache/LanguagesCacheContext.php @@ -66,7 +66,8 @@ public function getContext($type = NULL) { return implode(',', $context_parts); } else { - if (!in_array($type, $this->languageManager->getLanguageTypes())) { + $language_types = $this->languageManager->getDefinedLanguageTypesInfo(); + if (!isset($language_types[$type])) { throw new \RuntimeException(sprintf('The language type "%s" is invalid.', $type)); } return $this->languageManager->getCurrentLanguage($type)->getId(); diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php index f97f5fd7008b3fb93b5948d0baa8a4b372ef1f2f..eee76a2ad85b211162911a3adda164b372959af6 100644 --- a/core/lib/Drupal/Core/Config/ConfigBase.php +++ b/core/lib/Drupal/Core/Config/ConfigBase.php @@ -9,6 +9,8 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use \Drupal\Core\DependencyInjection\DependencySerializationTrait; /** @@ -26,7 +28,7 @@ * @see \Drupal\Core\Config\Config * @see \Drupal\Core\Theme\ThemeSettings */ -abstract class ConfigBase { +abstract class ConfigBase implements CacheableDependencyInterface { use DependencySerializationTrait; /** @@ -264,13 +266,24 @@ public function merge(array $data_to_merge) { } /** - * The unique cache tag associated with this configuration object. - * - * @return string[] - * An array of cache tags. + * {@inheritdoc} + */ + public function getCacheContexts() { + return []; + } + + /** + * {@inheritdoc} */ public function getCacheTags() { return ['config:' . $this->name]; } + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 08c20cd05a2d8048a112fccc8667a549755dd9bd..5a4073fcd28f4504932c6edfa6af3a5b4ffd4a59 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -426,6 +426,13 @@ public function referencedEntities() { return array(); } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return []; + } + /** * {@inheritdoc} */ @@ -434,6 +441,13 @@ public function getCacheTags() { return [$this->entityTypeId . ':' . $this->id()]; } + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + /** * {@inheritdoc} * diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index fac03809b7b5844e8db1579fae28b64b7778fc48..7b5ebc723b997d7a7c10343a0f8161a5c491c322 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -8,13 +8,14 @@ namespace Drupal\Core\Entity; use Drupal\Core\Access\AccessibleInterface; +use Drupal\Core\Cache\CacheableDependencyInterface; /** * Defines a common interface for all entity objects. * * @ingroup entity_api */ -interface EntityInterface extends AccessibleInterface { +interface EntityInterface extends AccessibleInterface, CacheableDependencyInterface { /** * Returns the entity UUID (Universally Unique Identifier). @@ -396,14 +397,6 @@ public function toArray(); */ public function getTypedData(); - /** - * The unique cache tag associated with this entity. - * - * @return string[] - * An array of cache tags. - */ - public function getCacheTags(); - /** * Gets the key that is used to store configuration dependencies. * diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index d12d028d6de60d813041eb169f259a7976100e3d..13a7b50625481c11549acfbbf1b35dcbd9584866 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -169,12 +169,16 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco // Collect cache defaults for this entity. '#cache' => array( 'tags' => Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags()), - 'contexts' => [ - 'user.roles', - ], + 'contexts' => $entity->getCacheContexts(), + 'max-age' => $entity->getCacheMaxAge(), ), ); + // @todo Remove when https://www.drupal.org/node/2099137 lands. + $build['#cache']['contexts'] = Cache::mergeContexts($build['#cache']['contexts'], [ + 'user.roles', + ]); + // Cache the rendered output if permitted by the view mode and global entity // type configuration. if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) { diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php index 0e508a20ae83890bda0db02bc80fa3c6d36a513b..c1daada49c168276741728be74214c21a9cce578 100644 --- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php @@ -9,7 +9,7 @@ use Drupal\Component\Datetime\DateTimePlus; use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\CacheableInterface; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Cache\CacheContexts; use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigFactoryInterface; @@ -135,7 +135,7 @@ public function onRespond(FilterResponseEvent $event) { // Apply the request's access result cacheability metadata, if it has any. $access_result = $request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT); - if ($access_result instanceof CacheableInterface) { + if ($access_result instanceof CacheableDependencyInterface) { $this->updateDrupalCacheHeaders($response, $access_result); } @@ -161,10 +161,10 @@ public function onRespond(FilterResponseEvent $event) { /** * Updates Drupal's cache headers using the route's cacheable access result. * - * @param Response $response - * @param CacheableInterface $cacheable_access_result + * @param \Symfony\Component\HttpFoundation\Response $response + * @param \Drupal\Core\Cache\CacheableDependencyInterface $cacheable_access_result */ - protected function updateDrupalCacheHeaders(Response $response, CacheableInterface $cacheable_access_result) { + protected function updateDrupalCacheHeaders(Response $response, CacheableDependencyInterface $cacheable_access_result) { // X-Drupal-Cache-Tags $cache_tags = $cacheable_access_result->getCacheTags(); if ($response->headers->has('X-Drupal-Cache-Tags')) { diff --git a/core/lib/Drupal/Core/Render/BubbleableMetadata.php b/core/lib/Drupal/Core/Render/BubbleableMetadata.php index d822d82bd944d3559bc7e2c8bbe02dafb051c76c..c4852a99aaf74de9bef01c94462051834441226d 100644 --- a/core/lib/Drupal/Core/Render/BubbleableMetadata.php +++ b/core/lib/Drupal/Core/Render/BubbleableMetadata.php @@ -9,13 +9,14 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; /** * Value object used for bubbleable rendering metadata. * * @see \Drupal\Core\Render\RendererInterface::render() */ -class BubbleableMetadata { +class BubbleableMetadata implements CacheableDependencyInterface { /** * Cache contexts. @@ -107,6 +108,22 @@ public static function createFromRenderArray(array $build) { return $meta; } + /** + * Creates a bubbleable metadata object from a cacheable depended object. + * + * @param \Drupal\Core\Cache\CacheableDependencyInterface $object + * The object whose cacheability metadata to retrieve. + * + * @return static + */ + public static function createFromObject(CacheableDependencyInterface $object) { + $meta = new static(); + $meta->contexts = $object->getCacheContexts(); + $meta->tags = $object->getCacheTags(); + $meta->maxAge = $object->getCacheMaxAge(); + return $meta; + } + /** * Gets cache tags. * diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 9205f6018a63c1012a1b3c23a1e0d7f30c73331b..0ec8fa13c771a4d3fad662f6325e17fa804c8bc6 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Cache\CacheContexts; use Drupal\Core\Cache\CacheFactoryInterface; use Drupal\Core\Controller\ControllerResolverInterface; @@ -788,6 +789,15 @@ public static function mergeBubbleableMetadata(array $a, array $b) { return $a; } + /** + * {@inheritdoc} + */ + public function addDependency(array &$elements, CacheableDependencyInterface $dependency) { + $meta_a = BubbleableMetadata::createFromRenderArray($elements); + $meta_b = BubbleableMetadata::createFromObject($dependency); + $meta_a->merge($meta_b)->applyTo($elements); + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php index 21b223d6fdffc6b76a321544af96c99a182da484..85035bfa526f79fe0c4eec74207119624254a740 100644 --- a/core/lib/Drupal/Core/Render/RendererInterface.php +++ b/core/lib/Drupal/Core/Render/RendererInterface.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Render; +use Drupal\Core\Cache\CacheableDependencyInterface; + /** * Defines an interface for turning a render array into a string. */ @@ -340,6 +342,20 @@ public function getCacheableRenderArray(array $elements); */ public static function mergeBubbleableMetadata(array $a, array $b); + /** + * Adds a dependency on an object: merges its cacheability metadata. + * + * E.g. when a render array depends on some configuration, an entity, or an + * access result, we must make sure their cacheability metadata is present on + * the render array. This method makes doing that simple. + * + * @param array &$elements + * The render array to update. + * @param \Drupal\Core\Cache\CacheableDependencyInterface $dependency + * The dependency. + */ + public function addDependency(array &$elements, CacheableDependencyInterface $dependency); + /** * Merges two attachments arrays (which live under the '#attached' key). * diff --git a/core/modules/block/src/BlockAccessControlHandler.php b/core/modules/block/src/BlockAccessControlHandler.php index 6ec53f67eab36f26b3ce48323f1a10292e92fec3..148e3e3c109410f3fcbd42a6fa45697af161bc61 100644 --- a/core/modules/block/src/BlockAccessControlHandler.php +++ b/core/modules/block/src/BlockAccessControlHandler.php @@ -93,7 +93,7 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A $this->contextHandler->applyContextMapping($condition, $contexts); } catch (ContextException $e) { - return AccessResult::forbidden()->setCacheable(FALSE); + return AccessResult::forbidden()->setCacheMaxAge(0); } } $conditions[$condition_id] = $condition; @@ -111,7 +111,7 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A // result should be varied. // @todo Change this to use $access->cacheUntilEntityChanges($entity) once // https://www.drupal.org/node/2375695 is resolved. - return $access->setCacheable(FALSE); + return $access->setCacheMaxAge(0); } } diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml index 89b25fc02af1b0b10cdbfd74f8d0a3237d645ce6..6d024f73bc9c1d90cbc36823fd6a5de693ae8783 100644 --- a/core/modules/book/book.services.yml +++ b/core/modules/book/book.services.yml @@ -6,7 +6,7 @@ services: - { name: breadcrumb_builder, priority: 701 } book.manager: class: Drupal\book\BookManager - arguments: ['@entity.manager', '@string_translation', '@config.factory', '@book.outline_storage'] + arguments: ['@entity.manager', '@string_translation', '@config.factory', '@book.outline_storage', '@renderer'] book.outline: class: Drupal\book\BookOutline arguments: ['@book.manager'] diff --git a/core/modules/book/src/BookManager.php b/core/modules/book/src/BookManager.php index 26c02fa781d58dd57e690474ec90beb19d6dea58..41daf56383155cccaea5c0bc9afaf56f101e400c 100644 --- a/core/modules/book/src/BookManager.php +++ b/core/modules/book/src/BookManager.php @@ -11,6 +11,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -63,14 +64,22 @@ class BookManager implements BookManagerInterface { */ protected $bookTreeFlattened; + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + /** * Constructs a BookManager object. */ - public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, BookOutlineStorageInterface $book_outline_storage) { + public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, BookOutlineStorageInterface $book_outline_storage, RendererInterface $renderer) { $this->entityManager = $entity_manager; $this->stringTranslation = $translation; $this->configFactory = $config_factory; $this->bookOutlineStorage = $book_outline_storage; + $this->renderer = $renderer; } /** @@ -353,7 +362,7 @@ protected function addParentSelectFormElements(array $book_link) { '#suffix' => '</div>', ); } - $form['#cache']['tags'] = Cache::mergeTags(isset($form['#cache']['tags']) ? $form['#cache']['tags'] : [], $config->getCacheTags()); + $this->renderer->addDependency($form, $config); return $form; } diff --git a/core/modules/book/tests/src/Unit/BookManagerTest.php b/core/modules/book/tests/src/Unit/BookManagerTest.php index 2c12385c16d88c9c0e05006daa41c9b2f595824a..cb45b2f0f818ce3f2bfec054a392f980c616b1f4 100644 --- a/core/modules/book/tests/src/Unit/BookManagerTest.php +++ b/core/modules/book/tests/src/Unit/BookManagerTest.php @@ -37,6 +37,13 @@ class BookManagerTest extends UnitTestCase { */ protected $translation; + /** + * The mocked renderer. + * + * @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $renderer; + /** * The tested book manager. * @@ -59,7 +66,8 @@ protected function setUp() { $this->translation = $this->getStringTranslationStub(); $this->configFactory = $this->getConfigFactoryStub(array()); $this->bookOutlineStorage = $this->getMock('Drupal\book\BookOutlineStorageInterface'); - $this->bookManager = new BookManager($this->entityManager, $this->translation, $this->configFactory, $this->bookOutlineStorage); + $this->renderer = $this->getMock('\Drupal\Core\Render\RendererInterface'); + $this->bookManager = new BookManager($this->entityManager, $this->translation, $this->configFactory, $this->bookOutlineStorage, $this->renderer); } /** diff --git a/core/modules/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php index 903bafae3e8659c9e925145014c81ff42d8d4256..65eea3e1c2e8bba690bb872c518f10dc50c6e57d 100644 --- a/core/modules/comment/src/CommentForm.php +++ b/core/modules/comment/src/CommentForm.php @@ -16,6 +16,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,13 +32,21 @@ class CommentForm extends ContentEntityForm { */ protected $currentUser; + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), - $container->get('current_user') + $container->get('current_user'), + $container->get('renderer') ); } @@ -48,10 +57,13 @@ public static function create(ContainerInterface $container) { * The entity manager service. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. */ - public function __construct(EntityManagerInterface $entity_manager, AccountInterface $current_user) { + public function __construct(EntityManagerInterface $entity_manager, AccountInterface $current_user, RendererInterface $renderer) { parent::__construct($entity_manager); $this->currentUser = $current_user; + $this->renderer = $renderer; } /** @@ -213,12 +225,9 @@ public function form(array $form, FormStateInterface $form_state) { '#access' => $is_admin, ); - $form['#cache']['tags'] = Cache::mergeTags( - isset($form['#cache']['tags']) ? $form['#cache']['tags'] : [], - $config->getCacheTags(), - // The form depends on the field definition. - $field_definition->getConfig($entity->bundle())->getCacheTags() - ); + $this->renderer->addDependency($form, $config); + // The form depends on the field definition. + $this->renderer->addDependency($form, $field_definition->getConfig($entity->bundle())); return parent::form($form, $form_state, $comment); } diff --git a/core/modules/contact/src/Controller/ContactController.php b/core/modules/contact/src/Controller/ContactController.php index f3d6825daf1a3224c52a61cad245695d495182e5..df545297b3851c7d124085c97331e6f8e9a4fdfb 100644 --- a/core/modules/contact/src/Controller/ContactController.php +++ b/core/modules/contact/src/Controller/ContactController.php @@ -12,6 +12,7 @@ use Drupal\Core\Datetime\DateFormatter; use Drupal\Core\Flood\FloodInterface; use Drupal\contact\ContactFormInterface; +use Drupal\Core\Render\RendererInterface; use Drupal\user\UserInterface; use Drupal\Component\Utility\SafeMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -37,6 +38,13 @@ class ContactController extends ControllerBase { */ protected $dateFormatter; + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + /** * Constructs a ContactController object. * @@ -44,10 +52,13 @@ class ContactController extends ControllerBase { * The flood service. * @param \Drupal\Core\Datetime\DateFormatter $date_formatter * The date service. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. */ - public function __construct(FloodInterface $flood, DateFormatter $date_formatter) { + public function __construct(FloodInterface $flood, DateFormatter $date_formatter, RendererInterface $renderer) { $this->flood = $flood; $this->dateFormatter = $date_formatter; + $this->renderer = $renderer; } /** @@ -56,7 +67,8 @@ public function __construct(FloodInterface $flood, DateFormatter $date_formatter public static function create(ContainerInterface $container) { return new static( $container->get('flood'), - $container->get('date.formatter') + $container->get('date.formatter'), + $container->get('renderer') ); } @@ -106,7 +118,7 @@ public function contactSitePage(ContactFormInterface $contact_form = NULL) { $form = $this->entityFormBuilder()->getForm($message); $form['#title'] = SafeMarkup::checkPlain($contact_form->label()); - $form['#cache']['tags'] = Cache::mergeTags(isset($form['#cache']['tags']) ? $form['#cache']['tags'] : [], $config->getCacheTags()); + $this->renderer->addDependency($form, $config); return $form; } diff --git a/core/modules/forum/src/Controller/ForumController.php b/core/modules/forum/src/Controller/ForumController.php index 18d7325ef627d24718f57c84330d065637eca466..82165fff8e852ccbf5c89606af086d619b725dff 100644 --- a/core/modules/forum/src/Controller/ForumController.php +++ b/core/modules/forum/src/Controller/ForumController.php @@ -11,6 +11,7 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\EntityAccessControlHandlerInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\forum\ForumManagerInterface; @@ -66,6 +67,13 @@ class ForumController extends ControllerBase { */ protected $nodeTypeStorage; + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + /** * Constructs a ForumController object. * @@ -83,8 +91,10 @@ class ForumController extends ControllerBase { * Array of active fields on the site. * @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage * Node type storage handler. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. */ - public function __construct(ForumManagerInterface $forum_manager, VocabularyStorageInterface $vocabulary_storage, TermStorageInterface $term_storage, AccountInterface $current_user, EntityAccessControlHandlerInterface $node_access, array $field_map, EntityStorageInterface $node_type_storage) { + public function __construct(ForumManagerInterface $forum_manager, VocabularyStorageInterface $vocabulary_storage, TermStorageInterface $term_storage, AccountInterface $current_user, EntityAccessControlHandlerInterface $node_access, array $field_map, EntityStorageInterface $node_type_storage, RendererInterface $renderer) { $this->forumManager = $forum_manager; $this->vocabularyStorage = $vocabulary_storage; $this->termStorage = $term_storage; @@ -92,6 +102,7 @@ public function __construct(ForumManagerInterface $forum_manager, VocabularyStor $this->nodeAccess = $node_access; $this->fieldMap = $field_map; $this->nodeTypeStorage = $node_type_storage; + $this->renderer = $renderer; } /** @@ -107,7 +118,8 @@ public static function create(ContainerInterface $container) { $container->get('current_user'), $entity_manager->getAccessControlHandler('node'), $entity_manager->getFieldMap(), - $entity_manager->getStorage('node_type') + $entity_manager->getStorage('node_type'), + $container->get('renderer') ); } @@ -191,7 +203,7 @@ protected function build($forums, TermInterface $term, $topics = array(), $paren if (empty($term->forum_container->value)) { $build['#attached']['feed'][] = array('taxonomy/term/' . $term->id() . '/feed', 'RSS - ' . $term->getName()); } - $build['#cache']['tags'] = Cache::mergeTags(isset($build['#cache']['tags']) ? $build['#cache']['tags'] : [], $config->getCacheTags()); + $this->renderer->addDependency($build, $config); return [ 'action' => $this->buildActionLinks($config->get('vocabulary'), $term), diff --git a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php index 9c4c63b59df8fd19c7eee008877e77ef793465c0..fd0be68125f34a72f57270963b7bc457c58272f0 100644 --- a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php +++ b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php @@ -119,7 +119,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { * The access result. */ public function linkIsResettable(MenuLinkInterface $menu_link_plugin) { - return AccessResult::allowedIf($menu_link_plugin->isResettable())->setCacheable(FALSE); + return AccessResult::allowedIf($menu_link_plugin->isResettable())->setCacheMaxAge(0); } } diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php index 5c661900468560404cb22c3bcec6149eec2aae16..218524551db2cea02e17bb9b532dd08310e33c0c 100644 --- a/core/modules/node/src/NodeGrantDatabaseStorage.php +++ b/core/modules/node/src/NodeGrantDatabaseStorage.php @@ -108,7 +108,7 @@ public function access(NodeInterface $node, $operation, $langcode, AccountInterf $set_cacheability = function (AccessResult $access_result) use ($operation) { $access_result->addCacheContexts(['user.node_grants:' . $operation]); if ($operation !== 'view') { - $access_result->setCacheable(FALSE); + $access_result->setCacheMaxAge(0); } return $access_result; }; diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module index b44117ecdd0c0927aad6c2a7e4f59ad7185b9e2e..e8d1d3624adee3b4301d1cab71ed7785d056316a 100644 --- a/core/modules/node/tests/modules/node_access_test/node_access_test.module +++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module @@ -148,8 +148,8 @@ function node_access_test_node_access(\Drupal\node\NodeInterface $node, $op, \Dr $secret_catalan = \Drupal::state()->get('node_access_test_secret_catalan') ?: 0; if ($secret_catalan && $langcode == 'ca') { // Make all Catalan content secret. - return AccessResult::forbidden()->setCacheable(FALSE); + return AccessResult::forbidden()->setCacheMaxAge(0); } // No opinion. - return AccessResult::neutral()->setCacheable(FALSE); + return AccessResult::neutral()->setCacheMaxAge(0); } diff --git a/core/modules/rest/src/Access/CSRFAccessCheck.php b/core/modules/rest/src/Access/CSRFAccessCheck.php index 2e39c61d1b49de80a09330cb2412e967b8e2b45d..5db08fe7a7a394232a0284180101126025c5a1c1 100644 --- a/core/modules/rest/src/Access/CSRFAccessCheck.php +++ b/core/modules/rest/src/Access/CSRFAccessCheck.php @@ -83,11 +83,11 @@ public function access(Request $request, AccountInterface $account) { ) { $csrf_token = $request->headers->get('X-CSRF-Token'); if (!\Drupal::csrfToken()->validate($csrf_token, 'rest')) { - return AccessResult::forbidden()->setCacheable(FALSE); + return AccessResult::forbidden()->setCacheMaxAge(0); } } // Let other access checkers decide if the request is legit. - return AccessResult::allowed()->setCacheable(FALSE); + return AccessResult::allowed()->setCacheMaxAge(0); } } diff --git a/core/modules/system/src/Access/CronAccessCheck.php b/core/modules/system/src/Access/CronAccessCheck.php index acb1401bea92350131ba65545c16f6d0b155f368..fa555da17b174283d4c281cafce8a4644d106ff6 100644 --- a/core/modules/system/src/Access/CronAccessCheck.php +++ b/core/modules/system/src/Access/CronAccessCheck.php @@ -27,12 +27,12 @@ class CronAccessCheck implements AccessInterface { public function access($key) { if ($key != \Drupal::state()->get('system.cron_key')) { \Drupal::logger('cron')->notice('Cron could not run because an invalid key was used.'); - return AccessResult::forbidden()->setCacheable(FALSE); + return AccessResult::forbidden()->setCacheMaxAge(0); } elseif (\Drupal::state()->get('system.maintenance_mode')) { \Drupal::logger('cron')->notice('Cron could not run because the site is in maintenance mode.'); - return AccessResult::forbidden()->setCacheable(FALSE); + return AccessResult::forbidden()->setCacheMaxAge(0); } - return AccessResult::allowed()->setCacheable(FALSE); + return AccessResult::allowed()->setCacheMaxAge(0); } } diff --git a/core/modules/system/src/Access/DbUpdateAccessCheck.php b/core/modules/system/src/Access/DbUpdateAccessCheck.php index 32e7ccf635c3e86fc4a1f57cff6f7af06d0c60ab..f78199185141adbb630bde77c087bfad72380c57 100644 --- a/core/modules/system/src/Access/DbUpdateAccessCheck.php +++ b/core/modules/system/src/Access/DbUpdateAccessCheck.php @@ -29,7 +29,7 @@ class DbUpdateAccessCheck implements AccessInterface { public function access(AccountInterface $account) { // Allow the global variable in settings.php to override the access check. if (Settings::get('update_free_access')) { - return AccessResult::allowed()->setCacheable(FALSE); + return AccessResult::allowed()->setCacheMaxAge(0); } if ($account->hasPermission('administer software updates')) { diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php index 6e87d2dff28804d71f2aa0edc5b16a0b9c0f0bd7..e44299bab491945290aec2ea943d22f0bad91b2a 100644 --- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php +++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php @@ -58,7 +58,7 @@ public function testEntityViewBuilderCache() { // Test that new entities (before they are saved for the first time) do not // generate a cache entry. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full'); - $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts'], 'The render array element of new (unsaved) entities is not cached, but does have cache tags set.'); + $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'The render array element of new (unsaved) entities is not cached, but does have cache tags set.'); // Get a fully built entity view render array. $entity_test->save(); @@ -163,17 +163,17 @@ public function testEntityViewBuilderCacheToggling() { // Test a view mode in default conditions: render caching is enabled for // the entity type and the view mode. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full'); - $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'keys', 'bin'] , 'A view mode with render cache enabled has the correct output (cache tags, keys, contexts and bin).'); + $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age', 'keys', 'bin'] , 'A view mode with render cache enabled has the correct output (cache tags, keys, contexts, max-age and bin).'); // Test that a view mode can opt out of render caching. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'test'); - $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts'], 'A view mode with render cache disabled has the correct output (only cache tags).'); + $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'A view mode with render cache disabled has the correct output (only cache tags, contexts and max-age).'); // Test that an entity type can opt out of render caching completely. $entity_test_no_cache = $this->createTestEntity('entity_test_label'); $entity_test_no_cache->save(); $build = $this->container->get('entity.manager')->getViewBuilder('entity_test_label')->view($entity_test_no_cache, 'full'); - $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts'], 'An entity type can opt out of render caching regardless of view mode configuration, but always has cache tags set.'); + $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'An entity type can opt out of render caching regardless of view mode configuration, but always has cache tags, contexts and max-age set.'); } /** diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index df80c1a9ddbea4c19902d330d384db91ac87fa36..174529424b0a9f03fe8186208b578d8d61d445e8 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -552,7 +552,7 @@ function entity_test_entity_access(EntityInterface $entity, $operation, AccountI // Uncacheable because the access result depends on a State key-value pair and // might therefore change at any time. $condition = \Drupal::state()->get("entity_test_entity_access.{$operation}." . $entity->id(), FALSE); - return AccessResult::allowedIf($condition)->setCacheable(FALSE); + return AccessResult::allowedIf($condition)->setCacheMaxAge(0); } /** diff --git a/core/modules/update/src/Access/UpdateManagerAccessCheck.php b/core/modules/update/src/Access/UpdateManagerAccessCheck.php index d057ce3290bd0557df2fa0852a567910e53c77bf..df52c5c37314cd64b0418559c83d6b0bc39639c0 100644 --- a/core/modules/update/src/Access/UpdateManagerAccessCheck.php +++ b/core/modules/update/src/Access/UpdateManagerAccessCheck.php @@ -42,7 +42,7 @@ public function __construct(Settings $settings) { public function access() { // Uncacheable because the access result depends on a Settings key-value // pair, and can therefore change at any time. - return AccessResult::allowedIf($this->settings->get('allow_authorize_operations', TRUE))->setCacheable(FALSE); + return AccessResult::allowedIf($this->settings->get('allow_authorize_operations', TRUE))->setCacheMaxAge(0); } } diff --git a/core/modules/user/src/Form/UserLoginForm.php b/core/modules/user/src/Form/UserLoginForm.php index a0c08768d8af4bb2eb1c6fef585f50d9cfec0b62..76736122f300f6946bb148a60247e69a20f7b5ef 100644 --- a/core/modules/user/src/Form/UserLoginForm.php +++ b/core/modules/user/src/Form/UserLoginForm.php @@ -11,6 +11,7 @@ use Drupal\Core\Flood\FloodInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\RendererInterface; use Drupal\user\UserAuthInterface; use Drupal\user\UserStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -41,6 +42,13 @@ class UserLoginForm extends FormBase { */ protected $userAuth; + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + /** * Constructs a new UserLoginForm. * @@ -50,11 +58,14 @@ class UserLoginForm extends FormBase { * The user storage. * @param \Drupal\user\UserAuthInterface $user_auth * The user authentication object. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. */ - public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, UserAuthInterface $user_auth) { + public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, UserAuthInterface $user_auth, RendererInterface $renderer) { $this->flood = $flood; $this->userStorage = $user_storage; $this->userAuth = $user_auth; + $this->renderer = $renderer; } /** @@ -64,7 +75,8 @@ public static function create(ContainerInterface $container) { return new static( $container->get('flood'), $container->get('entity.manager')->getStorage('user'), - $container->get('user.auth') + $container->get('user.auth'), + $container->get('renderer') ); } @@ -112,7 +124,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['#validate'][] = '::validateAuthentication'; $form['#validate'][] = '::validateFinal'; - $form['#cache']['tags'] = Cache::mergeTags(isset($form['#cache']['tags']) ? $form['#cache']['tags'] : [], $config->getCacheTags()); + $this->renderer->addDependency($form, $config); return $form; } diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 5aeac875cfa2357fc8ee2fefe87da3dc692d056b..31d29b38278bdaaa8490b6eba958442321d6fc1f 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -1208,6 +1208,13 @@ public function getDependencies() { return $this->storage->getDependencies(); } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return $this->storage->getCacheContexts(); + } + /** * {@inheritdoc} */ @@ -1215,6 +1222,13 @@ public function getCacheTags() { return $this->storage->getCacheTags(); } + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return $this->storage->getCacheMaxAge(); + } + /** * {@inheritdoc} */ diff --git a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php index 85028fdd7096a898d831e4a6d8b5e2a1a1e9a1b0..c549a3c373149c6d9aefd65f5798411ddacd4f76 100644 --- a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php +++ b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php @@ -11,7 +11,7 @@ use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Access\AccessResultNeutral; use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\CacheableInterface; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Tests\UnitTestCase; /** @@ -21,11 +21,8 @@ class AccessResultTest extends UnitTestCase { protected function assertDefaultCacheability(AccessResult $access) { - $this->assertTrue($access->isCacheable()); - $this->assertSame([], $access->getCacheKeys()); $this->assertSame([], $access->getCacheContexts()); $this->assertSame([], $access->getCacheTags()); - $this->assertSame('default', $access->getCacheBin()); $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge()); } @@ -34,7 +31,6 @@ protected function assertDefaultCacheability(AccessResult $access) { * * @covers ::__construct * @covers ::neutral - * @covers ::getCacheBin */ public function testConstruction() { $verify = function (AccessResult $access) { @@ -297,16 +293,6 @@ public function testOrIf() { $this->assertDefaultCacheability($access); } - /** - * @covers ::setCacheable - * @covers ::isCacheable - */ - public function testCacheable() { - $this->assertTrue(AccessResult::neutral()->isCacheable()); - $this->assertTrue(AccessResult::neutral()->setCacheable(TRUE)->isCacheable()); - $this->assertFalse(AccessResult::neutral()->setCacheable(FALSE)->isCacheable()); - } - /** * @covers ::setCacheMaxAge * @covers ::getCacheMaxAge @@ -329,8 +315,6 @@ public function testCacheContexts() { $this->assertFalse($access->isAllowed()); $this->assertFalse($access->isForbidden()); $this->assertTrue($access->isNeutral()); - $this->assertTrue($access->isCacheable()); - $this->assertSame('default', $access->getCacheBin()); $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge()); $this->assertSame($contexts, $access->getCacheContexts()); $this->assertSame([], $access->getCacheTags()); @@ -408,8 +392,6 @@ public function testCacheTags() { $this->assertFalse($access->isAllowed()); $this->assertFalse($access->isForbidden()); $this->assertTrue($access->isNeutral()); - $this->assertTrue($access->isCacheable()); - $this->assertSame('default', $access->getCacheBin()); $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge()); $this->assertSame([], $access->getCacheContexts()); $this->assertSame($tags, $access->getCacheTags()); @@ -471,20 +453,16 @@ public function testInheritCacheability() { $access = AccessResult::allowed(); $other = AccessResult::allowed()->setCacheMaxAge(1500)->cachePerPermissions()->addCacheTags(['node:20011988']); $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult); - $this->assertTrue($access->isCacheable()); $this->assertSame(['user.permissions'], $access->getCacheContexts()); $this->assertSame(['node:20011988'], $access->getCacheTags()); - $this->assertSame('default', $access->getCacheBin()); $this->assertSame(1500, $access->getCacheMaxAge()); // andIf(); 1st has custom tags, max-age, 2nd has custom contexts and max-age. $access = AccessResult::allowed()->cachePerUser()->setCacheMaxAge(43200); $other = AccessResult::forbidden()->addCacheTags(['node:14031991'])->setCacheMaxAge(86400); $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult); - $this->assertTrue($access->isCacheable()); $this->assertSame(['user'], $access->getCacheContexts()); $this->assertSame(['node:14031991'], $access->getCacheTags()); - $this->assertSame('default', $access->getCacheBin()); $this->assertSame(43200, $access->getCacheMaxAge()); } @@ -495,15 +473,17 @@ public function testInheritCacheability() { * every single bit of cacheability metadata, which would lead to a mind- * boggling number of permutations, in this test, we only consider the * permutations of all pairs of the following set: - * 1. Allowed, implements cacheable interface, is cacheable - * 2. Allowed, implements cacheable interface, is not cacheable - * 3. Allowed, does not implement cacheable interface (hence not cacheable) - * 4. Forbidden, implements cacheable interface, is cacheable - * 5. Forbidden, implements cacheable interface, is not cacheable - * 6. Forbidden, does not implement cacheable interface (hence not cacheable) - * 7. Neutral, implements cacheable interface, is cacheable - * 8. Neutral, implements cacheable interface, is not cacheable - * 9. Neutral, does not implement cacheable interface (hence not cacheable) + * 1. Allowed, implements CDI, is cacheable + * 2. Allowed, implements CDI, is not cacheable + * 3. Allowed, does not implement CDI (hence not cacheable) + * 4. Forbidden, implements CDI, is cacheable + * 5. Forbidden, implements CDI, is not cacheable + * 6. Forbidden, does not implement CDI (hence not cacheable) + * 7. Neutral, implements CDI, is cacheable + * 8. Neutral, implements CDI, is not cacheable + * 9. Neutral, does not implement CDI (hence not cacheable) + * + * (Where "CDI" is CacheableDependencyInterface.) * * This leads to 72 permutations (9!/(9-2)! = 9*8 = 72) per operation. There * are two operations to test (AND and OR), so that leads to a grand total of @@ -517,8 +497,8 @@ public function testInheritCacheability() { * 2. Any operation yields an access result object that is of the same class * (implementation) as the first operand. This is because operations are * invoked on the first operand. Therefore, if the first implementation - * does not implement CacheableInterface, then the result won't either. - * This is the case for items 3, 6 and 9 in the set above. + * does not implement CacheableDependencyInterface, then the result won't + * either. This is the case for items 3, 6 and 9 in the set above. */ public function andOrCacheabilityPropagationProvider() { // ct: cacheable=true, cf: cacheable=false, un: uncacheable. @@ -526,23 +506,23 @@ public function andOrCacheabilityPropagationProvider() { // test UncacheableTestAccessResult, not AccessResult. However, we // definitely want to verify that AccessResult's orIf() and andIf() methods // work correctly when given an AccessResultInterface implementation that - // does not implement CacheableInterface, and we want to test the full gamut - // of permutations, so that's not a problem. + // does not implement CacheableDependencyInterface, and we want to test the + // full gamut of permutations, so that's not a problem. $allowed_ct = AccessResult::allowed(); - $allowed_cf = AccessResult::allowed()->setCacheable(FALSE); + $allowed_cf = AccessResult::allowed()->setCacheMaxAge(0); $allowed_un = new UncacheableTestAccessResult('ALLOWED'); $forbidden_ct = AccessResult::forbidden(); - $forbidden_cf = AccessResult::forbidden()->setCacheable(FALSE); + $forbidden_cf = AccessResult::forbidden()->setCacheMaxAge(0); $forbidden_un = new UncacheableTestAccessResult('FORBIDDEN'); $neutral_ct = AccessResult::neutral(); - $neutral_cf = AccessResult::neutral()->setCacheable(FALSE); + $neutral_cf = AccessResult::neutral()->setCacheMaxAge(0); $neutral_un = new UncacheableTestAccessResult('NEUTRAL'); // Structure: // - First column: first access result. // - Second column: operator ('OR' or 'AND'). // - Third column: second access result. - // - Fourth column: whether the result implements CacheableInterface + // - Fourth column: whether result implements CacheableDependencyInterface // - Fifth column: whether the result is cacheable (if column 4 is TRUE) return [ // Allowed (ct) OR allowed (ct,cf,un). @@ -795,7 +775,7 @@ public function andOrCacheabilityPropagationProvider() { * * @dataProvider andOrCacheabilityPropagationProvider */ - public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_interface, $is_cacheable) { + public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) { if ($op === 'OR') { $result = $first->orIf($second); } @@ -805,14 +785,14 @@ public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $ else { throw new \LogicException('Invalid operator specified'); } - if ($implements_cacheable_interface) { - $this->assertTrue($result instanceof CacheableInterface, 'Result is an instance of CacheableInterface.'); - if ($result instanceof CacheableInterface) { - $this->assertSame($is_cacheable, $result->isCacheable(), 'isCacheable() matches expectations.'); + if ($implements_cacheable_dependency_interface) { + $this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.'); + if ($result instanceof CacheableDependencyInterface) { + $this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.'); } } else { - $this->assertFalse($result instanceof CacheableInterface, 'Result is not an instance of CacheableInterface.'); + $this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.'); } } diff --git a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php index 023f0e658821b22a38b9c23d51a777fb35401747..b9104e2e9c9104c45b3a534b8d285734541d3e3d 100644 --- a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php +++ b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php @@ -66,7 +66,7 @@ public function testAccessTokenPass() { $route = new Route('/test-path/{node}', array(), array('_csrf_token' => 'TRUE')); $request = Request::create('/test-path/42?token=test_query'); - $this->assertEquals(AccessResult::allowed()->setCacheable(FALSE), $this->accessCheck->access($route, $request, $this->routeMatch)); + $this->assertEquals(AccessResult::allowed()->setCacheMaxAge(0), $this->accessCheck->access($route, $request, $this->routeMatch)); } /** @@ -85,7 +85,7 @@ public function testAccessTokenFail() { $route = new Route('/test-path', array(), array('_csrf_token' => 'TRUE')); $request = Request::create('/test-path?token=test_query'); - $this->assertEquals(AccessResult::forbidden()->setCacheable(FALSE), $this->accessCheck->access($route, $request, $this->routeMatch)); + $this->assertEquals(AccessResult::forbidden()->setCacheMaxAge(0), $this->accessCheck->access($route, $request, $this->routeMatch)); } } diff --git a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php index f6c4cfb5db70714528b0c33efeb8783092d398ff..526b7ee899e26126b862981dd8efc0dec8c61b88 100644 --- a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php +++ b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\Core\Render; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Tests\UnitTestCase; use Drupal\Core\Render\Element; @@ -131,4 +132,35 @@ public function providerTestCreateFromRenderArray() { return $data; } + /** + * @covers ::createFromObject + * @dataProvider providerTestCreateFromObject + */ + public function testCreateFromObject(CacheableDependencyInterface $object, BubbleableMetadata $expected) { + $this->assertEquals($expected, BubbleableMetadata::createFromObject($object)); + } + + /** + * Provides test data for createFromObject(). + * + * @return array + */ + public function providerTestCreateFromObject() { + $data = []; + + $empty_metadata = new BubbleableMetadata(); + $nonempty_metadata = new BubbleableMetadata(); + $nonempty_metadata->setCacheContexts(['qux']) + ->setCacheTags(['foo:bar']) + ->setCacheMaxAge(600); + + $empty_cacheable_object = new TestCacheableDependency([], [], Cache::PERMANENT); + $nonempty_cacheable_object = new TestCacheableDependency(['qux'], ['foo:bar'], 600); + + $data[] = [$empty_cacheable_object, $empty_metadata]; + $data[] = [$nonempty_cacheable_object, $nonempty_metadata]; + + return $data; + } + } diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php index 7ac67b1b4b90d5a36f3750dde8f74a161b85edbb..fb757f7dfd1a2b8db5d39e6fc65f782dd5672a93 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\Core\Render; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; @@ -620,6 +621,69 @@ public function providerTestRenderCacheMaxAge() { ]; } + /** + * @covers ::addDependency + * + * @dataProvider providerTestAddDependency + */ + public function testAddDependency(array $build, CacheableDependencyInterface $object, array $expected) { + $this->renderer->addDependency($build, $object); + $this->assertEquals($build, $expected); + } + + public function providerTestAddDependency() { + return [ + // Empty render array, typical default cacheability. + [ + [], + new TestCacheableDependency([], [], Cache::PERMANENT), + [ + '#cache' => [ + 'contexts' => [], + 'tags' => [], + 'max-age' => Cache::PERMANENT, + ], + '#attached' => [], + '#post_render_cache' => [], + ], + ], + // Empty render array, some cacheability. + [ + [], + new TestCacheableDependency(['user.roles'], ['foo'], Cache::PERMANENT), + [ + '#cache' => [ + 'contexts' => ['user.roles'], + 'tags' => ['foo'], + 'max-age' => Cache::PERMANENT, + ], + '#attached' => [], + '#post_render_cache' => [], + ], + ], + // Cacheable render array, some cacheability. + [ + [ + '#cache' => [ + 'contexts' => ['theme'], + 'tags' => ['bar'], + 'max-age' => 600, + ] + ], + new TestCacheableDependency(['user.roles'], ['foo'], Cache::PERMANENT), + [ + '#cache' => [ + 'contexts' => ['theme', 'user.roles'], + 'tags' => ['bar', 'foo'], + 'max-age' => 600, + ], + '#attached' => [], + '#post_render_cache' => [], + ], + ], + ]; + } + } class TestAccessClass { diff --git a/core/tests/Drupal/Tests/Core/Render/TestCacheableDependency.php b/core/tests/Drupal/Tests/Core/Render/TestCacheableDependency.php new file mode 100644 index 0000000000000000000000000000000000000000..8980e8934042df9c51845b0d3a919be1c648d6ad --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/TestCacheableDependency.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\Tests\Core\Render; + +use Drupal\Core\Cache\CacheableDependencyInterface; + +/** + * Cacheable dependency object for use in tests. + */ +class TestCacheableDependency implements CacheableDependencyInterface { + + public function __construct(array $contexts, array $tags, $max_age) { + $this->contexts = $contexts; + $this->tags = $tags; + $this->maxAge = $max_age; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return $this->contexts; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return $this->tags; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return $this->maxAge; + } + +}