Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
D
drupal
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Custom Issue Tracker
Custom Issue Tracker
Labels
Merge Requests
222
Merge Requests
222
Requirements
Requirements
List
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Analytics
Analytics
Code Review
Insights
Issue
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
project
drupal
Commits
fe04699e
Commit
fe04699e
authored
Mar 03, 2015
by
catch
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Issue
#2429257
by Wim Leers, Fabianx, effulgentsia: Bubble cache contexts
parent
9dfa950c
Changes
25
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
947 additions
and
40 deletions
+947
-40
core/lib/Drupal/Core/Cache/Cache.php
core/lib/Drupal/Core/Cache/Cache.php
+20
-0
core/lib/Drupal/Core/Cache/CacheContexts.php
core/lib/Drupal/Core/Cache/CacheContexts.php
+1
-0
core/lib/Drupal/Core/Entity/EntityViewBuilder.php
core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+0
-3
core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LanguageFormatter.php
...e/Field/Plugin/Field/FieldFormatter/LanguageFormatter.php
+3
-0
core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
.../Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
+8
-1
core/lib/Drupal/Core/Render/BubbleableMetadata.php
core/lib/Drupal/Core/Render/BubbleableMetadata.php
+14
-1
core/lib/Drupal/Core/Render/Renderer.php
core/lib/Drupal/Core/Render/Renderer.php
+184
-4
core/lib/Drupal/Core/Render/RendererInterface.php
core/lib/Drupal/Core/Render/RendererInterface.php
+45
-7
core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
...c/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
+2
-0
core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
.../Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
+5
-0
core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
...rc/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
+8
-1
core/modules/filter/src/FilterProcessResult.php
core/modules/filter/src/FilterProcessResult.php
+44
-0
core/modules/filter/src/Tests/FilterAPITest.php
core/modules/filter/src/Tests/FilterAPITest.php
+9
-0
core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestCacheContexts.php
...filter_test/src/Plugin/Filter/FilterTestCacheContexts.php
+35
-0
core/modules/node/src/Tests/NodeCacheTagsTest.php
core/modules/node/src/Tests/NodeCacheTagsTest.php
+7
-0
core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
...dules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
+90
-4
core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php
...ystem/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php
+7
-3
core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
.../src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
+2
-0
core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
.../src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
+2
-0
core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php
core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php
+1
-1
core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
...tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
+10
-4
core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php
core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php
+398
-2
core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php
.../Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php
+32
-8
core/tests/Drupal/Tests/Core/Render/RendererTest.php
core/tests/Drupal/Tests/Core/Render/RendererTest.php
+4
-1
core/tests/Drupal/Tests/Core/Render/RendererTestBase.php
core/tests/Drupal/Tests/Core/Render/RendererTestBase.php
+16
-0
No files found.
core/lib/Drupal/Core/Cache/Cache.php
View file @
fe04699e
...
...
@@ -21,6 +21,26 @@ class Cache {
*/
const
PERMANENT
=
CacheBackendInterface
::
CACHE_PERMANENT
;
/**
* Merges arrays of cache contexts and removes duplicates.
*
* @param string[] …
* Arrays of cache contexts to merge.
*
* @return string[]
* The merged array of cache contexts.
*/
public
static
function
mergeContexts
()
{
$cache_context_arrays
=
func_get_args
();
$cache_contexts
=
[];
foreach
(
$cache_context_arrays
as
$contexts
)
{
$cache_contexts
=
array_merge
(
$cache_contexts
,
$contexts
);
}
$cache_contexts
=
array_unique
(
$cache_contexts
);
sort
(
$cache_contexts
);
return
$cache_contexts
;
}
/**
* Merges arrays of cache tags and removes duplicates.
*
...
...
core/lib/Drupal/Core/Cache/CacheContexts.php
View file @
fe04699e
...
...
@@ -105,6 +105,7 @@ public function getLabels($include_calculated_cache_contexts = FALSE) {
* @throws \InvalidArgumentException
*/
public
function
convertTokensToKeys
(
array
$context_tokens
)
{
sort
(
$context_tokens
);
$keys
=
[];
foreach
(
static
::
parseTokens
(
$context_tokens
)
as
$context
)
{
list
(
$context_id
,
$parameter
)
=
$context
;
...
...
core/lib/Drupal/Core/Entity/EntityViewBuilder.php
View file @
fe04699e
...
...
@@ -185,9 +185,6 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
'contexts'
=>
array
(
'theme'
,
'user.roles'
,
// @todo Move this out of here and into field formatters that depend
// on the timezone. Blocked on https://drupal.org/node/2099137.
'timezone'
,
),
'bin'
=>
$this
->
cacheBin
,
);
...
...
core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LanguageFormatter.php
View file @
fe04699e
...
...
@@ -30,6 +30,9 @@ class LanguageFormatter extends FormatterBase {
public
function
viewElements
(
FieldItemListInterface
$items
)
{
$elements
=
array
();
// The 'language' cache context is not necessary, because what is printed
// here is the language's name in English, not in the language of the
// response.
foreach
(
$items
as
$delta
=>
$item
)
{
$elements
[
$delta
]
=
array
(
'#markup'
=>
$item
->
language
?
String
::
checkPlain
(
$item
->
language
->
getName
())
:
''
);
}
...
...
core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
View file @
fe04699e
...
...
@@ -31,7 +31,14 @@ public function viewElements(FieldItemListInterface $items) {
$elements
=
array
();
foreach
(
$items
as
$delta
=>
$item
)
{
$elements
[
$delta
]
=
array
(
'#markup'
=>
format_date
(
$item
->
value
));
$elements
[
$delta
]
=
[
'#cache'
=>
[
'contexts'
=>
[
'timezone'
,
],
],
'#markup'
=>
format_date
(
$item
->
value
)
];
}
return
$elements
;
...
...
core/lib/Drupal/Core/Render/BubbleableMetadata.php
View file @
fe04699e
...
...
@@ -17,6 +17,13 @@
*/
class
BubbleableMetadata
{
/**
* Cache contexts.
*
* @var string[]
*/
protected
$contexts
;
/**
* Cache tags.
*
...
...
@@ -41,6 +48,8 @@ class BubbleableMetadata {
/**
* Constructs a BubbleableMetadata value object.
*
* @param string[] $contexts
* An array of cache contexts.
* @param string[] $tags
* An array of cache tags.
* @param array $attached
...
...
@@ -48,7 +57,8 @@ class BubbleableMetadata {
* @param array $post_render_cache
* An array of #post_render_cache metadata.
*/
public
function
__construct
(
array
$tags
=
[],
array
$attached
=
[],
array
$post_render_cache
=
[])
{
public
function
__construct
(
array
$contexts
=
[],
array
$tags
=
[],
array
$attached
=
[],
array
$post_render_cache
=
[])
{
$this
->
contexts
=
$contexts
;
$this
->
tags
=
$tags
;
$this
->
attached
=
$attached
;
$this
->
postRenderCache
=
$post_render_cache
;
...
...
@@ -69,6 +79,7 @@ public function __construct(array $tags = [], array $attached = [], array $post_
*/
public
function
merge
(
BubbleableMetadata
$other
)
{
$result
=
new
BubbleableMetadata
();
$result
->
contexts
=
Cache
::
mergeContexts
(
$this
->
contexts
,
$other
->
contexts
);
$result
->
tags
=
Cache
::
mergeTags
(
$this
->
tags
,
$other
->
tags
);
$result
->
attached
=
Renderer
::
mergeAttachments
(
$this
->
attached
,
$other
->
attached
);
$result
->
postRenderCache
=
NestedArray
::
mergeDeep
(
$this
->
postRenderCache
,
$other
->
postRenderCache
);
...
...
@@ -82,6 +93,7 @@ public function merge(BubbleableMetadata $other) {
* A render array.
*/
public
function
applyTo
(
array
&
$build
)
{
$build
[
'#cache'
][
'contexts'
]
=
$this
->
contexts
;
$build
[
'#cache'
][
'tags'
]
=
$this
->
tags
;
$build
[
'#attached'
]
=
$this
->
attached
;
$build
[
'#post_render_cache'
]
=
$this
->
postRenderCache
;
...
...
@@ -97,6 +109,7 @@ public function applyTo(array &$build) {
*/
public
static
function
createFromRenderArray
(
array
$build
)
{
$meta
=
new
static
();
$meta
->
contexts
=
(
isset
(
$build
[
'#cache'
][
'contexts'
]))
?
$build
[
'#cache'
][
'contexts'
]
:
[];
$meta
->
tags
=
(
isset
(
$build
[
'#cache'
][
'tags'
]))
?
$build
[
'#cache'
][
'tags'
]
:
[];
$meta
->
attached
=
(
isset
(
$build
[
'#attached'
]))
?
$build
[
'#attached'
]
:
[];
$meta
->
postRenderCache
=
(
isset
(
$build
[
'#post_render_cache'
]))
?
$build
[
'#post_render_cache'
]
:
[];
...
...
core/lib/Drupal/Core/Render/Renderer.php
View file @
fe04699e
...
...
@@ -166,6 +166,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// Try to fetch the prerendered element from cache, run any
// #post_render_cache callbacks and return the final markup.
$pre_bubbling_cid
=
NULL
;
if
(
isset
(
$elements
[
'#cache'
]))
{
$cached_element
=
$this
->
cacheGet
(
$elements
);
if
(
$cached_element
!==
FALSE
)
{
...
...
@@ -185,6 +186,15 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$this
->
bubbleStack
();
return
$elements
[
'#markup'
];
}
else
{
// Two-tier caching: set pre-bubbling cache ID, if this element is
// cacheable..
// @see ::cacheGet()
// @see ::cacheSet()
if
(
$this
->
requestStack
->
getCurrentRequest
()
->
isMethodSafe
()
&&
$cid
=
$this
->
createCacheID
(
$elements
))
{
$pre_bubbling_cid
=
$cid
;
}
}
}
// If the default values for this element have not been loaded yet, populate
...
...
@@ -206,6 +216,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
}
// Defaults for bubbleable rendering metadata.
$elements
[
'#cache'
][
'contexts'
]
=
isset
(
$elements
[
'#cache'
][
'contexts'
])
?
$elements
[
'#cache'
][
'contexts'
]
:
array
();
$elements
[
'#cache'
][
'tags'
]
=
isset
(
$elements
[
'#cache'
][
'tags'
])
?
$elements
[
'#cache'
][
'tags'
]
:
array
();
$elements
[
'#attached'
]
=
isset
(
$elements
[
'#attached'
])
?
$elements
[
'#attached'
]
:
array
();
$elements
[
'#post_render_cache'
]
=
isset
(
$elements
[
'#post_render_cache'
])
?
$elements
[
'#post_render_cache'
]
:
array
();
...
...
@@ -347,7 +358,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// Cache the processed element if #cache is set, and the metadata necessary
// to generate a cache ID is present.
if
(
isset
(
$elements
[
'#cache'
])
&&
(
isset
(
$elements
[
'#cache'
][
'keys'
])
||
isset
(
$elements
[
'#cache'
][
'cid'
])))
{
$this
->
cacheSet
(
$elements
);
$this
->
cacheSet
(
$elements
,
$pre_bubbling_cid
);
}
// Only when we're in a root (non-recursive) drupal_render() call,
...
...
@@ -495,6 +506,12 @@ protected function cacheGet(array $elements) {
if
(
!
empty
(
$cid
)
&&
(
$cache_bin
=
$this
->
cacheFactory
->
get
(
$bin
))
&&
$cache
=
$cache_bin
->
get
(
$cid
))
{
$cached_element
=
$cache
->
data
;
// Two-tier caching: redirect to actual (post-bubbling) cache item.
// @see ::doRender()
// @see ::cacheSet()
if
(
isset
(
$cached_element
[
'#cache_redirect'
]))
{
return
$this
->
cacheGet
(
$cached_element
);
}
// Return the cached element.
return
$cached_element
;
}
...
...
@@ -508,13 +525,15 @@ protected function cacheGet(array $elements) {
*
* @param array $elements
* A renderable array.
* @param string|null $pre_bubbling_cid
* The pre-bubbling cache ID.
*
* @return bool|null
* Returns FALSE if no cache item could be created, NULL otherwise.
*
* @see ::getFromCache()
*/
protected
function
cacheSet
(
array
&
$elements
)
{
protected
function
cacheSet
(
array
&
$elements
,
$pre_bubbling_cid
)
{
// Form submissions rely on the form being built during the POST request,
// and render caching of forms prevents this from happening.
// @todo remove the isMethodSafe() check when
...
...
@@ -525,14 +544,174 @@ protected function cacheSet(array &$elements) {
$data
=
$this
->
getCacheableRenderArray
(
$elements
);
// Cache tags are cached, but we also want to assoc
ai
te the "rendered" cache
// Cache tags are cached, but we also want to assoc
ia
te the "rendered" cache
// tag. This allows us to invalidate the entire render cache, regardless of
// the cache bin.
$data
[
'#cache'
][
'tags'
][]
=
'rendered'
;
$bin
=
isset
(
$elements
[
'#cache'
][
'bin'
])
?
$elements
[
'#cache'
][
'bin'
]
:
'render'
;
$expire
=
isset
(
$elements
[
'#cache'
][
'expire'
])
?
$elements
[
'#cache'
][
'expire'
]
:
Cache
::
PERMANENT
;
$this
->
cacheFactory
->
get
(
$bin
)
->
set
(
$cid
,
$data
,
$expire
,
$data
[
'#cache'
][
'tags'
]);
$cache
=
$this
->
cacheFactory
->
get
(
$bin
);
// Two-tier caching: detect different CID post-bubbling, create redirect,
// update redirect if different set of cache contexts.
// @see ::doRender()
// @see ::cacheGet()
if
(
isset
(
$pre_bubbling_cid
)
&&
$pre_bubbling_cid
!==
$cid
)
{
// The cache redirection strategy we're implementing here is pretty
// simple in concept. Suppose we have the following render structure:
// - A (pre-bubbling, specifies #cache['keys'] = ['foo'])
// -- B (specifies #cache['contexts'] = ['b'])
//
// At the time that we're evaluating whether A's rendering can be
// retrieved from cache, we won't know the contexts required by its
// children (the children might not even be built yet), so cacheGet()
// will only be able to get what is cached for a $cid of 'foo'. But at
// the time we're writing to that cache, we do know all the contexts that
// were specified by all children, so what we need is a way to
// persist that information between the cache write and the next cache
// read. So, what we can do is store the following into 'foo':
// [
// '#cache_redirect' => TRUE,
// '#cache' => [
// ...
// 'contexts' => ['b'],
// ],
// ]
//
// This efficiently lets cacheGet() redirect to a $cid that includes all
// of the required contexts. The strategy is on-demand: in the case where
// there aren't any additional contexts required by children that aren't
// already included in the parent's pre-bubbled #cache information, no
// cache redirection is needed.
//
// When implementing this redirection strategy, special care is needed to
// resolve potential cache ping-pong problems. For example, consider the
// following render structure:
// - A (pre-bubbling, specifies #cache['keys'] = ['foo'])
// -- B (pre-bubbling, specifies #cache['contexts'] = ['b'])
// --- C (pre-bubbling, specifies #cache['contexts'] = ['c'])
// --- D (pre-bubbling, specifies #cache['contexts'] = ['d'])
//
// Additionally, suppose that:
// - C only exists for a 'b' context value of 'b1'
// - D only exists for a 'b' context value of 'b2'
// This is an acceptable variation, since B specifies that its contents
// vary on context 'b'.
//
// A naive implementation of cache redirection would result in the
// following:
// - When a request is processed where context 'b' = 'b1', what would be
// cached for a $pre_bubbling_cid of 'foo' is:
// [
// '#cache_redirect' => TRUE,
// '#cache' => [
// ...
// 'contexts' => ['b', 'c'],
// ],
// ]
// - When a request is processed where context 'b' = 'b2', we would
// retrieve the above from cache, but when following that redirection,
// get a cache miss, since we're processing a 'b' context value that
// has not yet been cached. Given the cache miss, we would continue
// with rendering the structure, perform the required context bubbling
// and then overwrite the above item with:
// [
// '#cache_redirect' => TRUE,
// '#cache' => [
// ...
// 'contexts' => ['b', 'd'],
// ],
// ]
// - Now, if a request comes in where context 'b' = 'b1' again, the above
// would redirect to a cache key that doesn't exist, since we have not
// yet cached an item that includes 'b'='b1' and something for 'd'. So
// we would process this request as a cache miss, at the end of which,
// we would overwrite the above item back to:
// [
// '#cache_redirect' => TRUE,
// '#cache' => [
// ...
// 'contexts' => ['b', 'c'],
// ],
// ]
// - The above would always result in accurate renderings, but would
// result in poor performance as we keep processing requests as cache
// misses even though the target of the redirection is cached, and
// it's only the redirection element itself that is creating the
// ping-pong problem.
//
// A way to resolve the ping-pong problem is to eventually reach a cache
// state where the redirection element includes all of the contexts used
// throughout all requests:
// [
// '#cache_redirect' => TRUE,
// '#cache' => [
// ...
// 'contexts' => ['b', 'c', 'd'],
// ],
// ]
//
// We can't reach that state right away, since we don't know what the
// result of future requests will be, but we can incrementally move
// towards that state by progressively merging the 'contexts' value
// across requests. That's the strategy employed below and tested in
// \Drupal\Tests\Core\Render\RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing().
// The set of cache contexts for this element, including the bubbled ones,
// for which we are handling a cache miss.
$cache_contexts
=
$data
[
'#cache'
][
'contexts'
];
// Get the contexts by which this element should be varied according to
// the current redirecting cache item, if any.
$stored_cache_contexts
=
[];
$stored_cache_tags
=
[];
if
(
$stored_cache_redirect
=
$cache
->
get
(
$pre_bubbling_cid
))
{
$stored_cache_contexts
=
$stored_cache_redirect
->
data
[
'#cache'
][
'contexts'
];
$stored_cache_tags
=
$stored_cache_redirect
->
data
[
'#cache'
][
'tags'
];
}
// Calculate the union of the cache contexts for this request and the
// stored cache contexts.
$merged_cache_contexts
=
Cache
::
mergeContexts
(
$stored_cache_contexts
,
$cache_contexts
);
// Stored cache contexts incomplete: this request causes cache contexts to
// be added to the redirecting cache item.
if
(
array_diff
(
$merged_cache_contexts
,
$stored_cache_contexts
))
{
$redirect_data
=
[
'#cache_redirect'
=>
TRUE
,
'#cache'
=>
[
// The cache keys of the current element; this remains the same
// across requests.
'keys'
=>
$elements
[
'#cache'
][
'keys'
],
// The union of the current element's and stored cache contexts.
'contexts'
=>
$merged_cache_contexts
,
// The union of the current element's and stored cache tags.
'tags'
=>
Cache
::
mergeTags
(
$stored_cache_tags
,
$data
[
'#cache'
][
'tags'
]),
],
];
$cache
->
set
(
$pre_bubbling_cid
,
$redirect_data
,
$expire
,
$redirect_data
[
'#cache'
][
'tags'
]);
}
// Current cache contexts incomplete: this request only uses a subset of
// the cache contexts stored in the redirecting cache item. Vary by these
// additional (conditional) cache contexts as well, otherwise the
// redirecting cache item would be pointing to a cache item that can never
// exist.
if
(
array_diff
(
$merged_cache_contexts
,
$cache_contexts
))
{
// Recalculate the cache ID.
$recalculated_cid_pseudo_element
=
[
'#cache'
=>
[
'keys'
=>
$elements
[
'#cache'
][
'keys'
],
'contexts'
=>
$merged_cache_contexts
,
]
];
$cid
=
$this
->
createCacheID
(
$recalculated_cid_pseudo_element
);
// Ensure the about-to-be-cached data uses the merged cache contexts.
$data
[
'#cache'
][
'contexts'
]
=
$merged_cache_contexts
;
}
}
$cache
->
set
(
$cid
,
$data
,
$expire
,
$data
[
'#cache'
][
'tags'
]);
}
/**
...
...
@@ -572,6 +751,7 @@ public function getCacheableRenderArray(array $elements) {
'#attached'
=>
$elements
[
'#attached'
],
'#post_render_cache'
=>
$elements
[
'#post_render_cache'
],
'#cache'
=>
[
'contexts'
=>
$elements
[
'#cache'
][
'contexts'
],
'tags'
=>
$elements
[
'#cache'
][
'tags'
],
],
];
...
...
core/lib/Drupal/Core/Render/RendererInterface.php
View file @
fe04699e
...
...
@@ -72,13 +72,31 @@ public function renderPlain(&$elements);
* provided by the children is typically inserted into the markup generated by
* the parent array.
*
* An important aspect of rendering is the bubbling of rendering metadata:
* cache tags, attached assets and #post_render_cache metadata all need to be
* bubbled up. That information is needed once the rendering to a HTML string
* is completed: the resulting HTML for the page must know by which cache tags
* it should be invalidated, which (CSS and JavaScript) assets must be loaded,
* and which #post_render_cache callbacks should be executed. A stack data
* structure is used to perform this bubbling.
* An important aspect of rendering is caching the result, when appropriate.
* Because the HTML of a rendered item includes all of the HTML of the
* rendered children, caching it requires certain information to bubble up
* from child elements to their parents:
* - Cache contexts, so that the render cache is varied by every context that
* affects the rendered HTML. Because cache contexts affect the cache ID,
* and therefore must be resolved for cache hits as well as misses, it is
* up to the implementation of this interface to decide how to implement
* the caching of items whose children specify cache contexts not directly
* specified by the parent. \Drupal\Core\Render\Renderer implements this
* with a lazy two-tier caching strategy. Alternate strategies could be to
* not cache such parents at all or to cache them with the child elements
* replaced by placeholder tokens that are dynamically rendered after cache
* retrieval.
* - Cache tags, so that cached renderings are invalidated when site content
* or configuration that can affect that rendering changes.
* - #post_render_cache callbacks, for executing code to handle dynamic
* requirements that cannot be cached.
* A stack of \Drupal\Core\Render\BubbleableMetadata objects can be used to
* perform this bubbling.
*
* Additionally, whether retrieving from cache or not, it is important to
* know all of the assets (CSS and JavaScript) required by the rendered HTML,
* and this must also bubble from child to parent. Therefore,
* \Drupal\Core\Render\BubbleableMetadata includes that data as well.
*
* The process of rendering an element is recursive unless the element defines
* an implemented theme hook in #theme. During each call to
...
...
@@ -111,6 +129,20 @@ public function renderPlain(&$elements);
* metadata from the element retrieved from render cache. Then, this stack
* frame is bubbled: the two topmost frames are popped from the stack,
* they are merged, and the result is pushed back onto the stack.
* However, also in case of a cache miss we have to do something. Note
* that a Renderer renders top-down, which means that we try to render a
* parent first, and we try to avoid the work of rendering the children by
* using the render cache. Though in this case, we are dealing with a
* cache miss. So a Renderer traverses down the tree, rendering all
* children. In doing so, the render stack is updated with the bubbleable
* metadata of the children. That means that once the children are
* rendered, we can render cache this element. But the cache ID may have
* *changed* at that point, because the children's cache contexts have
* been bubbled!
* It is for that case that we must store the current (pre-bubbling) cache
* ID, so that we can compare it with the new (post-bubbling) cache ID
* when writing to the cache. We store the current cache ID in
* $pre_bubbling_cid.
* - If this element has #type defined and the default attributes for this
* element have not already been merged in (#defaults_loaded = TRUE) then
* the defaults for this type of element, defined in hook_element_info(),
...
...
@@ -210,6 +242,12 @@ public function renderPlain(&$elements);
* - If this element has #cache defined, the rendered output of this element
* is saved to Renderer::render()'s internal cache. This includes the
* changes made by #post_render.
* At the same time, if $pre_bubbling_cid is set, it is compared to the
* calculated cache ID. If they are different, then a redirecting cache
* item is created, containing the #cache metadata of the current element,
* and written to cache using the value of $pre_bubbling_cid as the cache
* ID. This ensures the pre-bubbling ("wrong") cache ID redirects to the
* post-bubbling ("right") cache ID.
* - If this element has an array of #post_render_cache functions defined,
* or any of its children has (which we would know thanks to the stack
* having been updated just before the render caching step), they are
...
...
core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
View file @
fe04699e
...
...
@@ -157,6 +157,7 @@ public function viewElements(FieldItemListInterface $items) {
// Unpublished comments are not included in
// $entity->get($field_name)->comment_count, but unpublished comments
// should display if the user is an administrator.
$elements
[
'#cache'
][
'contexts'
][]
=
'user.roles'
;
if
(
$this
->
currentUser
->
hasPermission
(
'access comments'
)
||
$this
->
currentUser
->
hasPermission
(
'administer comments'
))
{
// This is a listing of Comment entities, so associate its list cache
// tag for correct invalidation.
...
...
@@ -182,6 +183,7 @@ public function viewElements(FieldItemListInterface $items) {
// display below the entity. Do not show the form for the print view mode.
if
(
$status
==
CommentItemInterface
::
OPEN
&&
$comment_settings
[
'form_location'
]
==
CommentItemInterface
::
FORM_BELOW
&&
$this
->
viewMode
!=
'print'
)
{
// Only show the add comment form if the user has permission.
$elements
[
'#cache'
][
'contexts'
][]
=
'user.roles'
;
if
(
$this
->
currentUser
->
hasPermission
(
'post comments'
))
{
// All users in the "anonymous" role can use the same form: it is fine
// for this form to be stored in the render cache.
...
...
core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
View file @
fe04699e
...
...
@@ -127,6 +127,11 @@ public function viewElements(FieldItemListInterface $items) {
// Display the date using theme datetime.
$elements
[
$delta
]
=
array
(
'#cache'
=>
[
'contexts'
=>
[
'timezone'
,
],
],
'#theme'
=>
'time'
,
'#text'
=>
$formatted_date
,
'#html'
=>
FALSE
,
...
...
core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
View file @
fe04699e
...
...
@@ -46,7 +46,14 @@ public function viewElements(FieldItemListInterface $items) {
}
$output
=
$date
->
format
(
$format
);
}
$elements
[
$delta
]
=
array
(
'#markup'
=>
$output
);
$elements
[
$delta
]
=
[
'#cache'
=>
[
'contexts'
=>
[
'timezone'
,
],
],
'#markup'
=>
$output
,
];
}
return
$elements
;
...
...
core/modules/filter/src/FilterProcessResult.php
View file @
fe04699e
...
...
@@ -85,6 +85,13 @@ class FilterProcessResult {
*/
protected
$cacheTags
;
/**
* The associated cache contexts.
*
* @var string[]
*/
protected
$cacheContexts
;
/**
* The associated #post_render_cache callbacks.
*
...
...
@@ -105,6 +112,7 @@ public function __construct($processed_text) {
$this
->
assets
=
array
();
$this
->
cacheTags
=
array
();
$this
->
cacheContexts
=
array
();
$this
->
postRenderCacheCallbacks
=
array
();
}
...
...
@@ -174,6 +182,41 @@ public function setCacheTags(array $cache_tags) {
return
$this
;
}
/**
* Gets cache contexts associated with the processed text.
*
* @return string[]
*/
public
function
getCacheContexts
()
{
return
$this
->
cacheContexts
;
}
/**
* Adds cache contexts associated with the processed text.
*
* @param string[] $cache_contexts
* The cache contexts to be added.
*
* @return $this
*/
public
function
addCacheContexts
(
array
$cache_contexts
)
{
$this
->
cacheContexts
=
Cache
::
mergeContexts
(
$this
->
cacheContexts
,
$cache_contexts
);
return
$this
;
}
/**
* Sets cache contexts associated with the processed text.
*
* @param string[] $cache_contexts
* The cache contexts to be associated.
*
* @return $this
*/
public
function
setCacheContexts
(
array
$cache_contexts
)
{
$this
->
cacheContexts
=
$cache_contexts
;
return
$this
;
}
/**
* Gets assets associated with the processed text.
*
...
...
@@ -256,6 +299,7 @@ public function setPostRenderCacheCallbacks(array $post_render_cache_callbacks)
*/
public
function
getBubbleableMetadata
()
{
return
new
BubbleableMetadata
(
$this
->
getCacheContexts
(),
$this
->
getCacheTags
(),
$this
->
getAssets
(),
$this
->
getPostRenderCacheCallbacks
()
...
...
core/modules/filter/src/Tests/FilterAPITest.php
View file @
fe04699e
...
...
@@ -216,6 +216,10 @@ function testProcessedTextElement() {
'weight'
=>
0
,
'status'
=>
TRUE
,
),
'filter_test_cache_contexts'
=>
array
(
'weight'
=>
0
,
'status'
=>
TRUE
,
),
'filter_test_post_render_cache'
=>
array
(
'weight'
=>
1
,
'status'
=>
TRUE
,
...
...
@@ -253,6 +257,11 @@ function testProcessedTextElement() {
'foo:baz'
,
);
$this
->
assertEqual
(
$expected_cache_tags
,
$build
[
'#cache'
][
'tags'
],
'Expected cache tags present.'
);
$expected_cache_contexts
=
[
// The cache context set by the filter_test_cache_contexts filter.
'language'
,
];
$this
->
assertEqual
(
$expected_cache_contexts
,
$build
[
'#cache'
][
'contexts'
],
'Expected cache contexts present.'
);
$expected_markup
=
'<p>Hello, world!</p><p>This is a dynamic llama.</p>'
;
$this
->
assertEqual
(
$expected_markup
,
$build
[
'#markup'
],
'Expected #post_render_cache callback has been applied.'
);
}
...
...
core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestCacheContexts.php
0 → 100644
View file @
fe04699e
<?php
/**
* @file
* Contains \Drupal\filter_test\Plugin\Filter\FilterTestCacheContexts.
*/
namespace
Drupal\filter_test\Plugin\Filter
;
use
Drupal\filter\FilterProcessResult
;
use
Drupal\filter\Plugin\FilterBase
;
/**
* Provides a test filter to associate cache contexts.
*
* @Filter(
* id = "filter_test_cache_contexts",
* title = @Translation("Testing filter"),
* description = @Translation("Does not change content; associates cache contexts."),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
* )
*/
class
FilterTestCacheContexts
extends
FilterBase
{
/**
* {@inheritdoc}
*/