0422ecf5eb52a127c342e6eba11f49a3ddcbf92b
[yaffs-website] / web / core / lib / Drupal / Core / Render / PlaceholderingRenderCache.php
1 <?php
2
3 namespace Drupal\Core\Render;
4
5 use Drupal\Core\Cache\CacheFactoryInterface;
6 use Drupal\Core\Cache\Context\CacheContextsManager;
7 use Symfony\Component\HttpFoundation\RequestStack;
8
9 /**
10  * Adds automatic placeholdering to the RenderCache.
11  *
12  * This automatic placeholdering is performed to ensure the containing elements
13  * and overarching response are as cacheable as possible. Elements whose subtree
14  * bubble either max-age=0 or high-cardinality cache contexts (such as 'user'
15  * and 'session') are considered poorly cacheable.
16  *
17  * @see sites/default/default.services.yml
18  *
19  * Automatic placeholdering is performed only on elements whose subtree was
20  * generated using a #lazy_builder callback and whose bubbled cacheability meets
21  * the auto-placeholdering conditions as configured in the renderer.config
22  * container parameter.
23  *
24  * This RenderCache implementation automatically replaces an element with a
25  * placeholder:
26  * - on render cache hit, i.e. ::get()
27  * - on render cache miss, i.e. ::set() (in subsequent requests, it will be a
28  *   cache hit)
29  *
30  * In either case, the render cache is guaranteed to contain the to-be-rendered
31  * placeholder, so replacing (rendering) the placeholder will be very fast.
32  *
33  * Finally, in case the render cache item disappears between the time it is
34  * decided to automatically placeholder the element and the time where the
35  * placeholder is replaced (rendered), that is guaranteed to not be problematic.
36  * Because this only automatically placeholders elements that have a
37  * #lazy_builder callback set, which means that in the worst case, it will need
38  * to be re-rendered.
39  */
40 class PlaceholderingRenderCache extends RenderCache {
41
42   /**
43    * The placeholder generator.
44    *
45    * @var \Drupal\Core\Render\PlaceholderGeneratorInterface
46    */
47   protected $placeholderGenerator;
48
49   /**
50    * Stores rendered results for automatically placeholdered elements.
51    *
52    * This allows us to avoid talking to the cache twice per auto-placeholdered
53    * element, or in case of an uncacheable element, to render it twice.
54    *
55    * Scenario A. The double cache read would happen because:
56    * 1. when rendering, cache read, but auto-placeholdered
57    * 2. when rendering placeholders, again cache read
58    *
59    * Scenario B. The cache write plus read would happen because:
60    * 1. when rendering, cache write, but auto-placeholdered
61    * 2. when rendering placeholders, cache read
62    *
63    * Scenario C. The double rendering for an uncacheable element would happen because:
64    * 1. when rendering, not cacheable, but auto-placeholdered
65    * 2. when rendering placeholders, rendered again
66    *
67    * In all three scenarios, this static cache avoids the second step, thus
68    * avoiding expensive work.
69    *
70    * @var array
71    */
72   protected $placeholderResultsCache = [];
73
74   /**
75    * Constructs a new PlaceholderingRenderCache object.
76    *
77    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
78    *   The request stack.
79    * @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
80    *   The cache factory.
81    * @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager
82    *   The cache contexts manager.
83    * @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
84    *   The placeholder generator.
85    */
86   public function __construct(RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContextsManager $cache_contexts_manager, PlaceholderGeneratorInterface $placeholder_generator) {
87     parent::__construct($request_stack, $cache_factory, $cache_contexts_manager);
88     $this->placeholderGenerator = $placeholder_generator;
89   }
90
91   /**
92    * {@inheritdoc}
93    */
94   public function get(array $elements) {
95     // @todo remove this check when https://www.drupal.org/node/2367555 lands.
96     if (!$this->requestStack->getCurrentRequest()->isMethodCacheable()) {
97       return FALSE;
98     }
99
100     // When rendering placeholders, special case auto-placeholdered elements:
101     // avoid retrieving them from cache again, or rendering them again.
102     if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === FALSE) {
103       $cached_placeholder_result = $this->getFromPlaceholderResultsCache($elements);
104       if ($cached_placeholder_result !== FALSE) {
105         return $cached_placeholder_result;
106       }
107     }
108
109     $cached_element = parent::get($elements);
110
111     if ($cached_element === FALSE) {
112       return FALSE;
113     }
114     else {
115       if ($this->placeholderGenerator->canCreatePlaceholder($elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($cached_element)) {
116         return $this->createPlaceholderAndRemember($cached_element, $elements);
117       }
118
119       return $cached_element;
120     }
121   }
122
123   /**
124    * {@inheritdoc}
125    */
126   public function set(array &$elements, array $pre_bubbling_elements) {
127     $result = parent::set($elements, $pre_bubbling_elements);
128
129     // @todo remove this check when https://www.drupal.org/node/2367555 lands.
130     if (!$this->requestStack->getCurrentRequest()->isMethodCacheable()) {
131       return FALSE;
132     }
133
134     if ($this->placeholderGenerator->canCreatePlaceholder($pre_bubbling_elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($elements)) {
135       // Overwrite $elements with a placeholder. The Renderer (which called this
136       // method) will update the context with the bubbleable metadata of the
137       // overwritten $elements.
138       $elements = $this->createPlaceholderAndRemember($this->getCacheableRenderArray($elements), $pre_bubbling_elements);
139     }
140
141     return $result;
142   }
143
144   /**
145    * Create a placeholder for a renderable array and remember in a static cache.
146    *
147    * @param array $rendered_elements
148    *   A fully rendered renderable array.
149    * @param array $pre_bubbling_elements
150    *   A renderable array corresponding to the state (in particular, the
151    *   cacheability metadata) of $rendered_elements prior to the beginning of
152    *   its rendering process, and therefore before any bubbling of child
153    *   information has taken place. Only the #cache property is used by this
154    *   function, so the caller may omit all other properties and children from
155    *   this array.
156    *
157    * @return array
158    *   Renderable array with placeholder markup and the attached placeholder
159    *   replacement metadata.
160    */
161   protected function createPlaceholderAndRemember(array $rendered_elements, array $pre_bubbling_elements) {
162     $placeholder_element = $this->placeholderGenerator->createPlaceholder($pre_bubbling_elements);
163     // Remember the result for this placeholder to avoid double work.
164     $placeholder = (string) $placeholder_element['#markup'];
165     $this->placeholderResultsCache[$placeholder] = $rendered_elements;
166     return $placeholder_element;
167   }
168
169   /**
170    * Retrieves an auto-placeholdered renderable array from the static cache.
171    *
172    * @param array $elements
173    *   A renderable array.
174    *
175    * @return array|false
176    *   A renderable array, with the original element and all its children pre-
177    *   rendered, or FALSE if no cached copy of the element is available.
178    */
179   protected function getFromPlaceholderResultsCache(array $elements) {
180     $placeholder_element = $this->placeholderGenerator->createPlaceholder($elements);
181     $placeholder = (string) $placeholder_element['#markup'];
182     if (isset($this->placeholderResultsCache[$placeholder])) {
183       return $this->placeholderResultsCache[$placeholder];
184     }
185     return FALSE;
186   }
187
188 }