Blocked on #2433591: Views using pagers should specify a cache context

Used to be postponed on
#2450897: Cache Field views row output
and
#2452317: Let views result cache use cache contexts

Problem/Motivation

  1. We want to be able to use render caching for Views, because Views' output caching still requires Views to be initialized and hence is still very costly. See the #2323335: [meta] Improve Views' caching issue summary for details.
  2. Security! Views' results & output caching right now allows information disclosure because it doesn't take the necessary cache contexts into account. See comment #10.

Proposed resolution

Introduce render caching at view display level (Panoramic Views™). Each cache plugin will be responsible for determining the actual display render cache behavior, which will replace output caching. Result caching will remain the same.

  • The default cache plugin will become the Tag-based one, which will just implement render caching the usual way, at display level.
  • For very complex (uncacheable) displays it will be possible possible to disable render caching via the None plugin.
  • Finally, we will retain the Time-based plugin, which will implement regular render caching at display-level, but will override the invalidation strategy by ignoring invalid cache tags, so that only the cache entry expiration timestamp will matter. The actual expiration time will be configured via the usual settings. We will need a new #cache_min_age to avoid bubbling it to the upper levels (see #38).

The display renderable array will be provided by a static method on the DisplayPluginBase class, to avoid loading the view altogether. In case of a cache miss the renderable array will be available to the actual display object, that will be able to perform any required modification.

Remaining tasks

Tasks in that issue:

None

User interface changes

None.

API changes

Code which is not already render cached, like page controllers, should now use for example Page::buildRenderable($view_id, $display_id, $args);
in order to be rendered. This returns a minimum render array, that is render cached, and by that potentially much faster.

Review help

+++ b/core/modules/comment/comment.module
@@ -211,6 +211,29 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
+function comment_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {

Code to build up $entity->rss_elements is moved from the CommentLinksBuilder to hook_entity_view(). The reason for that is that we now don't do early rendering (drupal_render() inside each individual row) anymore. It used to work like this: Early rendering => triggers #post_render_cache => builds up the links and the $entity object is changed.
In rendering the row, views-view-row-rss.twig.html, we took over the changed $entity object. Now the rendering happens, when the row template is rendered, which means, that
$elements is not setup yet.

Changes to the way how views contextual links are rendered

For views contextual links we want to add the contextual links to the $page render array, in order to have the UX we want. As long we did not render cached those entries, it used a pre_render callback for #type 'page',
which called out to views_get_page_view(). This function cannot longer work, because the view is not always executed anymore. Instead, we introduce Views:: getPageRenderArray(), which
is called from the page controller itself. This allows to retrieve just the metadata we need, like the view ID, display ID, and whether we want to show contextual links.

Why do we override cache keys in ViewExecutable::setItemsPerPage

There are two ways to render a view. a) You start with a simple render array, see API changes. b) You have a view executable already

For the simple render array, which is the potential much better cacheable one, we still want to vary for example the current page. In this case, we just have one context available, the global one. Therefore
we use cache contexts, which define global variations of the content.

In case you already have a view executable available, you control the context, so the context is not global anymore. In this case the variation can be added to the cache keys, so for example we set 'page:$number' into cache keys.

CommentFileSizeAuthor
#240 238-240-interdiff.txt3.42 KBalexpott
#240 2381277.240.patch148.96 KBalexpott
#238 interdiff.txt4.19 KBdawehner
#238 2381277-238.patch148.3 KBdawehner
#236 2381277-235.patch147.42 KBdawehner
#231 interdiff.txt2.46 KBolli
#231 2381277-231.patch146.15 KBolli
#229 interdiff.txt3.65 KBdawehner
#229 2381277-229.patch145.49 KBdawehner
#218 interdiff.txt1.48 KBdawehner
#218 2381277-218.patch146.22 KBdawehner
#216 interdiff.txt5.8 KBdawehner
#216 2381277-213.patch144.74 KBdawehner
#205 2381277-205_views_render_cache.patch143.24 KBfgm
#205 interdiff-2381277-199-205.txt667 bytesfgm
#203 XHProf__Hierarchical_Profiler_Report 4.png91.82 KBplach
#199 interdiff.txt5.23 KBdawehner
#199 2381277-199.patch143.24 KBdawehner
#198 XHProf__Hierarchical_Profiler_Report 3.png96.65 KBplach
#198 XHProf__Hierarchical_Profiler_Report.png91.99 KBplach
#198 XHProf__Hierarchical_Profiler_Report 2.png88.78 KBplach
#195 interdiff.txt2.68 KBdawehner
#195 2381277-194.patch143.73 KBdawehner
#194 interdiff.txt2.68 KBdawehner
#194 2381277-194.patch143.73 KBdawehner
#192 interdiff.txt7.35 KBdawehner
#192 2381277-192.patch143.57 KBdawehner
#190 interdiff.txt8.91 KBdawehner
#190 2381277-190.patch140.54 KBdawehner
#186 interdiff.txt4.4 KBdawehner
#186 2381277-186.patch137.66 KBdawehner
#184 interdiff.txt12.93 KBdawehner
#184 2381277-184.patch137.63 KBdawehner
#182 interdiff.txt2.31 KBdawehner
#182 2381277-182.patch134.72 KBdawehner
#180 interdiff.txt12.02 KBdawehner
#180 2381277-179.patch134.03 KBdawehner
#175 interdiff.txt1.98 KBdawehner
#175 2381277-175.patch134.55 KBdawehner
#173 interdiff.txt7.76 KBdawehner
#173 2381277-173.patch134.5 KBdawehner
#167 interdiff.txt6.59 KBdawehner
#167 2381277-166.patch128.61 KBdawehner
#163 interdiff.txt13.62 KBdawehner
#163 2381277-163.patch127.03 KBdawehner
#158 interdiff.txt17.68 KBdawehner
#158 2381277-158.patch116.39 KBdawehner
#156 interdiff.txt4.38 KBdawehner
#156 2381277-156.patch105.23 KBdawehner
#153 interdiff.txt660 bytesdawehner
#153 2381277-153.patch106.91 KBdawehner
#151 interdiff.txt2.16 KBdawehner
#151 2381277-151.patch107.56 KBdawehner
#149 interdiff.txt15.8 KBdawehner
#149 2381277-149.patch106.37 KBdawehner
#135 interdiff-2381277-134.txt1.25 KBdamiankloip
#135 2381277-134.patch93.91 KBdamiankloip
#133 interdiff.txt1.85 KBdawehner
#133 2381277-131.patch92.66 KBdawehner
#127 interdiff.txt798 bytesdawehner
#127 2381277-126.patch92.44 KBdawehner
#124 interdiff.txt17.6 KBdawehner
#124 2381277-124.patch92.38 KBdawehner
#117 interdiff.txt9.2 KBdawehner
#117 2381277-117.patch90.22 KBdawehner
#115 interdiff.txt23.62 KBdawehner
#115 2381277-115.patch86.5 KBdawehner
#112 interdiff.txt6.17 KBdawehner
#112 2381277-111.patch67.16 KBdawehner
#109 2381277-109.patch65.08 KBdawehner
#109 interdiff.txt585 bytesdawehner
#107 interdiff.txt5.22 KBdawehner
#107 2381277-107.patch65.31 KBdawehner
#103 interdiff.txt11.3 KBdawehner
#103 2381277-103.patch66.08 KBdawehner
#100 interdiff.txt9.39 KBdawehner
#100 2381277-99.patch59.46 KBdawehner
#98 panoramic-views.jpg1.04 MBplach
#94 interdiff.txt8.7 KBdawehner
#94 2381277-94.patch50.28 KBdawehner
#92 interdiff.txt4.88 KBdawehner
#92 2381277-92.patch45.17 KBdawehner
#90 interdiff.txt10.42 KBdawehner
#90 2381277-90.patch49.14 KBdawehner
#84 interdiff.txt9.44 KBdawehner
#84 2381277-83.patch40.2 KBdawehner
#82 interdiff.txt5.99 KBdawehner
#82 2381277-82.patch32.18 KBdawehner
#80 interdiff.txt7.47 KBdawehner
#74 2381277-74.patch29.07 KBdawehner
#74 interdiff.txt6.64 KBdawehner
#53 interdiff.txt3.28 KBdawehner
#52 interdiff.txt2.33 KBdawehner
#52 2381277-51.patch34.84 KBdawehner
#49 interdiff.txt11.91 KBdawehner
#49 2381277-49.patch22.79 KBdawehner
#47 interdiff.txt2.77 KBdawehner
#47 2381277-47.patch28.09 KBdawehner
#45 interdiff.txt3.2 KBdawehner
#45 2381277-45.patch26.44 KBdawehner
#43 interdiff.txt7.22 KBdawehner
#43 2381277-43.patch25.43 KBdawehner
#42 2381277-42.patch18.21 KBdawehner
#39 views-cache_display-2381277-39.patch2.38 KBplach
#26 2381277-25.patch699 bytesdawehner
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner’s picture

Is there any chance that we can try to make that an opt-in feature?

As long we haven't figured out cache tags yet, we don't have any invalidation yet.

Wim Leers’s picture

Title: Make Views use render caching » [PP-3] Make Views use render caching
Status: Active » Postponed
Wim Leers’s picture

Wim Leers’s picture

Title: [PP-2] Make Views use render caching » [PP-1] Make Views use render caching

As per #2381217-5: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache, that issue is not actually blocked on #2342651: Cache tags for *all* config entities, so it can continue, and hence this issue in turn is only blocked on 1, not 2 issues.

Wim Leers’s picture

#2318377: Determine whether a view is cacheable and its required contexts, store this i/t config entity did the hard work of adding the necessary methods/APIs/metadata to Views handlers & plugins to be able to gather the necessary cache contexts without having to initialize all handlers & plugins. That's great.

But it didn't add test coverage. Nor did it actually put the added APIs to use. That will be done in this issue.

This issue will roughly have replace the logic in CachePluginBase::generateResultsKey with the cache metadata that is generated by that issue.

Wim Leers’s picture

Wim Leers’s picture

Title: Make Views use render caching » [PP-1] Make Views use render caching
Related issues: +#2445743: Allow views base tables and entity types to define additional cache contexts
Wim Leers’s picture

Title: [PP-1] Make Views use render caching » Make Views use render caching
Wim Leers’s picture

Issue summary: View changes
Issue tags: +Security

IMO this is actually even a security issue, for the reasons I explained in #2323335-6: [meta] Improve Views' caching. I think this may need to be critical because of that.

#2381217: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache was fixed; #2183039: Views' cache tag-based caching only sets cache tags for related entities based on Views relationships, should also do so for entity reference fields has been closed.

New is #2445743: Allow views base tables and entity types to define additional cache contexts. Which points to a third problem: Views' caches are broken by design. Look at how the CID for a Views results cache item is calculated:

  /**
   * Calculates and sets a cache ID used for the result cache.
   *
   * @return string
   *   The generated cache ID.
   */
  public function generateResultsKey() {
      …

      $key_data = array(
        'build_info' => $build_info,
        'roles' => $user->getRoles(),
        'super-user' => $user->id() == 1, // special caching for super user.
        'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
        'base_url' => $GLOBALS['base_url'],
      );
      foreach (array('exposed_info', 'sort', 'order') as $key) {
        if ($this->view->getRequest()->query->has($key)) {
          $key_data[$key] = $this->view->getRequest()->query->get($key);
        }
      }

      $key_data['pager'] = [
        'page' => $this->view->getCurrentPage(),
        'items_per_page' => $this->view->getItemsPerPage(),
        'offset' => $this->view->getOffset(),
      ];

      $this->resultsKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':results:' . hash('sha256', serialize($key_data));
    }

    return $this->resultsKey;
  }

and it's even worse for a Views output cache item:

 $this->view->result,
        'roles' => $user->getRoles(),
        'super-user' => $user->id() == 1, // special caching for super user.
        'theme' => \Drupal::theme()->getActiveTheme()->getName(),
        'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
        'base_url' => $GLOBALS['base_url'],
      );

Note how this assumes that:

  1. all Views are varied by the same contexts
  2. there are no contexts other than the dozen this tracks (build info, roles, super user, langcode, base URL, 3 request parameters and the pager)

This is wrong.

But… Views also couldn't have done better in the past. It comes from D7. D8 has only had the concept of cache contexts since about a year. But now it is time to update Views' caching to use the cache contexts it knows about. Since #2267453: Views plugins do not store additional dependencies, it knows the cache contexts for the plugins/handlers for a specific view display. And with #2445743: Allow views base tables and entity types to define additional cache contexts, it will have the information it needs to know for a specific view display by which cache contexts it is varied. So we should put that information to good use — not just use it for rendering, but also for Views' own caching.

Wim Leers’s picture

Note that #10 does not point to a security issue in D7.

First of all: #10 only describes an information disclosure bug about the *rendered* results. The results themselves (i.e. "which things to show") is secure in D7 and D8, because that happens at the query level.
So this is only about the rendered rows.

In Drupal 7, we didn't have cache contexts. It was/is the Dynamic Content Wild West. In Drupal 7, if content listed by Views is very dynamic and potentially sensitive, it is your responsibility as the site builder/developer to subclass the Views cache plugin and write your own, and as such hardcoding your own "(cache) contexts" in addition to the default ones.

To make this very concrete: in Drupal 7, there's no entity render caching, but in Drupal 8 there is. If an entity is varied by a cache context not captured by Views' default cache plugin (see #10), then Views' output cache doesn't care, and will serve the exact same node X to users A and B (both of whom legitimately can view node X, thanks to the query-level access checking), even though user A can see a field Y that user B shouldn't be able to see. The reason user B can see it too, is because it's varied by a foobar cache context that Views doesn't care about.

effulgentsia’s picture

Priority: Major » Critical
Issue tags: +Needs Drupal 8 critical triage

Raising to critical, because while in Drupal 7, it's the site builder's responsibility to think about cache poisoning issues when enabling block or Views caches, that is no longer the case in Drupal 8, except for Views output cache, which I think qualifies this as a real D8 security bug. Tagging for triage though, since the branch maintainers might not see it that way.

catch’s picture

fwiw I agree with the reasoning on this.

dawehner’s picture

Good point @effulgentsia

In general I think we should clearly split up work on the result cache from other kind of cache, see #2452317: Let views result cache use cache contexts

Wim Leers’s picture

Yes, let's improve the results cache first!

effulgentsia’s picture

Discussed with webchick, alexpott, and xjm, and they also agreed with #12 / #13.

plach’s picture

effulgentsia’s picture

At one level, unrelated. This issue is about caching the entire rendered View (or rather, doing it in a more D8-proper way rather than HEAD's legacy custom approach). While #2450897: Cache Field views row output is about a single row.

At another level, I don't know, but suspect that #2450897: Cache Field views row output will be easier to do once this issue is finished. If that's true, I recommend postponing that issue on this one.

plach’s picture

Sounds reasonable to me, the D7 version of views_row_cache is a mighty hack due to the fact that we never get to have full render arrays...

effulgentsia’s picture

Title: Make Views use render caching » [PP-2] Make Views use render caching
Related issues: +#2452317: Let views result cache use cache contexts, +#2450897: Cache Field views row output

Discussed this with Wim today, and he suggested the opposite of what I said in #18. Specifically, that #2450897: Cache Field views row output can proceed without this issue, because we already have full render arrays where we need to for that issue, and that per #15, this issue is more blocked on #2452317: Let views result cache use cache contexts than on anything rendering related. In fact, Wim suggested that doing #2450897: Cache Field views row output and #2452317: Let views result cache use cache contexts first, independently of each other, should then make this issue simple, since caching a rendered View is just the combination of caching the results plus the rendering of each result. So, postponing this on those two.

YesCT’s picture

Issue summary: View changes
Status: Active » Postponed

putting that info in the summary, and postponing.

Wim Leers’s picture

Title: [PP-2] Make Views use render caching » [PP-1] Make Views use render caching
Wim Leers’s picture

#2450897: Cache Field views row output is very close!

Also: the related-to-this-but-not-blocking-this #1867518: Leverage entityDisplay to provide fast rendering for fields landed last week.

dawehner’s picture

Title: [PP-1] Make Views use render caching » Make Views use render caching

Well, either we bubble up cache metadata via cached or uncached rows, I don't see why this should be blocked on the other issue, at least the basic building blocks can be achieved entirely independent.

Wim Leers’s picture

Status: Postponed » Active

Per #24.

dawehner’s picture

Status: Active » Needs review
FileSize
699 bytes

Just curious what happens :)

Wim Leers’s picture

Just curious what happens :)

:)

Status: Needs work » Needs review

plach queued 26: 2381277-25.patch for re-testing.

plach’s picture

#2450897: Cache Field views row output was committed (yay!), updated the test failures count by sending #26 to the bot again.

dawehner’s picture

Issue summary: View changes

Updating the issue summary

Status: Needs work » Needs review

plach queued 26: 2381277-25.patch for re-testing.

plach’s picture

Discussed with Daniel what role cache plugins should have with respect to render caching at display level and we came up with the Panoramic Views Render Caching strategy. Each cache plugin will be responsible for determining the actual display render cache behavior, while result caching will remain the same:

  • The default cache plugin will become the Tag-based one, which will just implement render caching the usual way, at display level. Output caching will be completely replaced by render caching.
  • For very complex (uncacheable) displays it will be possible possible to disable render caching via the None plugin.
  • Finally, we will retain the Time-based plugin, which will implement regular render caching at display-level, but will override the invalidation strategy by getting rid of cache tags (aside possibly from the rendered one), so that only the cache entry expiration timestamp will matter. The actual expiration time will be configured via the usual settings.

The display renderable array will be provided by a static method on the DisplayPluginBase class, to avoid loading the view altogether. In case of a cache miss the renderable array will be available to the actual display object, that will be able to perform any required modification.

plach’s picture

Fabianx’s picture

Discussed with plach at the airport:

For 3. we will need a #cache_minage property (that does not bubble and hence is not within #cache), which controls that the RenderCache service should set that special flag for retrieving the cache, so that invalid / expired items are returned.

Will likely need some more thought as we do not want to call the cache plugin at all, but determine on cache retrieval, so we probably should be setting that always and then check if that property is present in the retrieved render array.ù

If it is set and invalid, we compare the timestamp of the minage with the actual age timestamp, else we return a cache miss.

--

- None plugin should just set max-age 0 and we are done.

- Tag based output kinda does nothing on itself. (what we already do in core except that we remove the old and now due to bubbling broken output cache support)

plach’s picture

Status: Needs work » Needs review
FileSize
2.38 KB

Here are a couple of test fixes.

plach’s picture

Once again too many branches ;)

dawehner’s picture

FileSize
18.21 KB

So current work.

dawehner’s picture

Status: Needs work » Needs review
FileSize
25.43 KB
7.22 KB

More work.

dawehner’s picture

Status: Needs work » Needs review
FileSize
26.44 KB
3.2 KB

Some more fixes.

dawehner’s picture

Status: Needs work » Needs review
FileSize
28.09 KB
2.77 KB

A couple of test fixes

dawehner’s picture

Status: Needs work » Needs review
FileSize
22.79 KB
11.91 KB

Fixed quite some of the failure:

plach’s picture

I investigated test failures a bit:

dawehner’s picture

Status: Needs work » Needs review
FileSize
34.84 KB
2.33 KB

Applied #2489966: The Views table style plugin does not specify cache contexts for click sorting and #2433591: Views using pagers should specify a cache context and fixed some small points.

Agreed these two subissues would solve a lot of the problems.

dawehner’s picture

FileSize
3.28 KB

Just trying to expand the test coverage and well, the page cache does not take into account the max age bubbling.

catch’s picture

Committed one of the blockers and reviewed and bumped status of the other one.

Patch looks really encouraging to me - great that the necessary changes really aren't that much after all the other issues.

YesCT’s picture

Issue summary: View changes

noting the blocker in the summary.

Fabianx’s picture

Title: Make Views use render caching » [PP-1] Make Views use render caching
Status: Needs work » Postponed

Postponed on #2433591: Views using pagers should specify a cache context to keep critical stats properly.

dawehner’s picture

Title: [PP-1] Make Views use render caching » Make Views use render caching
Status: Postponed » Needs review

Not really sure why it should be postponed. Its required to make it pass, but its not required to continue on this issue.

dawehner’s picture

FileSize
32.67 KB
1.01 KB

Did some debugging on the failure in Drupal\contextual\Tests\ContextualDynamicContextTest
The problem is primarily caused by the code in views_page_display_pre_render::

  if ($view = views_get_page_view()) {
    views_add_contextual_links($element, 'page', $view, $view->current_display);
  }

In case we have a cache hit, views_get_page_view() does not exist ... as we have no active view. This is a general problem, which will also hit themers, given that they want to change / add some css classes depending for example on the current result of the view.

At least for this case its enough to check for the route object itself.

Fabianx’s picture

Why is the views_page_display_pre_render() called when there is no view?

Can we call it in the #pre_render callback itself and use another hook for being able to tamper with the render array itself (i.e. cache keys, display parameters, cache contexts up-front to optimize experience)?

dawehner’s picture

Why is the views_page_display_pre_render() called when there is no view?

Can we call it in the #pre_render callback itself and use another hook for being able to tamper with the render array itself (i.e. cache keys, display parameters, cache contexts up-front to optimize experience)?

... We want to place the contextual link not on the level of the rendering of the view, but on the page level, so we add that as pre_render function to #type page.

Fabianx’s picture

As this is page specific, could we instead add the necessary information to $element when we have constructed the view?

Then we would have all overhead in #pre_render, but the contextual link is still dynamically created ...

plach’s picture

Issue summary: View changes

Updated issue summary with #36 and #38.

Wim Leers’s picture

  1. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2136,15 +2136,17 @@ public function render() {
    +    $element['#cache']['contexts'] = Cache::mergeTags($element['#cache']['contexts'], $display_contexts);
    

    Cache::mergeContexts()

  2. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2331,6 +2333,22 @@ public function buildRenderable(array $args = []) {
    +  public static function buildBasicRenderable($view_id, $display_id, array $args = []) {
    

    The documentation for this method is missing.

dawehner’s picture

As this is page specific, could we instead add the necessary information to $element when we have constructed the view?

Then we would have all overhead in #pre_render, but the contextual link is still dynamically created ...

Does that allow you to bubble up the contextual links information? We want to place it on the page render array.

Fabianx’s picture

No what I mean is to add information so that we can re-create information without having the view object itself:

Lets take a look at the _dependencies_ on $view of the https://api.drupal.org/api/drupal/core%21modules%21views%21views.module/... function:


function views_add_contextual_links(&$render_element, $location, ViewExecutable $view, $display_id) {
  // We use the $view->getShowAdminLinks() function, need to know what that depends on.
  if (\Drupal::moduleHandler()->moduleExists('contextual') && $view->getShowAdminLinks()) {

    // Given the same display_id the plugin_id should not change, so would be safe to pre-compute.
    $plugin_id = $view->displayHandlers->get($display_id)->getPluginId();
    $plugin = Views::pluginManager('display')->getDefinition($plugin_id);

     // ... stuff that only depends on $plugin ...

        // If the link was valid, attach information about it to the renderable
        // array.
        if ($valid) {
          $render_element ['#views_contextual_links'] = TRUE;
          $render_element ['#contextual_links'][$group] = array(
            'route_parameters' => $args,
            'metadata' => array(
              'location' => $location,
// Here we use the $view->storage->id(), which we can also pre-compute easily.
              'name' => $view->storage->id(),
              'display_id' => $display_id,
            ),
          );
          // If we're setting contextual links on a page, for a page view, for a
          // user that may use contextual links, attach Views' contextual links
          // JavaScript.
          if ($location === 'page' && $render_element ['#type'] === 'page' && \Drupal::currentUser()->hasPermission('access contextual links')) {
            $render_element ['#attached']['library'][] = 'views/views.contextual-links';
          }
        }
      }
    }
  }
}

So overall this depends on:

- The location, which we have already anyway.
- The display ID - which we have before #pre_render stage
- The plugin ID - which we have before #pre_render stage
- The view storage ID - which we have before #pre_render stage
- The getShowAdminLinks(), which we need to still analyze:

https://api.drupal.org/api/drupal/core!modules!views!src!ViewExecutable....

only depends on the display option, which we can cache as an extra property.

Overall:

location - have already -- cheap
display ID - have already, part of cache keys -- cheap

plugin ID, view storage ID, getShowAdminLinksResult() can put during #pre_render into the $element like:

#view_plugin_id
#view_storage_id
#view_display_show_admin_links

and then use #cache_properties to store this across cache get / sets and boom we can simplify views_add_contextual_links() to no longer rely on the $view object itself, but just on properties in $element - regardless if it was retrieved via #pre_render or cache get.

Fabianx’s picture

Oh and the thing I missed:

No, bubbling is not possible, but as this relies on a global, I would not feel bad to use views_set_page_render_array() / views_get_page_render_array() instead to keep BC.

As its part of the main controller, it would be executed always anyway ... (unless we cache the whole page)

Fabianx’s picture

Another possibility is (once placeholders exist) to just use a placeholder in #attached placeholders, but then use the placeholder string in the page pre_render. [ and just share the placeholder string between them as e.g. a CONSTANT ]

That is likely the absolute cleanest solution, but can only be done as follow-up as placeholders should obviously not block this ...

Wim Leers’s picture

#2433591: Views using pagers should specify a cache context landed.

I think we should also remove the "output caching" concept from CachePluginBase here. That makes it so that CachePluginBase would only need to care about results caching, which is an important conceptual/maintainability simplification.

OTOH, that may make it harder to implement the strategy in #36? Not sure.

Fabianx’s picture

#71: No, that _is_ the strategy of #36 actually (as explained to me by plach), so we are all on the same page.

Wim Leers’s picture

But it's not yet in the patch, which is why I wasn't sure about that. Excellent, then :)

dawehner’s picture

Status: Needs work » Needs review
FileSize
6.64 KB
29.07 KB

I'm not entirely convinced that #68 makes things easier than the current state of the patch, anyway, here is a patch for just that.

dawehner’s picture

But for sure I totally get the point in not having to load the view object on cached pages.

Fabianx’s picture

Overall nice, some nit-picks on how #68 was implemented.

  1. +++ b/core/modules/views/views.module
    @@ -309,9 +309,10 @@ function views_page_display_pre_render(array $element) {
       $route = \Drupal::routeMatch()->getRouteObject();
       if ($route && $route->hasDefault('view_id')) {
    -    $view = Views::getView($route->getDefault('view_id'));
    -    $view->setDisplay($route->getDefault('display_id'));
    -    views_add_contextual_links($element, 'page', $view, $view->current_display);
    +    $element['#view_id'] = $route->getDefault('view_id');
    +    $element['#view_display_show_admin_links'] = $route->getOption('_view_display_show_admin_links');
    +    $element['#view_display_plugin_id'] = $route->getDefault('display_id');
    

    The idea was that $element would have that already cached, just need to find the right element on the page.

  2. +++ b/core/modules/views/views.module
    @@ -404,15 +405,20 @@ function views_preprocess_html(&$variables) {
    +  $render_element['#cache_properties'] = ['view_id', 'view_display_show_admin_links', 'view_display_plugin_id'];
    

    Needs to use '#view_id', '#view_display_show_admin_links', etc.

    And this is likely not the right place for setting the #cache_properties value.

dawehner’s picture

just need to find the right element on the page.

Well, the question is whether we can assume that the main content is placed in a particular level on the page render array, I guess we can?

It might be easily the case that maybe that regions are rendered before the $page array is assembled together

Fabianx’s picture

That is why I voted for simplicity with adding _views_get_page_render_array / _views_set_page_render_array (@internal).

dawehner’s picture

FileSize
7.47 KB

First trying to make blocks lazy, I guess we actually don't need that because block caching already does all we need?

Wim Leers’s picture

First trying to make blocks lazy, I guess we actually don't need that because block caching already does all we need?

Correct. In \Drupal\block\BlockViewBuilder::viewMultiple():

        '#pre_render' => [
          [$this, 'buildBlock'],
        ],
dawehner’s picture

Status: Needs work » Needs review
FileSize
32.18 KB
5.99 KB

This is work to use render caching for the rest export.

Wim Leers’s picture

Is this patch feature-complete? Can you list what still needs to happen for it to be feature-complete?

dawehner’s picture

FileSize
40.2 KB
9.44 KB

Now also with some basic work to get rid of output caching.

dawehner’s picture

Issue summary: View changes

Listed a couple of points.

Fabianx queued 84: 2381277-83.patch for re-testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
49.14 KB
10.42 KB

Some progress.

dawehner’s picture

Status: Needs work » Needs review
FileSize
45.17 KB
4.88 KB

doh!

dawehner’s picture

Status: Needs work » Needs review
FileSize
50.28 KB
8.7 KB

Continous work

nagraj404’s picture

Drupal Subuser Module
i am not able to assign automatically Parent roles to subuser , while creating sub user .
But same working in D6 but not in D7
Please help me

plach’s picture

@nagraj404:

This is a completely unrelated issue, please use the search available at https://www.drupal.org/project/issues/subuser?categories=All to see whether your issue has already been responded, otherwise create a support request for the Subuser project at:

https://www.drupal.org/node/add/project-issue/subuser

plach’s picture

Issue summary: View changes
FileSize
1.04 MB

Improved issue summary :)

plach’s picture

A code review to get familiarity with last patches:

  1. +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
    @@ -306,15 +313,10 @@ public function render() {
    +    $build['#cache-content-type'] = $this->getMimeType();
    

    Why are we using inconsistent separators in the property name? Shouldn't this be #cache_content_type?

  2. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2136,16 +2137,25 @@ public function render() {
    +  protected function renderSetCacheData(array &$element) {
    

    Missing PHP doc

  3. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2136,16 +2137,25 @@ public function render() {
    -    // If the output is a render array, add cache tags, regardless of whether
    -    // caching is enabled or not; cache tags must always be set.
    +      // If the output is a render array, add cache tags, regardless of whether
    +      // caching is enabled or not; cache tags must always be set.
    

    Huh?

  4. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2314,8 +2324,8 @@ public function execute() { }
    +  public function buildRenderable(array $args = [], $caching = TRUE) {
    

    Mabye just $cache, intended as a verb?

  5. +++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
    @@ -203,6 +206,10 @@ protected function getRoute($view_id, $display_id) {
    +    $route->setOption('returns_response', !empty($this->getPluginDefinition()['returns_response']));
    

    Would it make sense to use an imperative form: return_response?

  6. +++ b/core/modules/views/src/Routing/ViewPageController.php
    @@ -65,31 +66,20 @@ public static function create(ContainerInterface $container) {
           // Allow parameters be pulled from the request.
           // The map stores the actual name of the parameter in the request. Views
           // which override existing controller, use for example 'node' instead of
           // arg_nid as name.
    

    This comment seems to need an update too.

  7. +++ b/core/modules/views/src/Routing/ViewPageController.php
    @@ -104,12 +94,12 @@ public function handle($view_id, $display_id, Request $request, RouteMatchInterf
    +    $class = $route->getOption('_view_display_plugin_class');
    +    if ($route->getOption('returns_response')) {
    +      return $class::returnResponse($view_id, $display_id, $args);
         }
         else {
    -      return $view->buildRenderable($display_id, $args);
    +      return $class::buildBasicRenderable($view_id, $display_id, $args);
         }
    

    Given that both methods are static and take the same arguments, do we really need to differentiate them? The actual return value seems an implementation detail to me. Could it be just a static ::handle() method in both cases?

  8. +++ b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
    @@ -32,14 +32,20 @@
    +    $copy = $build;
    

    In other places we use $original instead of $copy :)

  9. +++ b/core/modules/views/src/ViewExecutable.php
    @@ -1492,12 +1481,14 @@ public function getCacheTags() {
    +   * @param bool $caching
    ...
    +  public function buildRenderable($display_id = NULL, $args = array(), $caching = TRUE) {
    

    Same as above

  10. +++ b/core/modules/views/src/ViewExecutable.php
    @@ -1492,12 +1481,14 @@ public function getCacheTags() {
    -   * @return array|null
    -   *   A renderable array with #type 'view' or NULL if the display ID was
    -   *   invalid.
    +   * @return array|null A renderable array with #type 'view' or NULL if the display ID was
    +   * A renderable array with #type 'view' or NULL if the display ID was
    +   * invalid.
    

    Oops :)

dawehner’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
59.46 KB
9.39 KB

Ignoring the reviews so far, just posting work on rss/feed related code.

plach’s picture

+++ b/core/modules/node/src/Plugin/views/row/Rss.php
@@ -60,12 +62,26 @@ class Rss extends RssPluginBase {
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {

PHP doc

dawehner’s picture

Status: Needs work » Needs review
FileSize
66.08 KB
11.3 KB

Some intermediate work.

mglaman queued 103: 2381277-103.patch for re-testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
65.31 KB
5.22 KB

Fixed hopefully most of the feed related failures

dawehner’s picture

Status: Needs work » Needs review
FileSize
585 bytes
65.08 KB

mh

plach’s picture

+++ b/core/modules/views/src/Plugin/views/cache/Time.php
@@ -177,10 +177,13 @@ protected function cacheExpire($type) {
+  protected function cacheSetMaxAge($type) {

This name is a bit confusing: shouldn't it be ::getCacheMaxAge()?

dawehner’s picture

Status: Needs work » Needs review
FileSize
67.16 KB
6.17 KB

Just some general work.

Wim Leers’s picture

#84 is very exciting!


#98: :D plach++


#112: Wow, down to 39 fails! Great work, @dawehner!

dawehner’s picture

Status: Needs work » Needs review
FileSize
86.5 KB
23.62 KB

More work, now working on getting the contextual links working

dawehner’s picture

Status: Needs work » Needs review
FileSize
90.22 KB
9.2 KB

Alright, let's fix contextual links.

disasm’s picture

Category: Task » Bug report

I'm reclassifying this as a bug because of the security issue mentioned in #10.

Fabianx’s picture

Title: Make Views use render caching » Make Views use render caching and remove the rendered output caching

Re-titling based on #118

Wim Leers’s picture

Title: Make Views use render caching and remove the rendered output caching » Make Views use render caching and remove Views' own "output caching"

Clarifying the title further.

disasm’s picture

I'm not an expert on views or caching, so I can't give an in-depth review of this, but here's a few things that jumped out at me:

  1. +++ b/core/modules/comment/src/Plugin/views/row/Rss.php
    @@ -84,7 +84,7 @@ public function render($row) {
    +    $item_text = [];
    
    @@ -115,14 +115,16 @@ public function render($row) {
    +      $item_text = $build;
    ...
         $item->description = $item_text;
    
    +++ b/core/modules/node/src/Plugin/views/row/Rss.php
    @@ -111,7 +130,7 @@ public function render($row) {
    +    $item_text = [];
    
    @@ -154,22 +173,23 @@ public function render($row) {
    +      $item_text = $build;
    ...
    +    $item->description = $item_text;
    

    since $item_text is no longer concatenated text, would it make sense to rename it something more explanatory of what it's storing?

  2. +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
    @@ -273,28 +273,35 @@ public function collectRoutes(RouteCollection $collection) {
    +    $renderer = \Drupal::service('renderer');
    
    +++ b/core/modules/views/src/Plugin/views/display/Feed.php
    @@ -67,25 +69,38 @@ protected function getType() {
    +    $renderer = \Drupal::service('renderer');
    

    Can this service be injected?

  3. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
    @@ -45,7 +45,7 @@ protected function setUp() {
    +  public function ptestFieldBasedViewCacheTagsWithCachePluginNone() {
    
    @@ -65,7 +65,7 @@ public function testFieldBasedViewCacheTagsWithCachePluginTag() {
    +  public function ptestFieldBasedViewCacheTagsWithCachePluginTime() {
    
    @@ -135,14 +140,14 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
    +  public function ptestEntityBasedViewCacheTagsWithCachePluginNone() {
    ...
    +  public function ptestEntityBasedViewCacheTagsWithCachePluginTag() {
    
    @@ -155,7 +160,7 @@ public function testEntityBasedViewCacheTagsWithCachePluginTag() {
    +  public function ptestEntityBasedViewCacheTagsWithCachePluginTime() {
    
    @@ -205,7 +210,7 @@ protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) {
    +  public function ptestBuildRenderableWithCacheContexts() {
    
    @@ -218,7 +223,7 @@ public function testBuildRenderableWithCacheContexts() {
    +  public function ptestViewAddCacheMetadata() {
    

    Why are these functions renamed with a prefix 'p'.

plach’s picture

  1. +++ b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php
    @@ -76,9 +76,13 @@ protected function assertPageCacheContextsAndTags(Url $url, array $expected_cont
    +  protected function debugCacheTags($actual_tags, $expected_tags) {
    

    Missing PHP doc

  2. +++ b/core/modules/views/src/Routing/ViewPageController.php
    @@ -100,7 +100,11 @@ public function handle($view_id, $display_id, RouteMatchInterface $route_match)
    +      views_set_curre
    

    WAT? :)

  3. +++ b/core/modules/views/src/Tests/Plugin/CacheTagTest.php
    @@ -88,93 +90,118 @@ protected function setUp() {
    +  protected function getRenderCache(ViewExecutable $view) {
    

    Missing PHP doc. Also, can we name this getRenderCacheEntry? Otherwise it seems we are returning the service.

  4. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
    @@ -45,7 +45,7 @@ protected function setUp() {
    +  public function ptestFieldBasedViewCacheTagsWithCachePluginNone() {
    
    @@ -65,7 +65,7 @@ public function testFieldBasedViewCacheTagsWithCachePluginTag() {
    +  public function ptestFieldBasedViewCacheTagsWithCachePluginTime() {
    
    @@ -135,14 +140,14 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
    +  public function ptestEntityBasedViewCacheTagsWithCachePluginNone() {
    ...
    +  public function ptestEntityBasedViewCacheTagsWithCachePluginTag() {
    
    @@ -155,7 +160,7 @@ public function testEntityBasedViewCacheTagsWithCachePluginTag() {
    +  public function ptestEntityBasedViewCacheTagsWithCachePluginTime() {
    
    @@ -205,7 +210,7 @@ protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) {
    +  public function ptestBuildRenderableWithCacheContexts() {
    
    @@ -218,7 +223,7 @@ public function testBuildRenderableWithCacheContexts() {
    +  public function ptestViewAddCacheMetadata() {
    

    Cheater :)

  5. +++ b/core/modules/views/views.theme.inc
    @@ -917,7 +917,12 @@ function template_preprocess_views_view_row_rss(&$variables) {
    +  $variables['description'] = $renderer->renderPlain($item->description);
    

    Judging from the comment, this seems to require regular metadata bubbling. Should we use ::render() instead?

dawehner’s picture

Status: Needs work » Needs review
FileSize
92.38 KB
17.6 KB

Thank you for your review, tried to address them.

This should remove quite some of the failures again, puh!
Looking forward to another 100K patch :(

Status: Needs review » Needs work

The last submitted patch, 124: 2381277-124.patch, failed testing.

YesCT’s picture

@disasm tests renamed to start with p ... are skipped.

dawehner’s picture

Status: Needs work » Needs review
FileSize
92.44 KB
798 bytes

Hopefully one less failure.

@disasm tests renamed to start with p ... are skipped.

Yeah that is a thing I do in order to speed up tests during debugging
failures.

Status: Needs review » Needs work

The last submitted patch, 127: 2381277-126.patch, failed testing.

plach’s picture

  1. +++ b/core/modules/aggregator/src/Tests/AddFeedTest.php
    @@ -28,6 +28,7 @@ public function testAddFeed() {
    +    debug($feed->getWebsiteUrl());
    

    hm

  2. +++ b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php
    @@ -79,6 +79,14 @@ protected function assertPageCacheContextsAndTags(Url $url, array $expected_cont
       protected function debugCacheTags($actual_tags, $expected_tags) {
    

    Sorry for being a PITA, I just realized this is missing type hints.

  3. +++ b/core/modules/views/src/Views.php
    @@ -56,6 +56,13 @@ class Views {
    +   * The current page render array.
    +   *
    +   * @var array
    +   */
    +  protected static $pageRenderArray;
    +
    +  /**
        * Returns the views data service.
        *
        * @return \Drupal\views\ViewsData
    @@ -534,4 +541,22 @@ protected static function t($string, array $args = array(), array $options = arr
    
    @@ -534,4 +541,22 @@ protected static function t($string, array $args = array(), array $options = arr
         return static::$translationManager->translate($string, $args, $options);
       }
     
    +  /**
    +   * Sets the current page views render array.
    +   */
    +  public static function &setPageRenderArray(array &$element = NULL) {
    +    if (isset($element)) {
    +      static::$pageRenderArray = &$element;
    +    }
    +
    +    return static::$pageRenderArray;
    +  }
    +
    +  /**
    +   * Gets the current views page render array.
    +   */
    +  public static function &getPageRenderArray() {
    +    return static::$pageRenderArray;
    +  }
    +
    

    Why don't we define these on the Page display plugin?

    Missing @param, btw.

  4. +++ b/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php
    @@ -43,6 +43,13 @@ class ViewPageControllerTest extends UnitTestCase {
    +  protected $defaultRenderArray = [
    

    Missing PHP doc :)

  5. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2136,16 +2137,25 @@ public function render() {
    +  protected function renderSetCacheData(array &$element) {
    

    Same here :)

damiankloip’s picture

  1. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2136,16 +2137,25 @@ public function render() {
    +    $element['#cache']['max-age'] = Cache::mergeMaxAges($element['#cache']['max-age'], $cache->getCacheMaxAge());
    

    This would mean that a display can essentially override the max-age, if it's lower than the cache setting. That doesn't seem like the right behaviour?

  2. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2314,18 +2324,43 @@ public function execute() { }
    +      $build['#cache'] = $this->view->element['#cache'];
    

    Where is the use case of setting ->element['#cache'] ?

  3. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2314,18 +2324,43 @@ public function execute() { }
    +    $build['#cache_properties'] =  ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'];
    

    Is this a views specific thing? Can it live within the #cache array anyway?

  4. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
    @@ -469,10 +469,15 @@ public function execute();
    +  public function buildRenderable(array $args = [], $cache = TRUE);
    

    Where and when do you opt out? Wouldn't the cache plugin control this? i.e. if it's 'none' do nothing.

  5. +++ b/core/modules/views/src/Plugin/views/display/Page.php
    @@ -113,14 +114,27 @@ protected function defineOptions() {
    +  public static function buildBasicRenderable($view_id, $display_id, array $args = [], Route $route = NULL) {
    ...
    +    if ($route) {
    

    Do we need a route parameter? Shouldn't Page always we using a route, and just assume this behaviour in this overridden method?

  6. +++ b/core/modules/views/src/Plugin/views/display/Page.php
    @@ -113,14 +114,27 @@ protected function defineOptions() {
    -    views_set_page_view($this->view);
    

    Is there a replacement for this functionality?

  7. +++ b/core/modules/views/src/Routing/ViewPageController.php
    @@ -104,12 +94,16 @@ public function handle($view_id, $display_id, Request $request, RouteMatchInterf
    +      Views::setPageRenderArray($build);
    

    Is this the replacement for views_set_page_view?

  8. +++ b/core/modules/views/views.theme.inc
    @@ -917,7 +917,12 @@ function template_preprocess_views_view_row_rss(&$variables) {
    +  $variables['description'] = is_array($item->description) ? $renderer->render($item->description) : $item->description;
    

    Don't we create the item anyway? So we could make sure it's always an array?

damiankloip’s picture

+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -273,28 +273,35 @@ public function collectRoutes(RouteCollection $collection) {
+    $response->headers->set('Content-type', $build['view_build']['#content_type']);

Nice to see that fixed too :D

damiankloip’s picture

+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -273,28 +273,35 @@ public function collectRoutes(RouteCollection $collection) {
+    $response->headers->set('Content-type', $build['view_build']['#content_type']);

I don't think we need to set this. Symfony does it for us. See Response::prepare(). Maybe it's better to explicitly set it anyway?

dawehner’s picture

Status: Needs work » Needs review
FileSize
92.66 KB
1.85 KB

Thank you for your reviews!! This is just temporary work in order for damian to join the party.

Status: Needs review » Needs work

The last submitted patch, 133: 2381277-131.patch, failed testing.

damiankloip’s picture

Thanks! Was just going to start by adding a couple of assertions to help with our confusion over the RestExport headers, they actually work fine, as per my comment in #132

dawehner’s picture

Status: Needs work » Needs review

Let's check whether #135 provides more failures

Fabianx’s picture

From IRC:

- When someone uses setPage() we need to change the cache keys (injected data)
- When someone does not use setPage(), we set the cache context for the pager (already in place)

Same for items_per_page and offset. (and later also contexts, but other issue as broken atm. anyway)

Also a key sentence by dawehner for anyone still confused:

< dawehner> yeah cache contexts are just needed in case you start with a render array

(and don't have objects to work with)

This is key to everything.

If someone injects data in a view in that way, then the view itself is not affected and cache keys are fine, but if that should e.g. be cached again by e.g. block cache or panels cache or ... THEN the callers that injected the data into the view needs to bubble up that information that this view varies by that.

And the reason it is the callers responsibility for upper levels is:

- It is the caller who accesses e.g. the request and gets the 'page' parameter.

Rule of thumb:

"Whoever uses an external resource to make a decision or command a decision on another class and that decision varies the output, needs to specify the variation criteria, i.e. the cache context".

Status: Needs review » Needs work

The last submitted patch, 135: 2381277-134.patch, failed testing.

plach’s picture

Issue tags: +D8 Accelerate
dawehner’s picture

Status: Needs work » Needs review
FileSize
98.22 KB
14.88 KB

hm, Sorry for being a PITA, I just realized this is missing type hints.

Fixed

Why don't we define these on the Page display plugin?

Good point, moved it, btw. storm can do it.

Missing PHP doc :)

Same here :)

Fixed.

This would mean that a display can essentially override the max-age, if it's lower than the cache setting. That doesn't seem like the right behaviour?

Yeah that is the central problem, we want to have a min cache age, see issue summary.

Where is the use case of setting ->element['#cache'] ?

For example when you want to vary things via a cache context, when you don't have the view object yet

Is this a views specific thing? Can it live within the #cache array anyway?

#cache_properties is a thing which exists, but we want to store those additional information in order to make contextual links working outside of a loaded view object. Do you think this is okay?

Where and when do you opt out? Wouldn't the cache plugin control this? i.e. if it's 'none' do nothing.

Well, I thought that this method is more of an API function people could use. Cache plugins would opt out on top of that. I see your point thought, mhhh

Do we need a route parameter? Shouldn't Page always we using a route, and just assume this behaviour in this overridden method?

Well, technically its defined on the parent class, so additional parameters can just be optional. For other display types though, it does not make sense to add $route.

Is there a replacement for this functionality?
Is this the replacement for views_set_page_view?

Yes this is the replacement, now on the Page display class.

Don't we create the item anyway? So we could make sure it's always an array?

The aggregator implementation is a little bit odd:

  public function render($row) {
    $entity = $row->_entity;

    $item = new \stdClass();
    foreach ($entity as $name => $field) {
      // views_view_row_rss takes care about the escaping.
      $item->{$name} = $field->value;
    }

    $item->elements = array(

Do you think we should convert $item->description there to be a render array?

Status: Needs review » Needs work

The last submitted patch, 140: 2381277-140.patch, failed testing.

plach’s picture

+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -2142,6 +2142,12 @@ public function render() {
+   * Set ups the render cache information onto a view render element

<pita>
"Setups", missing trailing dot :)
</pita>

dawehner’s picture

Status: Needs work » Needs review
FileSize
99.47 KB
2.63 KB
"Setups", missing trailing dot :)

Thank you a lot for your quick reviews!!!!!!!

Wow, this debugging session needed to much time, but yeah! for IDEs show memory addresses

Status: Needs review » Needs work

The last submitted patch, 143: 2381277-143.patch, failed testing.

Wim Leers’s picture

Wow, this debugging session needed to much time, but yeah! for IDEs show memory addresses

$view->element strikes again!

dawehner’s picture

Issue summary: View changes

Working on test failures atm, some cleanup in the meantime.

dawehner’s picture

almaudoh’s picture

Issue summary: View changes

Nice IS picture. Wish all IS had pix.

dawehner’s picture

Status: Needs work » Needs review
FileSize
106.37 KB
15.8 KB

"Some" work in the meantime ...

Status: Needs review » Needs work

The last submitted patch, 149: 2381277-149.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
107.56 KB
2.16 KB

Its green!

Status: Needs review » Needs work

The last submitted patch, 151: 2381277-151.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
106.91 KB
660 bytes

This time for real

Fabianx’s picture

Nice work!

jibran’s picture

Awesome it's green.

  1. +++ b/core/modules/comment/comment.module
    @@ -211,6 +211,29 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
    +function comment_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
    

    Any reason for this change and removing it from CommentLinkBuilder CLB?

  2. +++ b/core/modules/node/src/Plugin/views/row/Rss.php
    @@ -60,15 +62,32 @@ class Rss extends RssPluginBase {
    +    $this->renderer = $renderer;
    ...
    +      $container->get('renderer')
    

    Why are we injecting renderer if we are removing calls of drupal_render_root?

  3. +++ b/core/modules/views/src/Element/View.php
    @@ -70,7 +75,16 @@ public static function preRenderViewElement($element) {
    -        views_add_contextual_links($element, 'view', $view, $view->current_display);
    +        if (empty($view->display_handler->getPluginDefinition()['returns_response'])) {
    +          $element['#view_id'] = $view->storage->id();
    +          $element['#view_display_show_admin_links'] = $view->getShowAdminLinks();
    +          $element['#view_display_plugin_id'] = $view->display_handler->getPluginId();
    +          views_add_contextual_links($element, 'view', $view->current_display);
    ...
    +      if (empty($view->display_handler->getPluginDefinition()['returns_response'])) {
    +        $element['#attributes']['class'][] = 'views-element-container';
    +        $element['#theme_wrappers'] = array('container');
    
    +++ b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
    @@ -195,14 +195,19 @@ public function blockSubmit($form, FormStateInterface $form_state) {
    -      views_add_contextual_links($output, $block_type, $this->view, $this->displayID);
    +      $output['#view_id'] = $this->view->storage->id();
    +      $output['#view_display_show_admin_links'] = $this->view->getShowAdminLinks();
    +      $output['#view_display_plugin_id'] = $this->view->display_handler->getPluginId();
    +      views_add_contextual_links($output, $block_type, $this->displayID);
    

    Can we have comments for these changes? :)

  4. +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
    @@ -191,20 +191,8 @@ public function cacheSet($type) {
    +        $expire = ($this->cacheSetMaxAge($type) === Cache::PERMANENT) ? Cache::PERMANENT : (int) $this->view->getRequest()->server->get('REQUEST_TIME') + $this->cacheSetMaxAge($type);
    

    Can't we just use REQUEST_TIME instead of (int) $this->view->getRequest()->server->get('REQUEST_TIME')

  5. +++ b/core/modules/views/src/ViewExecutable.php
    @@ -490,21 +491,30 @@ public function setArguments(array $args) {
    +    // Calls like ::unserialize() might setup a $page might call this method
    +    // without a proper $page.
    

    Doesn't make sense at all.

dawehner’s picture

Issue summary: View changes
FileSize
105.23 KB
4.38 KB

Thank you jibran for your super fast review response time!

Any reason for this change and removing it from CommentLinkBuilder CLB?

I'll put that into the issue summary to explain that to others.

Why are we injecting renderer if we are removing calls of drupal_render_root?

Good catch! Fixed that ...

Can't we just use REQUEST_TIME instead of (int) $this->view->getRequest()->server->get('REQUEST_TIME')

Well, I just wanted to do it the right way ... see #2459155: Remove REQUEST_TIME from bootstrap.php

Doesn't make sense at all.

Fair :) Let's update it a bit.

Status: Needs review » Needs work

The last submitted patch, 156: 2381277-156.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
116.39 KB
17.68 KB
  • Adapt the test views cache test coverage to test render caching
  • Done

    Here is more test coverage, puh, things aren't trivial always.

    dawehner’s picture

    Issue summary: View changes

    Updating the remaining tasks ...

    Status: Needs review » Needs work

    The last submitted patch, 158: 2381277-158.patch, failed testing.

    webchick queued 158: 2381277-158.patch for re-testing.

    webchick’s picture

    Status: Needs work » Needs review
    dawehner’s picture

    Issue summary: View changes
    FileSize
    127.03 KB
    13.62 KB

    Went through the list of plugins defined in views.

    Bugs I realized:

    • The Serializer style plugin does not expose the request format as cache context. Added some test coverage for that.
    • Various things still lack proper cache contexts, see @todos added by this interdiff. Yes quite some of them are actually security issues like access checking (permissions, roles)!!
    • Enabled tag based caching for the frontpage view, let's see what fails based upon that
    plach’s picture

    (Crosspost, reviewed #158)

    Additional test coverage looks great. Some minor stuff:

    1. +++ b/core/modules/node/src/Tests/Views/FrontPageTest.php
      @@ -54,7 +54,7 @@ protected function setUp() {
      +  public function ptestFrontPage() {
      
      @@ -173,7 +173,7 @@ protected function assertNotInResultSet(ViewExecutable $view, array $not_expecte
      +  public function ptestAdminFrontPage() {
      
      @@ -189,7 +189,7 @@ public function testAdminFrontPage() {
      +  public function ptestCacheTagsWithCachePluginNone() {
      
      @@ -197,7 +197,7 @@ public function testCacheTagsWithCachePluginNone() {
      +  public function ptestCacheTagsWithCachePluginTag() {
      

      Ha!

    2. +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
      @@ -178,6 +183,75 @@ public function testSerializerResponses() {
      +  public function testRestRenderCaching() {
      

      PHP doc

    3. +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
      @@ -178,6 +183,75 @@ public function testSerializerResponses() {
      +    // Create a new entity and ensure that the cache tags are taken over.
      

      Can we explain that we are removing one item because we display only ten?

    4. +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
      @@ -178,6 +183,75 @@ public function testSerializerResponses() {
      +    entity_create('entity_test', array('name' => 'test_11', 'user_id' => $this->adminUser->id()))->save();
      

      EntityTest::create()?

    5. +++ b/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php
      @@ -27,7 +27,8 @@ public static function getLabel() {
      +    // Return a constant value.
      +    return '299792458';
      

      Why? To avoid random failures?

    6. +++ b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
      @@ -105,4 +106,44 @@ protected function assertViewsCacheTags(ViewExecutable $view, $expected_results_
      +  protected function assertViewsCacheTagsFromStaticRenderArray(ViewExecutable $view, array $expected_render_array_cache_tags, $views_caching_is_enabled) {
      

      PHP doc

    7. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
      @@ -84,7 +84,7 @@ public function testFieldBasedViewCacheTagsWithCachePluginTime() {
      -   */
      +  */
      

      Mmh, no

    8. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
      @@ -126,6 +129,7 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
      +    // $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_2, $do_assert_views_caches);
      

      Hm

    9. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
      @@ -134,6 +138,8 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
      +    $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_1, $do_assert_views_caches);
      +    $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_1, $do_assert_views_caches);
      

      Double call

    plach’s picture

    1. +++ b/core/modules/user/src/Plugin/views/access/Permission.php
      @@ -132,4 +133,19 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
      +  public function getCacheContexts() {
      +    // @todo Write explicit test coverage that the variation works.
      +    return ['user.permissions'];
      +  }
      +
      

      Wondering whether #2493033: Make 'user.permissions' a required cache context will make all these unnecessary. It's not critical, though.

    2. +++ b/core/modules/views/src/Plugin/views/sort/Random.php
      @@ -37,6 +37,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
      +    // @todo Write test coverage to ensure that opt out of caching works.
      

      Is this opting out from display-level render caching or whole render caching? I'm asking because row caching should still work.

    plach’s picture

    Blocked on #2498857: Support min-age in render caching, not postponing since we have other areas to focus on.

    dawehner’s picture

    Thanks again for the quite quick review!

    We discussed about min age and moved it first to its own subissue: #2498857: Support min-age in render caching

    Can we explain that we are removing one item because we display only ten?

    Good point.

    Why? To avoid random failures?

    Well, to have something which can be used between requests, for example inside the test code and the actual site.
    Let me explain that a bit better.

    Is this opting out from display-level render caching or whole render caching? I'm asking because row caching should still work.

    You are absolutely right, this just takes care about the order.

    +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
    @@ -126,6 +129,7 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
    + // $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_2, $do_assert_views_caches);
    Hm

    Ah, let me manipulate the current request in the test.

    plach’s picture

    Issue summary: View changes

    Form the interdiff it seems a few items from #164 were not addressed: 1, 7, 9.

    dawehner’s picture

    Form the interdiff it seems a few items from #164 were not addressed: 1, 7, 9.

    Well, 1, 7 got addressed in #163 already and 9 is part of the interdiff:

        $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_1, $do_assert_views_caches);
    -    $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_1, $do_assert_views_caches);
         $this->assertTrue(strpos($build['#markup'], $random_name) !== FALSE);
    
    dawehner’s picture

    Opened up a follow up around the discussion @berdir, @catch, @msonnabaum, @plach had in IRC: #2498969: Time-based cached views get invalidated too frequently

    plach’s picture

    Sorry, I need a pair of glasses :)

    The last submitted patch, 163: 2381277-163.patch, failed testing.

    dawehner’s picture

    Issue summary: View changes
    FileSize
    134.5 KB
    7.76 KB

    There are signs that you sit too much in front of the computer, see interdiff

    Added the test coverage for random sort, role and permission access checking.

    The last submitted patch, 167: 2381277-166.patch, failed testing.

    dawehner’s picture

    FileSize
    134.55 KB
    1.98 KB

    Fixed the failures of #167

    The last submitted patch, 173: 2381277-173.patch, failed testing.

    dawehner’s picture

    Issue summary: View changes

    .

    Wim Leers’s picture

    Status: Needs review » Needs work
    Related issues: +#2464427: Replace CacheablePluginInterface with CacheableDependencyInterface

    Awesome work! :) 57 files changed, 1158 insertions, 589 deletions, but most of that is due to added test coverage.

    Many nits, many minor things, a few substantial things.

    Questions:

    • Finally, we will retain the Time-based plugin, which will implement regular render caching at display-level, but will override the invalidation strategy by ignoring invalid cache tags, — where is this happening?
    • Could you add to the IS' "Review help" section explanations for the following more meta-ish questions: A) the changes to how Views' contextual links are added to the page, B) the overriding/extending of #cache[keys] in several places. (Several concrete questions in the review below.)
    • Would you say that at this point it makes more sense to have Views' plugin/handlers interfaces extending CacheablePluginInterface, hence forcing every handler/plugin to consider their cacheability impact? It seems that in this patch all remaining handlers/plugins (that didn't already provide cacheability information) now provide this metadata too.
    1. +++ b/core/lib/Drupal/Core/Render/RenderCacheInterface.php
      @@ -42,7 +42,7 @@ public function getCacheableRenderArray(array $elements);
      -   * @return array
      +   * @return array|FALSE
          *   A renderable array, with the original element and all its children pre-
          *   rendered, or FALSE if no cached copy of the element is available.
      

      Good catch! :)

    2. +++ b/core/modules/comment/comment.module
      @@ -211,6 +211,29 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
      +  foreach ($fields as $field_name => $detail) {
      +    if ($view_mode == 'rss' && $display->getComponent('links') && $entity->get($field_name)->status) {
      +      // Add a comments RSS element which is a URL to the comments of this
      +      // entity.
      +      $options = array(
      +        'fragment' => 'comments',
      +        'absolute' => TRUE,
      +      );
      +      $entity->rss_elements[] = array(
      +        'key' => 'comments',
      +        'value' => $entity->url('canonical', $options),
      +      );
      +    }
      +  }
      

      This is weird… for every comment field, if there is a 'links' display component in the entity's 'RSS' view mode and the first comment is published, then we a link to basically "http://mysite.com/node/X#comments".

      WTF? :P

      Would be great to document why it works this way. I know it's pulled out of CommentLinkBuilder, but there's zero documentation right now to explain the if- and for-statements.

    3. +++ b/core/modules/comment/src/Tests/CommentRssTest.php
      @@ -24,6 +28,23 @@ class CommentRssTest extends CommentTestBase {
      +  }
      +
      +
      +  /**
      

      Nit: Two newlines, should be one.

    4. +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
      @@ -179,6 +185,112 @@ public function testSerializerResponses() {
      +   * Tests rest export with enabled views render caching.
      

      Nits:
      s/rest/REST/
      s/with enabled views render caching/with views render caching enabled/

    5. +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
      @@ -179,6 +185,112 @@ public function testSerializerResponses() {
      +    // Ensure that there is no corresponding render cache entry yet.
      

      Nit: s/cache entry/cache item/

    6. +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
      @@ -179,6 +185,112 @@ public function testSerializerResponses() {
      +  }
      +
      +
      +  /**
      

      Nit: 2 newlines, should be one.

    7. +++ b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php
      @@ -76,9 +76,21 @@ protected function assertPageCacheContextsAndTags(Url $url, array $expected_cont
      +   * @param array $actual_tags
      ...
      +   * @param array $expected_tags
      

      Nit: s/array/string[]/

    8. +++ b/core/modules/user/src/Plugin/views/access/Role.php
      @@ -145,4 +146,19 @@ public function calculateDependencies() {
       }
      +
      

      Nit: extraneous newline.

    9. +++ b/core/modules/user/src/Tests/Views/AccessPermissionTest.php
      @@ -40,4 +41,32 @@ function testAccessPerm() {
      +    $account_switcher->switchTo($this->webUser);
      
      +++ b/core/modules/user/src/Tests/Views/AccessRoleTest.php
      @@ -92,4 +93,36 @@ function testAccessRole() {
      +    $account_switcher->switchTo($this->webUser);
      

      Hah, clever use of that service for testing :P

    10. +++ b/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php
      @@ -95,7 +95,7 @@ public function isCacheable() {
         public function getCacheContexts() {
      -    return ['url'];
      +    return ['url.query_args.' . $this->options['query_param']];
         }
      

      Yay! More precise cache contexts! Better cacheability!

    11. +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
      @@ -191,20 +191,8 @@ public function cacheSet($type) {
      -      case 'output':
      
      @@ -235,18 +223,6 @@ public function cacheGet($type) {
      -      case 'output':
      
      @@ -280,11 +256,6 @@ public function cacheFlush() {
      -   * Start caching the html head.
      -   */
      -  public function cacheStart() { }
      
      @@ -326,30 +297,6 @@ public function generateResultsKey() {
      -  public function generateOutputKey() {
      

      \o/ Yay!

    12. +++ b/core/modules/views/src/Plugin/views/cache/Time.php
      @@ -101,7 +101,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
      -    $options = array(-1 => $this->t('Never cache')) + $options + array('custom' => $this->t('Custom'));
      +    $options = array(0 => $this->t('Never cache')) + $options + array('custom' => $this->t('Custom'));
      

      Another excellent catch.

    13. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2131,21 +2132,33 @@ public function render() {
      -      '#attached' => &$this->view->element['#attached'],
      -      '#cache' => &$this->view->element['#cache'],
      -      '#post_render_cache' => &$this->view->element['#post_render_cache'],
      -    );
      +      '#attached' => &$this->view->element['#attached'],    );
      

      This looks weird. Perhaps correct, but then the formatting for the added line is definitely off.

    14. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2131,21 +2132,33 @@ public function render() {
      +   * Setups the render cache information onto a view render element
      

      s/Setups/Sets/

    15. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2131,21 +2132,33 @@ public function render() {
      +   *   The render element enhanced with cache tags, contexts and max age.
      ...
      +    $element += ['#cache' => []];
      +    $element['#cache'] += ['tags' => [], 'contexts' => [], 'max-age' => CacheBackendInterface::CACHE_PERMANENT];
       
      -    // If the output is a render array, add cache tags, regardless of whether
      -    // caching is enabled or not; cache tags must always be set.
      +      // If the output is a render array, add cache tags, regardless of whether
      +      // caching is enabled or not; cache tags must always be set.
           $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $this->view->getCacheTags());
       
      -    return $element;
      +    $display_contexts = isset($this->display['cache_metadata']['contexts']) ? (array) $this->display['cache_metadata']['contexts'] : [];
      +    $element['#cache']['contexts'] = Cache::mergeTags($element['#cache']['contexts'], $display_contexts);
      +
      +    /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
      +    $cache = $this->getPlugin('cache');
      +    $element['#cache']['max-age'] = Cache::mergeMaxAges($element['#cache']['max-age'], $cache->getCacheMaxAge());
      

      It'd be better to not have explicit code for all this, because it makes it harder to add more cacheability metadata.

      Besides that, there are indentation problems here, as well as using Cache::mergeTags() for merging cache contexts.

      I think all of this can be simplified to:

      $cacheability = new CacheableMetadata();
      $cacheability->setCacheTags($this->view->getCacheTags());
      $cacheability->setCacheContexts($this->display['cache_metadata']['contexts']);
      $cacheability->setCacheMaxAge($cache->getCacheMaxAge());
      
      $cacheability
        ->merge(CacheableMetadata::createFromRenderArray($element)
        ->applyTo($element);
      

      ?>

    16. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2314,18 +2327,53 @@ public function execute() { }
      +      // Places like \Drupal\views\ViewExecutable::setCurrentPage() set up an
      +      // additional cache context.
      +      $this->view->element['#cache']['keys'] = array_merge(['views', 'display', $this->view->element['#name'], $this->view->element['#display_id']], $this->view->element['#cache']['keys']);
      

      The comment is talking about cache contexts, but the code is dealing with cache keys.

    17. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2314,18 +2327,53 @@ public function execute() { }
      +      $this->view->element['#cache_properties'] =  ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'];
      

      Two spaces after the equals sign.

    18. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2314,18 +2327,53 @@ public function execute() { }
      +    else {
      +      unset($this->view->element['#cache']);
      

      Why unsetting #cache? Why not let cache tags be bubbled, for example?

      Needs a comment explaining the rationale.

    19. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2314,18 +2327,53 @@ public function execute() { }
      +    }
      +
      +
      +    return $this->view->element;
      

      Two newlines here.

    20. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2314,18 +2327,53 @@ public function execute() { }
      +    $build = [
      +      '#type' => 'view',
      +      '#name' => $view_id,
      +      '#display_id' => $display_id,
      +      '#arguments' => $args,
      +      '#embed' => FALSE,
             '#cache' => [
      -        'contexts' => isset($this->display['cache_metadata']['contexts']) ?  $this->display['cache_metadata']['contexts'] : [],
      +        'keys' => ['views', 'display', $view_id, $display_id],
             ],
           ];
      +
      +    $build['#cache_properties'] =  ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'];
      

      Why set the #cache_properties separately?

    21. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
      @@ -469,10 +469,15 @@ public function execute();
      +   * @return array The render array of a view.
      +   * The render array of a view.
      

      The second line needs two leading spaces.

    22. +++ b/core/modules/views/src/Plugin/views/display/Feed.php
      @@ -67,25 +69,38 @@ protected function getType() {
      +    $response = new CacheableResponse('', 200);
      +    $build['#response'] = $response;
      

      Ok, so this first creates an empty response, so that the rendering logic can access the Response object via the #response property on the render array.

      Why? Needs documentation.

    23. +++ b/core/modules/views/src/Plugin/views/display/Page.php
      @@ -113,14 +149,27 @@ protected function defineOptions() {
      +  }
      +
      +
      +  /**
      

      Nit: two newlines, should be one.

    24. +++ b/core/modules/views/src/Plugin/views/sort/Random.php
      @@ -26,6 +26,7 @@ public function usesGroupBy() {
      +    $this->view->element['#cache']['max-age'] = 0;
      

      … and this is why we really should do #2464427: Replace CacheablePluginInterface with CacheableDependencyInterface; then Views plugins/handlers can specify a max-age!

      IMHO needs a @todo for that, to remove this direct manipulation of $this->view->element.

    25. +++ b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
      @@ -53,35 +67,97 @@ protected function assertViewsCacheTags(ViewExecutable $view, $expected_results_
      +   * @param array $expected_render_array_cache_tags
      

      Nit: s/array/string[]/

    26. +++ b/core/modules/views/src/Tests/Handler/SortRandomTest.php
      @@ -26,10 +28,17 @@ class SortRandomTest extends ViewUnitTestBase {
      +   * In total we have then 60 entries, which makes a probability of a collision
      +   * of 1/60!, which is around 1/1E80, which is higher than the estimated amount
      +   * of protons / electrons in the observable universe, also called the eddington
      +   * number.
      +   *
      +   * @see http://en.wikipedia.org/wiki/Eddington_number
      

      Haha, love it :D

      Nit: 80 cols exceeded at line 35.

    27. +++ b/core/modules/views/src/Tests/Handler/SortRandomTest.php
      @@ -96,4 +105,39 @@ public function testRandomOrdering() {
      +   * The random sorting should opt out of caching by defining a max age 0.
      

      Nit: s/max age 0/max age of 0/

    28. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
      @@ -84,7 +84,7 @@ public function testFieldBasedViewCacheTagsWithCachePluginTime() {
      -   */
      +  */
      

      Nit: accidental change that should be reverted.

    29. +++ b/core/modules/views/src/ViewExecutable.php
      --- a/core/modules/views/src/Views.php
      +++ b/core/modules/views/src/Views.php
      
      +++ b/core/modules/views/src/Views.php
      +++ b/core/modules/views/src/Views.php
      @@ -8,6 +8,7 @@
      
      @@ -8,6 +8,7 @@
       namespace Drupal\views;
       
       use Drupal\Component\Utility\SafeMarkup;
      +use Drupal\views\Plugin\views\display\Page;
       
       /**
        * Static service container wrapper for views.
      

      Unused 'use' statement.

    30. +++ b/core/modules/views/views.module
      @@ -410,15 +414,23 @@ function views_preprocess_html(&$variables) {
      +  $view_element['#cache_properties'] = ['view_id', 'view_display_show_admin_links', 'view_display_plugin_id'];
      

      Is this really necessary here? Could use a comment explaining why if "yes".

    31. +++ b/core/modules/views/views.theme.inc
      @@ -917,7 +917,12 @@ function template_preprocess_views_view_row_rss(&$variables) {
      +  // We render the item description it might contain entities, which attach
      +  // rss elements via hook_entity_view.
      

      Difficult to parse.

    plach’s picture

    1. +++ b/core/modules/user/src/Tests/Views/AccessPermissionTest.php
      @@ -40,4 +41,32 @@ function testAccessPerm() {
      +    $build = DisplayPluginBase::buildBasicRenderable('test_access_perm', 'default');
      +
      +    // First access as user without access, then with access.
      +    $account_switcher->switchTo($this->normalUser);
      +    $result = $renderer->renderPlain($build);
      +    $this->assertNotEqual($result, '');
      +
      +    $build = DisplayPluginBase::buildBasicRenderable('test_access_perm', 'default');
      +    $account_switcher->switchTo($this->webUser);
      ...
      +    $this->assertEqual($result, '');
      
      +++ b/core/modules/user/src/Tests/Views/AccessRoleTest.php
      @@ -92,4 +93,36 @@ function testAccessRole() {
      +    $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
      +
      +    // First access as user without access, then with access.
      +    $account_switcher->switchTo($this->normalUser);
      +    $result = $renderer->renderPlain($build);
      +    $this->assertNotEqual($result, '');
      +
      +    $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
      +    $account_switcher->switchTo($this->webUser);
      +    $result = $renderer->renderPlain($build);
      +    $this->assertEqual($result, '');
      

      Übernitpick (trying to win the "nitpick of the month" context):

      It would be more readable if this line were below the comment so we'd have two symmetric blocks of 4 lines implementing symmetric logic :)

    2. +++ b/core/modules/user/src/Tests/Views/AccessRoleTest.php
      @@ -92,4 +93,36 @@ function testAccessRole() {
      +    $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
      +
      +    // First access as user without access, then with access.
      +    $account_switcher->switchTo($this->normalUser);
      +    $result = $renderer->renderPlain($build);
      +    $this->assertNotEqual($result, '');
      +
      +    $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
      +    $account_switcher->switchTo($this->webUser);
      +    $result = $renderer->renderPlain($build);
      +    $this->assertEqual($result, '');
      

      Same here

    3. +++ b/core/modules/views/src/Tests/Handler/SortRandomTest.php
      @@ -26,10 +28,17 @@ class SortRandomTest extends ViewUnitTestBase {
      +   * In total we have then 60 entries, which makes a probability of a collision
      +   * of 1/60!, which is around 1/1E80, which is higher than the estimated amount
      +   * of protons / electrons in the observable universe, also called the eddington
      +   * number.
      +   *
      +   * @see http://en.wikipedia.org/wiki/Eddington_number
      

      dawehner++

    4. +++ b/core/modules/views/src/Tests/Handler/SortRandomTest.php
      @@ -96,4 +105,39 @@ public function testRandomOrdering() {
      +   * The random sorting should opt out of caching by defining a max age 0.
      

      Can we add a note that this is not supposed to opt out from row render caching?

    dawehner’s picture

    Issue summary: View changes
    Status: Needs work » Needs review
    FileSize
    134.03 KB
    12.02 KB

    Finally, we will retain the Time-based plugin, which will implement regular render caching at display-level, but will override the invalidation strategy by ignoring invalid cache tags, — where is this happening?

    Given that we don't change the behaviour of the time based cache plugin in this issue, we should do that as part of #2498969: Time-based cached views get invalidated too frequently

    Could you add to the IS' "Review help" section explanations for the following more meta-ish questions: A) the changes to how Views' contextual links are added to the page, B) the overriding/extending of #cache[keys] in several places. (Several concrete questions in the review below.)

    Answered ...

    Would you say that at this point it makes more sense to have Views' plugin/handlers interfaces extending CacheablePluginInterface, hence forcing every handler/plugin to consider their cacheability impact? It seems that in this patch all remaining handlers/plugins (that didn't already provide cacheability information) now provide this metadata too.

    Not as part of this issue, but still, we need to figure out the different levels, like render vs. result caching ...

    This is weird… for every comment field, if there is a 'links' display component in the entity's 'RSS' view mode and the first comment is published, then we a link to basically "http://mysite.com/node/X#comments".

    WTF? :P

    Would be great to document why it works this way. I know it's pulled out of CommentLinkBuilder, but there's zero documentation right now to explain the if- and for-statements.

    No there is in HEAD, see review section, its pretty epic, that it actually works.

    Another excellent catch.

    Well, that was simply a test failure ...

    s/Setups/Sets/

    I'll just try it long enough, until its part of the english language.

    This looks weird. Perhaps correct, but then the formatting for the added line is definitely off.

    Well yeah, there is no reason to for example post_render_cache both on the view element and the view_build twice, you just need it either on one or the other.

    I think all of this can be simplified to:

    That is pretty cool!

    Why unsetting #cache? Why not let cache tags be bubbled, for example?
    Needs a comment explaining the rationale.

    This is tricky, but well, we want to certainly unset the cache keys, as we don't want the element to be cached. Let's try that.

    Why set the #cache_properties separately?

    Fixed that

    Is this really necessary here? Could use a comment explaining why if "yes".

    No idea, I'll just ignore that now :P

    Difficult to parse.

    Added ...

    Status: Needs review » Needs work

    The last submitted patch, 180: 2381277-179.patch, failed testing.

    dawehner’s picture

    Status: Needs work » Needs review
    FileSize
    134.72 KB
    2.31 KB

    Let's fix it ...

    Wim Leers’s picture

    Status: Needs review » Needs work

    Interdiff review:

    1. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2140,25 +2141,23 @@ public function render() {
      +    $cacheability->setCacheTags($this->view->getCacheTags());
      +    $cacheability->setCacheContexts($this->display['cache_metadata']['contexts']);
      +    $cacheability->setCacheMaxAge($cache->getCacheMaxAge());
      +
      +    $cacheability
      +      ->merge(CacheableMetadata::createFromRenderArray($element))
      +        ->applyTo($element);
      

      These can be chained.

      You can even chain it with the instantiation:

      $cacheability = (new CacheableMetadata())
        ->set…()
        ->set…()
        ->set…()
        ->merge(…)
        ->applyTo(…);
      
    2. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2343,15 +2343,13 @@ public function buildRenderable(array $args = [], $cache = TRUE) {
      +      // Remove the cacheablity.
      +      unset($this->view->element['#cache']['keys']);
      

      I think a more accurate comment would be something like:

      // Remove the cache keys, to ensure render caching is not triggered. We don't unset the other #cache values, to allow cacheability metadata to still be bubbled.
      
    3. plach's review in #179 is not yet addressed.

    Patch review round 2. Mostly nits, but also suggestions for better function names. IMHO the proposed function names significantly improve understandability.

    1. +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
      @@ -273,28 +273,35 @@ public function collectRoutes(RouteCollection $collection) {
      +  public static function returnResponse($view_id, $display_id, array $args = []) {
      

      buildResponse()

      Also: missing docs.

    2. +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
      @@ -179,6 +185,111 @@ public function testSerializerResponses() {
      +   * Sets up a request onto the request stack with a specified format.
      

      s/onto/on/

    3. +++ b/core/modules/views/src/Plugin/views/cache/Time.php
      @@ -177,10 +177,13 @@ protected function cacheExpire($type) {
      +  protected function cacheSetMaxAge($type) {
      

      setCacheMaxAge() would be better IMHO, but would probably be internally inconsistent.

    4. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2131,24 +2133,34 @@ public function render() {
      +   * Sets up the render cache information onto a view render element
      

      Applies the cacheability of the current view display to the given render array.

    5. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2131,24 +2133,34 @@ public function render() {
      +   *   The render element enhanced with cache tags, contexts and max age.
      

      The render array with updated cacheability metadata.

    6. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2131,24 +2133,34 @@ public function render() {
      +  protected function renderSetCacheData(array &$element) {
      

      applyDisplayCacheability()

    7. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2314,18 +2326,52 @@ public function execute() { }
      +  /**
      +   * {@inheritdoc}
      +   */
      +  public static function buildBasicRenderable($view_id, $display_id, array $args = []) {
      

      Both occurrences inherit docs, hence neither actually documents it :)

    8. +++ b/core/modules/views/src/Plugin/views/display/Feed.php
      @@ -67,25 +69,40 @@ protected function getType() {
      +    // Setup an empty response, so for example RSS can setup the proper
      

      s/Setup/Set up/
      s/setup/set/

    9. +++ b/core/modules/views/src/Plugin/views/display/Feed.php
      @@ -67,25 +69,40 @@ protected function getType() {
      +    // Content-type header.
      

      s/Content-type/Content-Type/

    10. +++ b/core/modules/views/src/Plugin/views/display/Page.php
      @@ -84,6 +92,34 @@ public static function create(ContainerInterface $container, array $configuratio
      +   *   (optional) An array array. If not specified the previous element is
      

      s/array array/page render array/

      :)

    11. +++ b/core/modules/views/src/Plugin/views/display/Page.php
      @@ -84,6 +92,34 @@ public static function create(ContainerInterface $container, array $configuratio
      +   *   A render array.
      

      s/render array/page render array/

      (For consistency with setPageRenderArray()'s docs.)

    dawehner’s picture

    Status: Needs work » Needs review
    FileSize
    137.63 KB
    12.93 KB

    Addressed the reviews of #179 and #183. Thanks a lot of for the both great and quick feedback

    It would be more readable if this line were below the comment so we'd have two symmetric blocks of 4 lines implementing symmetric logic :)

    Hei, I think this is a total valid point! Sorry no, award for you ;)

    Can we add a note that this is not supposed to opt out from row render caching?

    Sure

    setCacheMaxAge() would be better IMHO, but would probably be internally inconsistent.

    Have a look at the method, it doesn't set the max age, but rather it determines the max age for ::cacheSet()

    applyDisplayCacheability()

    Went with applyDisplayCachablityMetadata()

    Wim Leers’s picture

    Only small things left now. As far as I'm concerned, once these are addressed, this is RTBC. plach is going to do his final review round tonight/tomorrow — I defer the actual RTBC decision to him, since he knows Views better than I do.

    So glad that this is so close now! :)


    1. +++ b/core/modules/views/src/Plugin/views/display/DisplayBuildResponseInterface.php
      @@ -0,0 +1,33 @@
      + * Defines a display which returns a response object.
      

      s/response/Response/

    2. +++ b/core/modules/views/src/Plugin/views/display/DisplayBuildResponseInterface.php
      @@ -0,0 +1,33 @@
      + * HTML but rather JSON or XML.
      

      s/JSON or XML./JSON, XML, or some other format./

    3. +++ b/core/modules/views/src/Plugin/views/display/DisplayBuildResponseInterface.php
      @@ -0,0 +1,33 @@
      +interface DisplayBuildResponseInterface extends DisplayPluginInterface {
      

      I think ResponseDisplayPluginInterface might be a better name?

    4. +++ b/core/modules/views/src/Plugin/views/display/DisplayBuildResponseInterface.php
      @@ -0,0 +1,33 @@
      +   *   The view ID
      ...
      +   *   The display ID
      ...
      +   *   The arguments of the view
      

      Missing trailing periods.

    5. +++ b/core/modules/views/src/Plugin/views/display/DisplayBuildResponseInterface.php
      @@ -0,0 +1,33 @@
      +   *   The build response.
      

      s/build/built/

    6. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2135,30 +2135,27 @@ public function render() {
      -    $cacheability = new CacheableMetadata();
      -    $cacheability->setCacheTags($this->view->getCacheTags());
      -
      -    $cacheability->setCacheContexts(isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : []);
      -    $cacheability->setCacheMaxAge($cache->getCacheMaxAge());
      -
      -    $cacheability
      +    (new CacheableMetadata())
      +      ->setCacheTags($this->view->getCacheTags())
      +      ->setCacheContexts(isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : [])
      +      ->setCacheMaxAge($cache->getCacheMaxAge())
             ->merge(CacheableMetadata::createFromRenderArray($element))
      -        ->applyTo($element);
      +      ->applyTo($element);
      

      So much better!

    7. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
      @@ -463,6 +463,25 @@ public function getCacheMetadata();
      +   *   THe display ID.
      

      s/THe/The/

    8. +++ b/core/modules/views/src/Plugin/views/display/Page.php
      @@ -113,7 +113,7 @@ public static function &setPageRenderArray(array &$element = NULL) {
      +   *   The page render.
      

      The page render array.

    dawehner’s picture

    FileSize
    137.66 KB
    4.4 KB

    Yeah, another round

    damiankloip’s picture

    It seems that ViewExecutable::render() has to do a lot more every time it is called than it used to. With output caching much more was bypassed I think. How much worse is this for render time for a cached view? or is it better because of other benefits?

    damiankloip’s picture

    You can remove the getOutputKey() getter and the property now too.

    amateescu’s picture

    Here's a not so useful review, mostly because I don't know views' internals that well :)

    1. +++ b/core/lib/Drupal/Core/Render/RenderCacheInterface.php
      @@ -42,7 +42,7 @@ public function getCacheableRenderArray(array $elements);
      +   * @return array|FALSE
      

      I think the doc standard says to use only lowercase here, so "|false".

    2. +++ b/core/modules/comment/comment.module
      @@ -211,6 +211,29 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
      + * Implements hook_ENTITY_TYPE_view.
      ...
      +function comment_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
      

      We actually implement only hook_entity_view(), and we're missing the () suffix.

    3. +++ b/core/modules/node/src/Tests/Views/FrontPageTest.php
      @@ -293,6 +291,7 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
      +    $this->pass('First page');
      
      @@ -329,6 +328,7 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
      +    $this->pass('second page');
      

      Not exactly sure why these are useful, but if they are, let's capitalize the second string as well: Second page.

    4. +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
      @@ -276,25 +277,35 @@ public function collectRoutes(RouteCollection $collection) {
      +    /** @var \Drupal\Core\Render\RendererInterface $renderer */
      +    $renderer = \Drupal::service('renderer');
      

      It looks like the renderer service that is injected in this class is no longer used anywhere, so we can remove it, along with the use statement at the top of the file :)

    5. +++ b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
      @@ -195,14 +195,20 @@ public function blockSubmit($form, FormStateInterface $form_state) {
           if (!empty($output)) {
      +
             // Contextual links only work on blocks whose content is a renderable
      

      Unneeded empty line :P

    6. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -2131,24 +2133,32 @@ public function render() {
      +      '#attached' => &$this->view->element['#attached'],    );
      

      I think the trailing ");" needs to go on a separate line.

    7. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
      @@ -463,16 +463,40 @@ public function getCacheMetadata();
      +   * @param array $args
      +   *   The view arguments.
      ...
      +  public static function buildBasicRenderable($view_id, $display_id, array $args = []);
      

      Looks like the $args argument is optional, let's note that in the doc.

    8. +++ b/core/modules/views/src/Plugin/views/display/ResponseDisplayPluginInterface.php
      @@ -0,0 +1,33 @@
      +   * @param array $args
      +   *   The arguments of the view.
      ...
      +  public static function buildResponse($view_id, $display_id, array $args = []);
      

      Same here, $args is optional.

    9. +++ b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
      @@ -53,35 +67,97 @@ protected function assertViewsCacheTags(ViewExecutable $view, $expected_results_
      +  protected function assertViewsCacheTagsFromStaticRenderArray(ViewExecutable $view, array $expected_render_array_cache_tags, $views_caching_is_enabled) {
      ...
      +    $request_stack->push($request);
      

      Should we do a $request_stack->pop() at the end like we do in assertViewsCacheTags() above?

    dawehner’s picture

    FileSize
    140.54 KB
    8.91 KB

    It seems that ViewExecutable::render() has to do a lot more every time it is called than it used to. With output caching much more was bypassed I think. How much worse is this for render time for a cached view? or is it better because of other benefits?

    Talked with damian about it, and we agreed that the cached case is much faster now, as we don't even have to load the view object.

    You can remove the getOutputKey() getter and the property now too.

    Good idea!

    We actually implement only hook_entity_view(), and we're missing the () suffix.

    Ha, good point.

    Not exactly sure why these are useful, but if they are, let's capitalize the second string as well: Second page.

    Yeah I really needed it in order to figure things out. I think people will need it in the future as well.

    Should we do a $request_stack->pop() at the end like we do in assertViewsCacheTags() above?

    I decided to not do that, in order to keep things as simple as possible ..., I mean its not needed as part of the test.

    Wim Leers’s picture

    Yeah I really needed it in order to figure things out. I think people will need it in the future as well.

    +1 to pass() to make tests more debuggable. Maintainability++.

    dawehner’s picture

    FileSize
    143.57 KB
    7.35 KB

    Several additional things:

    • We forgot to add the arguments to the cache keys
    • The cache keys order is a little bit odd, let's adapt that

    Status: Needs review » Needs work

    The last submitted patch, 192: 2381277-192.patch, failed testing.

    dawehner’s picture

    Status: Needs work » Needs review
    FileSize
    143.73 KB
    2.68 KB

    Let's be green again.

    dawehner’s picture

    Added a follow up to add test coverage for render caching on ajax requests ... which doesn't work at the moment, see #2500313: Add views render caching on views ajax requests

    plach’s picture

    1. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
      @@ -475,7 +475,7 @@ public function execute();
      -   *   The view arguments.
      +   *   (optional) The view arguments.
      
      +++ b/core/modules/views/src/Plugin/views/display/ResponseDisplayPluginInterface.php
      @@ -23,7 +23,7 @@
      -   *   The arguments of the view.
      +   *   (optional) The arguments of the view.
      

      Missing default specification.

    2. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
      @@ -132,6 +138,7 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
      +    // @todo Static render arrays don't support different pages yet.
      

      Missing issue link :(

    3. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
      @@ -144,6 +151,39 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
      +    $view->setArguments([$entities[0]->id()]);
      +    $build = $this->assertViewsCacheTags($view, $tags_argument, $do_assert_views_caches, $tags_argument);
      +    $single_entity_assertions($build, $entities[0]);
      +
      +    $view->setArguments([$entities[0]->id()]);
      +    $build = $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_argument, $do_assert_views_caches);
      +    $single_entity_assertions($build, $entities[0]);
      

      Are we doing this twice to test caching? If so can we add a comment?

    4. +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
      @@ -144,6 +151,39 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
      +    $build = $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags2_argument, $do_assert_views_caches);
      +    $single_entity_assertions($build, $entities[1]);
      +
      +    $view->destroy();
      +
         }
       
      

      And... surplus blank line!
      (I'm an awarded nitpicker now :)

    plach’s picture

    Here's a final review, doing some manual testing now.

    1. +++ b/core/modules/node/src/Tests/Views/NodeLanguageTest.php
      @@ -9,6 +9,7 @@
      +use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
      
      @@ -20,6 +21,8 @@
      +  use AssertPageCacheContextsAndTagsTrait;
      

      Is this used?

    2. +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
      @@ -169,9 +142,9 @@ protected function cacheExpire($type) {
      +  protected function cacheSetMaxAge($type) {
      

      Shouldn't this be ::getCacheMaxAge()?

    3. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
      @@ -463,16 +463,40 @@ public function getCacheMetadata();
      +   * In order to be rendered cached, it includes the "#cache['keys']" as well
      +   * as did not loaded the view yet at all.
      

      This is a bit convolute. What about the following?

      "In order to be rendered cached, it includes cache keys as well as the data required to load the view on cache misses."

    4. +++ b/core/modules/views/src/Plugin/views/display/Page.php
      @@ -113,14 +149,26 @@ protected function defineOptions() {
      +  public static function buildBasicRenderable($view_id, $display_id, array $args = [], Route $route = NULL) {
      +    $build = parent::buildBasicRenderable($view_id, $display_id, $args);
      +
      +    if ($route) {
      

      I assume $route is optional only for compatiblity with the parent method. Should we throw an exception if it is not defined?

    5. +++ b/core/modules/views/src/Plugin/views/display/ResponseDisplayPluginInterface.php
      @@ -0,0 +1,33 @@
      + * Defines a display which returns a Response object.
      + *
      + * This interface is meant to be used for display plugins, which don't return
      + * HTML but rather JSON, XML or some other format.
      

      What about this?

      "[...] some other format requiring to return a response directly."

    6. +++ b/core/modules/views/src/Tests/Plugin/CacheTagTest.php
      @@ -88,93 +90,128 @@ protected function setUp() {
      +    // Mark the current request safe, in order to make render cache working, see
      +    // \Drupal\Core\Render\RenderCache::get
      +    // @see https://www.drupal.org/node/2367555
      

      Missing trailing dot in the second line. Also, do we use "@see" in inline comments? Shouldn't it be "See"?

    plach’s picture

    Ok, I did some manual testing with my various test views and things seem to work fine even in complex use cases, test coverage++

    I did a quick profiling round and with the usual test view with 8 fields and 50 rows. These are the results:

    I couldn't compare HEAD vs PATCH because I had to reinstall to apply the patch. However disabling display render cache is more or less equivalent, handle results with care ;)

    That said, numbers look pretty good :)

    Once the last reviews are addressed, I'm ready to RTBC this!

    dawehner’s picture

    Missing default specification.

    Let me quote https://www.drupal.org/coding-standards/docs

    include information about the default value only if it is not obvious from the function signature.

    Missing issue link :(

    Sure, let's add one #2500701: Make it possible to specify the page in the basic views renderable

    Are we doing this twice to test caching? If so can we add a comment?

    No, we are calling out once to assertViewsCacheTagsFromStaticRenderArray and once to assertViewsCacheTags . Added one comment.

    Is this used?

    No, let's remove it again ...

    Shouldn't this be ::getCacheMaxAge()?

    Well, the thing is that this method already exists, and is used for render caching, not for the result caching ...

    This is a bit convolute. What about the following?

    Sure

    I assume $route is optional only for compatiblity with the parent method. Should we throw an exception if it is not defined?

    Good idea!

    Missing trailing dot in the second line. Also, do we use "@see" in inline comments? Shouldn't it be "See"?

    As it turns out, its the expected behaviour, so we don't need to link to the other issue.

    damiankloip’s picture

    I'm not sure I get the profiling comparisons. I would always expect a warm cache to be way faster when compared to cold caches so that seems moot?

    A better comparison would be warm caches with this patch and before with output cache?

    Unless I miss something of course :)

    Wim Leers’s picture

    #200: I was confused too at first; asked plach for clarification.

    1. HEAD (without this patch)! Left: cold cache, right: warm cache (as in row render caching, #2450897: Cache Field views row output)
    2. HEAD + patch! Left: cold cache (same as left in 1.), right: warm cache (as in entire-view render caching, this issue)
    3. Right in 1 vs. right in 2: i.e. warm row render cache alone vs. warm entire-view render caching.
    damiankloip’s picture

    Yes. That is exactly how I interpreted it. I still don't get it. I stick by #200 :)

    plach’s picture

    Issue summary: View changes
    Status: Needs review » Reviewed & tested by the community
    FileSize
    91.82 KB

    Awesome!

    +++ b/core/modules/views/src/Plugin/views/display/Page.php
    @@ -159,6 +159,9 @@ public static function buildBasicRenderable($view_id, $display_id, array $args =
    +      throw new \BadFunctionCallException('Missing route parameters.');
    

    If we happen to reroll this \InvalidArgumentException would look more appropriate to me :)

    I'm not sure I get the profiling comparisons. I would always expect a warm cache to be way faster when compared to cold caches so that seems moot?
    A better comparison would be warm caches with this patch and before with output cache?

    Well, I could not apply the patch to my existing installation as it failed badly, so I had to reinstall. I tried to recreate more or less the same situation in the new installation (500 nodes, same test views) but it's not a fair comparison, at least wrt my old profiling runs. So I decided to compare row render caching (default scenario in HEAD) vs display render caching (default scenario with patch). I thought that getting a sense of what caching is bringing us had a value.

    Anyway, here is the profiling case suggested by Damian (HEAD vs PATCH) on the new installation, so not exactly comparable with my earlier profiling runs here and in the parent issues. Still good :)

    damiankloip’s picture

    AWESOME! Thanks plach :)

    That is what I was after, this now show the comparable (as much as possible) benefit of using render caching. It shows the improvements by render caching working a level above the actual view laye, and therefore not needing the view at all for cached output! This is RTBC from me too.

    fgm’s picture

    Congratulations to all. Just added Plach's suggested exception change. I notice we don't have a test for it : shouldn't one be added ?

    catch’s picture

    Note that 38,000 function calls is the current more-or-less baseline for a full HTML response at the moment (i.e. just node/1 with the page content type takes that much too), so this really shows no additional cost from Views at all with a warm render cache.

    dawehner’s picture

    @plach
    After manually doing some profiling for /admin/content I see similar results, even the absolute numbers are totally different
    I see something like 120ms with the patch vs. 180ms with old tag based caching.

    Btw. I tried to compare that with old D7 numbers.
    Went with the same test case /admin/content (admin_views module in use), we achieve like 150ms without time based caching and 40ms with time based caching.

    One thing which shines out of the remaining milliseconds is potentially #2500979: Try to optimize Safemarkup::set()

    dawehner’s picture

    Added a follow up for a potential speedup once we have render cache on ajax requests

    plach’s picture

    After manually doing some profiling for /admin/content I see similar results, even the absolute numbers are totally different

    I have a crappy MAMP stack I've never spent 1 second to optimize so, yes, my absolute numbers are not very meaningful, only percentages and function call numbers.

    Btw. I tried to compare that with old D7 numbers.
    Went with the same test case /admin/content (admin_views module in use), we achieve like 150ms without time based caching and 40ms with time based caching.

    Well, time-based caching from admin/content really does not make sense, so we are faster than D7 now :)

    Anyway, any further improvement is welcome obviously ;)

    Fabianx’s picture

    Could WimLeers and me be added to the commit credit list, please? Thanks!

    alexpott’s picture

    Status: Reviewed & tested by the community » Needs work
    Issue tags: +Needs reroll
    1. Needs reroll
    2. What fantastic test coverage this patch adds - stunning
    3. So much win removing early rendering and calls to SafeMarkup::Set()
    4. +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
      @@ -75,49 +71,6 @@ class RestExport extends PathPluginBase {
      -  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
      -    return new static(
      -      $configuration,
      -      $plugin_id,
      -      $plugin_definition,
      -      $container->get('router.route_provider'),
      -      $container->get('state'),
      -      $container->get('renderer')
      -    );
      -  }
      
      @@ -276,25 +229,35 @@ public function collectRoutes(RouteCollection $collection) {
      +    /** @var \Drupal\Core\Render\RendererInterface $renderer */
      +    $renderer = \Drupal::service('renderer');
      

      How come - is the renderer not injectable?

    5. +++ b/core/modules/simpletest/src/WebTestBase.php
      @@ -2598,6 +2621,7 @@ protected function prepareRequestForGenerator($clean_urls = TRUE, $override_serv
           $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
      +    $request->server->set('REQUEST_TIME', REQUEST_TIME);
      

      Why is this necessary... it should already be that. The code below is how $server is populated.

          $request = Request::createFromGlobals();
          $server = $request->server->all();
      

      If it is necessary it needs a comment.

    6. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
      @@ -12,6 +12,8 @@
      +use Drupal\Core\Cache\CacheBackendInterface;
      

      Not used

    7. +++ b/core/modules/views/src/Plugin/views/display/Page.php
      @@ -113,14 +149,29 @@ protected function defineOptions() {
      +      throw new \InvalidArgumentException('Missing route parameters.');
      

      As per @fgm not tested.

    8. +++ b/core/modules/views/src/Plugin/views/display/Page.php
      @@ -113,14 +149,29 @@ protected function defineOptions() {
      -    // Let the world know that this is the page view we're using.
      -    views_set_page_view($this->view);
      -
      

      The world is better off not knowing :)

    9. +++ b/core/modules/views/src/Plugin/views/style/Rss.php
      @@ -126,14 +126,14 @@ public function render() {
      -      '#rows' => SafeMarkup::set($rows),
      +      '#rows' => $rows,
      

      Can remove the use statement for SafeMarkup now.

    10. +++ b/core/modules/views/src/Routing/ViewPageController.php
      @@ -11,7 +11,9 @@
      +use Drupal\views\Views;
      

      Not used.

    11. +++ b/core/modules/views/src/Routing/ViewPageController.php
      @@ -65,31 +67,20 @@ public static function create(ContainerInterface $container) {
      -  public function handle($view_id, $display_id, Request $request, RouteMatchInterface $route_match) {
      

      Can remove the use for Request now.

    12. +++ b/core/modules/views/src/Routing/ViewPageController.php
      @@ -65,31 +67,20 @@ public static function create(ContainerInterface $container) {
      -      throw new NotFoundHttpException(SafeMarkup::format('Page controller for view %id requested, but view was not found.', array('%id' => $view_id)));
      

      Remove use SafeMarkup and use NotFoundHttpException...

    13. +++ b/core/modules/views/src/Tests/Plugin/CacheTest.php
      @@ -8,6 +8,7 @@
      +use Drupal\views\Plugin\views\display\DisplayPluginBase;
      

      Not used

    14. +++ b/core/modules/views/src/ViewExecutable.php
      @@ -490,21 +491,31 @@ public function setArguments(array $args) {
      +   * @retun $this
      

      return

    15. +++ b/core/modules/views/views.module
      @@ -410,15 +414,23 @@ function views_preprocess_html(&$variables) {
      -function views_add_contextual_links(&$render_element, $location, ViewExecutable $view, $display_id) {
      +function views_add_contextual_links(&$render_element, $location, $display_id, array $view_element = NULL) {
      

      Need to update function's doc block.

    16. +++ b/core/modules/views/src/Routing/ViewPageController.php
      @@ -104,12 +95,17 @@ public function handle($view_id, $display_id, Request $request, RouteMatchInterf
      +    if ($route->getOption('returns_response')) {
      +      /** @var \Drupal\views\Plugin\views\display\ResponseDisplayPluginInterface $class */
      +      return $class::buildResponse($view_id, $display_id, $args);
           }
           else {
      

      I think the new ResponseDisplayPluginInterface makes the returns_response annotation obsolete. Couldn't we set this option on the route if the display implements this interface without the annotation?

    Fabianx’s picture

    Issue tags: +Needs change record

    The removal of views_get_page_view / views_set_page_view is an API change however and needs a change record - unless I am mistaken.

    Overall this whole thing could use a change record on how to convert code to be fast and take care of the new pattern.

    Overall: Great patch!

    alexpott’s picture

    re #212/@Fabianx - yes it needs a CR but not for Drupal - for Views :)

    alexpott’s picture

    Adding @Fabianx and @Wim Leers to the commit credit

    lauriii’s picture

    Status: Needs work » Needs review
    Issue tags: -Needs reroll
    FileSize
    248.67 KB

    Rerolled the latest patch

    dawehner’s picture

    How come - is the renderer not injectable?

    Its a static method where this is used ...

    Why is this necessary... it should already be that. The code below is how $server is populated.

    Well its about time() vs. REQUEST_TIME(). Added it to BrowserTestBase as well and documented it.

    As per @fgm not tested.

    Sure, let's add a simple one ...

    Need to update function's doc block.

    Fixed ...

    I think the new ResponseDisplayPluginInterface makes the returns_response annotation obsolete. Couldn't we set this option on the route if the display implements this interface without the annotation?

    Added a follow up for it, ... it might require more changes than obviously needed: #2501389: Explore whether the return_response annotation can be removed and replaced by \Drupal\views\Plugin\views\display\ResponseDisplayPluginInterface

    Working on the CR now.

    dawehner’s picture

    Issue tags: -Needs change record

    Added two different change records ...

    dawehner’s picture

    FileSize
    146.22 KB
    1.48 KB

    Forgot the additional test

    lauriii’s picture

    Status: Needs review » Reviewed & tested by the community

    Patch on #218 addresses all the points on #216

    plach’s picture

    RTBC +1

    catch’s picture

    I haven't managed to review the latest patch all the way through yet, as far as I read looks (very) great though.

    One question: with render caching/tags enabled by default, is there any use now for the query/result cache? The main reason to have those was when output caching wasn't safe, but you didn't want to run the query each time. This was quite a lot of the time in 7.x.

    Now that render caching is reliable, I'm not sure how useful those are any more. Obviously this is follow-up if at all.

    dawehner’s picture

    One question: with render caching/tags enabled by default,

    That statement is not true, at least according to the patch

    Regarding result caching ... the amount of variations of render caching is potentially much higher than for result caching, as all the rendering / field access etc.
    potentially even makes things not cacheable. On the other hand, result caching is way more limited, how it can vary. Its mostly pager + exposed form + arguments + node access.

    catch’s picture

    That statement is not true, at least according to the patch

    Right I think we should aim for this though, then we could retire https://www.drupal.org/project/views_cache_bully

    Regarding result caching ... the amount of variations of render caching is potentially much higher than for result caching, as all the rendering / field access etc.
    potentially even makes things not cacheable. On the other hand, result caching is way more limited, how it can vary. Its mostly pager + exposed form + arguments + node access.

    Yes very true, most result sets/queries won't vary by permissions etc. so the separation still makes sense.

    plach’s picture

    That statement is not true, at least according to the patch

    Mmh, I didn't realize the tag-based cache plugin was not the default yet. Any reason not to do so? Maybe because we should update all the shipped views? We should open a follow-up to do that, unless we want to keep none as default, but that's not what the proposed solution states.

    catch’s picture

    Yes I also thought we'd make it the default here. I think that's fine as a follow-up just because this patch is plenty to review by itself. But we probably want to make that critical/upgrade path - we may not allow ourselves to update the shipped views on existing sites after release.

    dawehner’s picture

    Well, in my opinion you could so easy just cache too much, like when you have an exposed filter with a text field, thought yeah maybe this is something not to care about.
    This patch currently just enables it for the frontpage view.

    olli’s picture

    Status: Reviewed & tested by the community » Needs work
    1. +++ b/core/modules/comment/comment.module
      @@ -211,6 +211,29 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
      +function comment_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
      +  /** @var \Drupal\comment\CommentManagerInterface $comment_manager */
      +  $comment_manager = \Drupal::service('comment.manager');
      +  $fields = $comment_manager->getFields($entity->getEntityTypeId());
      +  foreach ($fields as $field_name => $detail) {
      +    if ($view_mode == 'rss' && $display->getComponent('links') && $entity->get($field_name)->status) {
      
      +++ b/core/modules/comment/src/CommentLinkBuilder.php
      @@ -101,19 +101,7 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
             if ($commenting_status != CommentItemInterface::HIDDEN) {
      

      We only need comment manager when $view_mode == 'rss' && $display->getComponent('links'). Any reason to not use the HIDDEN constant in the new code? After promoting a basic page the ->get($field_name) gives me "Twig_Error_Runtime: An exception has been thrown during the rendering of a template ("Field comment is unknown.") ".

    2. +++ b/core/modules/comment/src/CommentLinkBuilder.php
      @@ -101,19 +101,7 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
      -        if ($view_mode == 'rss') {
      ...
      -        elseif ($view_mode == 'teaser') {
      +        if ($view_mode == 'teaser') {
      
      @@ -167,7 +155,7 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
      -        else {
      +        elseif ($view_mode !== 'rss') {
      

      Could we instead return early when view mode is 'rss' like with search and print view modes?

    3. +++ b/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php
      @@ -95,7 +95,7 @@ public function isCacheable() {
      +    return ['url.query_args.' . $this->options['query_param']];
      

      'url.query_args.' -> 'url.query_args:'

    Berdir’s picture

    1. Nice catch, that code absolutely needs a $entity->hasField($field_name) before trying to access it.

    dawehner’s picture

    We only need comment manager when $view_mode == 'rss' && $display->getComponent('links'). Any reason to not use the HIDDEN constant in the new code? After promoting a basic page the ->get($field_name) gives me "Twig_Error_Runtime: An exception has been thrown during the rendering of a template ("Field comment is unknown.") ".

    Good catch. I went with adding $entity instanceof FieldableEntityInterface as well ...

    'url.query_args.' -> 'url.query_args:'

    Let's put that into their own dedicated issue #2501905: \Drupal\views\Plugin\views\argument_default\QueryParameter should specify a more specific cache context in order to always add test coverage and what not ...

    Status: Needs review » Needs work

    The last submitted patch, 229: 2381277-229.patch, failed testing.

    olli’s picture

    Status: Needs work » Needs review
    FileSize
    146.15 KB
    2.46 KB

    CommentRssTest passes locally with this

    dawehner’s picture

    Thank you olli!
    Do you consider this patch as RTBC now? I had a look at the interdiff, that this looked pretty sane ...

    olli’s picture

    Status: Needs review » Reviewed & tested by the community

    yes!

    alexpott’s picture

    Status: Reviewed & tested by the community » Needs work
    Issue tags: +Needs reroll

    Unfortunately needs a reroll...

    +++ b/core/modules/comment/src/CommentLinkBuilder.php
    @@ -82,11 +82,12 @@ public function __construct(AccountInterface $current_user, CommentManagerInterf
    -    if ($view_mode == 'search_index' || $view_mode == 'search_result' || $view_mode == 'print') {
    +    if ($view_mode == 'search_index' || $view_mode == 'search_result' || $view_mode == 'print' || $view_mode == 'rss') {
    

    Let's get a followup for comment to add a third party setting to views modes this is beginning to look like it is something that should be configurable.

    catch’s picture

    dawehner’s picture

    Status: Needs work » Needs review
    FileSize
    147.42 KB

    --d.o

    Status: Needs review » Needs work

    The last submitted patch, 236: 2381277-235.patch, failed testing.

    dawehner’s picture

    Status: Needs work » Needs review
    Issue tags: -Needs reroll
    FileSize
    148.3 KB
    4.19 KB

    Status: Needs review » Needs work

    The last submitted patch, 238: 2381277-238.patch, failed testing.

    alexpott’s picture

    Status: Needs work » Needs review
    FileSize
    148.96 KB
    3.42 KB

    Added olli's fixes from #231 back and fixed the debug messages because the order of arguments made no sense with the messages.

    dawehner’s picture

    Status: Needs review » Reviewed & tested by the community

    Oh yeah, sorry olli!

    • catch committed 15c848b on 8.0.x
      Issue #2381277 by dawehner, plach, damiankloip, alexpott, olli, fgm, Wim...
    catch’s picture

    Status: Reviewed & tested by the community » Fixed

    Committed/pushed to 8.0.x, thanks!

    plach’s picture

    Yay!

    Fabianx’s picture

    Congratulations!

    Wim Leers’s picture

    #206:

    Note that 38,000 function calls is the current more-or-less baseline for a full HTML response at the moment (i.e. just node/1 with the page content type takes that much too), so this really shows no additional cost from Views at all with a warm render cache.

    Indeed! :) This was one of the key goals as well, of course: not only security (i.e. use bubbled cache contexts rather than hardcoded cache context-approximations), but also pure performance, i.e. truly do no work other than building a super thin super cheap render array and then doing a render cache hit. No more initializing of all Views handlers & plugins. :)

    #211

    What fantastic test coverage this patch adds - stunning

    +1!

    #222: +1 to keeping results caching. But I wonder if it would be better to have that in contrib rather than in core? Not sure.

    #223: +1 to having views_cache_bully in core, and it actually already happened in the mean time: #2502255: Enable tags cache plugin by default for Views. :O :O :)


    Amazing work, dawehner & plach!

    Wim Leers’s picture

    While working on something else, I noticed a tiny bit of clean-up that was missed here, filed a follow-up with patch: #2507459: CachePluginBase and subclasses no longer need Renderer/RenderCache services injected (pure deleting of code :)).

    Status: Fixed » Closed (fixed)

    Automatically closed - issue fixed for 2 weeks with no activity.

    penyaskito’s picture

    Published both change records.