Problem/Motivation

There are cases when we should not rebuild the routes when saving or deleting a view:

  • You have a view that only has a block
  • You have a view with a page display, but you are not changing any option that would require a menu rebuild

The page display should be smart enough to be able to tell if the path(s) have changed. If paths or menu information has not changed, or it has no page displays at all, then menu should not be rebuilt.

Proposed resolution

  • Deprecate views_invalidate_cache() and split its logic in \Drupal\views\Views::invalidateCache() and \Drupal\views\Views::routeRebuildNeeded()
  • Introduce a new plugin method is called when the view is saved. The method should make decision whether to rebuild the routes.
  • Don't ask route rebuilding on each save, rather call each plugin to make decisions.

Remaining tasks

None.

User interface changes

None.

Introduced terminology

None.

API changes

New method \Drupal\views\Plugin\views\ViewsPluginInterface::postSaveView() with a default empty implementation in \Drupal\views\Plugin\views\PluginBase.

Data model changes

None.

Original report by @merlinofchaos

Whenever you save a view, it rebuilds the menu, even if it does not.

Something I learned in Page Manager is that you can avoid rebuilding the menu a lot of the time.

The page display should be smart enough to be able to tell if the path(s) have changed. If paths or menu information has not changed, or it has no page displays at all, then menu should not be rebuilt. This can drastically speed up view saving in these cases.

CommentFileSizeAuthor
#96 views_rebuilds_the_menu-941970-96.patch18.46 KBlendude
#96 interdiff-941970-94-96.txt4.51 KBlendude
#94 views_rebuilds_the_menu-941970-94.patch19.57 KBlendude
#94 interdiff-941970-88-94.txt5.9 KBlendude
#88 8.1.x-views_rebuilds_the_menu-941970-88.patch17.38 KBlendude
#88 8.0.x-views_rebuilds_the_menu-941970-88.patch17.38 KBlendude
#88 interdiff-941970-82-88.txt636 byteslendude
#82 8.1.x-views_rebuilds_the_menu-941970-82.patch17.79 KBlendude
#82 8.0.x-views_rebuilds_the_menu-941970-82.patch17.79 KBlendude
#82 interdiff-941970-79-82.txt805 byteslendude
#79 8.1.x-views_rebuilds_the_menu-941970-79.patch17.74 KBlendude
#77 views_rebuilds_the_menu-941970-77.patch17.74 KBlendude
#77 interdiff-941970-75-77.txt3.19 KBlendude
#75 views_rebuilds_the_menu-941970-75.patch18.43 KBlendude
#75 interdiff-941970-72-75.txt1.48 KBlendude
#72 views_rebuilds_the_menu-941970-72.patch18.33 KBlendude
#72 interdiff-941970-70-72.txt3.24 KBlendude
#70 views_rebuilds_the_menu-941970-70.patch17.43 KBlendude
#70 interdiff-941970-67-70.txt1.53 KBlendude
#67 views_rebuilds_the_menu-941970-67.patch17.25 KBlendude
#64 views_rebuilds_the_menu-941970-64.patch17.54 KBmpdonadio
#58 views_rebuilds_the_menu-941970-58.patch17.42 KBmortona2k
#51 interdiff.txt705 bytesdawehner
#51 941970-51.patch17.11 KBdawehner
#49 interdiff.txt6.29 KBdawehner
#49 941970-49.patch17.27 KBdawehner
#47 interdiff.txt3.73 KBdawehner
#47 941970-47.patch12.75 KBdawehner
#42 interdiff.txt8.56 KBdawehner
#42 941970-42.patch9.82 KBdawehner
#37 interdiff.txt777 bytesdawehner
#37 941970-37.patch8.44 KBdawehner
#37 941970-37.patch8.44 KBdawehner
#34 interdiff.txt1.52 KBdawehner
#34 941970-34.patch8.34 KBdawehner
#32 interdiff.txt3.22 KBdawehner
#32 941970-32.patch8.24 KBdawehner
#30 interdiff.txt1.35 KBdawehner
#30 941970-30.patch7.43 KBdawehner
#28 interdiff.txt1.85 KBdawehner
#28 941970-28.patch7.35 KBdawehner
#26 interdiff.txt1.26 KBdawehner
#26 941970-26.patch7.04 KBdawehner
#24 941970-24.patch6.83 KBdawehner
#14 941970-8.x.patch7.82 KBdamiankloip
#11 941970-only_rebuild_menu_for_page_displays-2.patch3.05 KBandrewbelcher
#9 941970-1-only_rebuild_menu_for_page_displays.patch3.06 KBandrewbelcher
#1 941970-views.patch2.44 KBdawehner

Issue fork drupal-941970

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

dawehner’s picture

StatusFileSize
new2.44 KB

This has to be discussed a bit

* Should cache plugins be able to hook into this process? They might store the caches somewhere.
* The block display has to clear the block caches, too.

dave reid’s picture

Status: Active » Needs review
+++ plugins/views_plugin_display_page.incundefined
@@ -503,4 +503,16 @@ class views_plugin_display_page extends views_plugin_display {
+    parent::save();

Should probably be parent::delete()?

+++ plugins/views_plugin_display_page.incundefined
@@ -503,4 +503,16 @@ class views_plugin_display_page extends views_plugin_display {
+    menu_rebuild(); // force a menu rebuild when a view is deleted.

Rather than calling a menu rebuild, it's preferred to use variable_set('menu_rebuild_needed', TRUE); which will automatically rebuild the menu on the next request.

dawehner’s picture

Status: Needs review » Needs work
+++ plugins/views_plugin_display_page.incundefined
@@ -503,4 +503,16 @@ class views_plugin_display_page extends views_plugin_display {
+  function delete() {

OMG you suck!

iamEAP’s picture

Just chiming in to note the severity of the issue. Every time we save a view, our website comes to a grinding halt. Watching the DB live indicated that it was hits to menu/cache related tables.

incursio’s picture

We are seeing the same thing in D7 ... views take forever to save, web site grinds to a halt. Looking at the semaphore table reveals that the menu_rebuild keeps firing .... sigh.

dawehner’s picture

@incursio
The real solution is not to edit views on the actual page, but using exported views or some other proper deployment functionality.

incursio’s picture

This *is* on a development machine.

jason.fisher’s picture

incursio, I temporarily resolve my problems by commenting out the menu rebuild/all cache flushing on view save and instead trigger those manually. I believe views may be triggering menu_rebuild multiple times: #1885668: Slow saving of views and site lockups

andrewbelcher’s picture

Version: 6.x-3.x-dev » 7.x-3.x-dev
Assigned: dawehner » Unassigned
Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new3.06 KB

Here is a start at something for 7.x-3.x.

I have added a parameter to views_invalidate_cache() to allow bypassing of rebuilding the menu:

/**
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
 *
 * @param bool $rebuild_menu
 *   Indicate whether the menu should be rebuilt. This should only be done when
 *   we expect the menu to have changed.
 */
function views_invalidate_cache($rebuild_menu = TRUE) { ... }

I have defaulted it to TRUE so that other modules calling it continue to get the same behaviour.

I've then updated view::save() and view::delete() to no rebuild the menu and pass on to the displays to be do any additional cache invalidation with a new method in views_plugin_display:

  /**
   * Invalidate any appropriate caches for this display.
   */
  function invalidate_cache() { }

Displays can override this to do things such as marking the menu for rebuild in views_plugin_display_page

There are a whole load of ways this could be better, but hopefully it's a start:

  • views_plugin_display_page only rebuild the menu when the path has changed
  • Other calls to views_invalidate_cache() can only rebuild the menu when necessary
  • Page/block cache could also be included

Status: Needs review » Needs work

The last submitted patch, 9: 941970-1-only_rebuild_menu_for_page_displays.patch, failed testing.

andrewbelcher’s picture

Status: Needs work » Needs review
StatusFileSize
new3.05 KB

Lets try that again...

Status: Needs review » Needs work

The last submitted patch, 11: 941970-only_rebuild_menu_for_page_displays-2.patch, failed testing.

damiankloip’s picture

Project: Views (for Drupal 7) » Drupal core
Version: 7.x-3.x-dev » 8.x-dev
Component: Miscellaneous » views.module
Category: Task » Bug report
Priority: Normal » Major
damiankloip’s picture

Status: Needs work » Needs review
Issue tags: +VDC, +Performance, +D8 cacheability
StatusFileSize
new7.82 KB

Patch from over in #2226541: views_invalidate_cache() clears too much - closed as a dupe of this.

dawehner’s picture

Just in general, try to get inspired by the previous patches as well.

  1. +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php
    @@ -420,6 +420,8 @@ public function submitOptionsForm(&$form, &$form_state) {
     
         if ($form_state['section'] == 'path') {
           $this->setOption('path', $form_state['values']['path']);
    +      // Set the menu as needed to be rebuilt.
    +      \Drupal::service('router.builder')->setRebuildNeeded();
         }
       }
    

    I don't think we should trigger a menu rebuild in the UI. Instead we should try to figure that out while saving / updating / importing the view.

  2. +++ b/core/modules/views/lib/Drupal/views/ViewsData.php
    @@ -194,6 +195,22 @@ protected function prepareCid($cid) {
       /**
    +   * Gets an array of cache tags.
    +   *
    +   * @return array
    +   *   An array of cache tags.
    +   */
    +  protected function getCacheTags($key = NULL) {
    

    let's describe what the $key is doing.

  3. +++ b/core/modules/views/views.module
    @@ -525,46 +525,23 @@ function views_language_list($field = 'name', $flags = Language::STATE_ALL) {
    + *
    + * @deprecated @todo
    

    Let's ship with this.

damiankloip’s picture

Fair, that was just a rough patch though, as you can probably tell.

Status: Needs review » Needs work

The last submitted patch, 14: 941970-8.x.patch, failed testing.

damiankloip’s picture

wim leers’s picture

Is this still a problem ever since the menu homestretch patch landed?

moshe weitzman’s picture

views_invalidate_cache() still rebuilds the router regardless of whether the current View even has a route (i.e. a page display). It also clears block cache regardless of whether the block display plugin is involved. If we think those are separate issues, we could reopen #2226541: views_invalidate_cache() clears too much

wim leers’s picture

Berdir has confirmed that this is quite a big performance problem in #2241377-28: [meta] Profile/rationalise cache tags.

fabianx’s picture

#8: This is I think strongly related to #2425259: Router rebuild lock_wait() condition can result in rebuild later in the request (race condition), which can also trigger rebuild again and again.

All thats needed is something doing a menu_get_item() while the cache is being rebuild ...

berdir’s picture

I don't think this issue is about that problem.

This is about the following:

a) Assuming you have a view that only has a block, then saving that view will still trigger a router rebuild.
b) Assuming you have a view with a page display, but you are not changing any option that would require a menu rebuild, it will still do that.

(menu is somewhat misleading here and 7.x terminology)

Fabianx's issue is more related to this, as is #356399: Optimize the route rebuilding process to rebuild on write I think.

Also, there are two distinct problems with that I reported I think.
a) Multiple router rebuilds, which I've noticed in some cases, e.g. when saving views, but I don't think it's the fault of views.
b) menu cache tag invalidations, which did not happen during router rebuilds but at other times.. possibly saving menu links.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new6.83 KB

Just some ideas.

Status: Needs review » Needs work

The last submitted patch, 24: 941970-24.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new7.04 KB
new1.26 KB

Status: Needs review » Needs work

The last submitted patch, 26: 941970-26.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new7.35 KB
new1.85 KB

Another try.

Status: Needs review » Needs work

The last submitted patch, 28: 941970-28.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new7.43 KB
new1.35 KB

Meh.

Status: Needs review » Needs work

The last submitted patch, 30: 941970-30.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new8.24 KB
new3.22 KB

Let's try.

Status: Needs review » Needs work

The last submitted patch, 32: 941970-32.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new8.34 KB
new1.52 KB

This could fix a couple of the failures.

moshe weitzman’s picture

"Invalidate the views cache, forcing a rebuild on the next grab of table data."

I'm not clear what we mean by 'table data'. I think we mean the views config entities themselves.

Status: Needs review » Needs work

The last submitted patch, 34: 941970-34.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new8.44 KB
new8.44 KB
new777 bytes

There we go.

The last submitted patch, 37: 941970-37.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 37: 941970-37.patch, failed testing.

berdir’s picture

Nice, this looks very promising!

Looking forward to views savings not taking forever when you just want to make a tiny change somewhere in a field handler.

  1. +++ b/core/modules/views/src/Views.php
    @@ -534,4 +535,25 @@ protected static function t($string, array $args = array(), array $options = arr
    +    // Reset the RouteSubscriber from views.
    +    \Drupal::getContainer()->get('views.route_subscriber')->reset();
    

    Why use getContainer() here?

  2. +++ b/core/modules/views/src/Views.php
    @@ -534,4 +535,25 @@ protected static function t($string, array $args = array(), array $options = arr
    +    Cache::invalidateTags(['views_data']);
    

    can we call resetCache() on the service here instead? That can also reset static caches.

wim leers’s picture

Looking forward to views savings not taking forever when you just want to make a tiny change somewhere in a field handler.

+1 :)

The long time it takes to save a view (in D7 & D8) has always puzzled me. Sounds like this will make that go away :)

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new9.82 KB
new8.56 KB

Thank you @berdir for the review.

Some work on it.

sharique’s picture

+++ b/core/modules/views/src/Entity/View.php
@@ -220,7 +220,13 @@ protected function generateDisplayId($plugin_id) {
+    $empty_array = [];

Do you mean array()?

Status: Needs review » Needs work

The last submitted patch, 42: 941970-42.patch, failed testing.

dawehner’s picture

@Sharique.
Well, I mean []. In PHP 5.4 you can use [] as alternative to array(), see http://php.net/manual/de/migration54.new-features.php

sharique’s picture

Thanks dawehner, I did not know about that.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new12.75 KB
new3.73 KB

Some work here.

Status: Needs review » Needs work

The last submitted patch, 47: 941970-47.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new17.27 KB
new6.29 KB

Let's see whether we managed to find all the failures.

Status: Needs review » Needs work

The last submitted patch, 49: 941970-49.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new17.11 KB
new705 bytes

Let's see whether this is all we need.

Status: Needs review » Needs work

The last submitted patch, 51: 941970-51.patch, failed testing.

The last submitted patch, 51: 941970-51.patch, failed testing.

paboden queued 51: 941970-51.patch for re-testing.

The last submitted patch, 51: 941970-51.patch, failed testing.

mortona2k’s picture

Issue tags: +Needs reroll

Reviewed with paboden.

mortona2k’s picture

I triaged with @paboden at Drupalcon Los Angeles Sprints.

The issue summary is understandable and up to date.

To reproduce, I set a breakpoint on function setRebuildNeeded() in RouteBuilder.php and saved a view with no changes. setRebuildNeeded() was called during the save, indicating an unnecessary router rebuild.

The last patch only had warnings during the last simpletest run. We might only need a reroll.

In postSave(), there is a comment before views_invalidate_cache() is called:
// @todo Remove if views implements a view_builder controller.

But does the router rebuild still cause crippling performance? If not, maybe this is no longer a major bug.

mortona2k’s picture

StatusFileSize
new17.42 KB

Rerolled.

mortona2k’s picture

Status: Needs work » Needs review

The last submitted patch, 1: 941970-views.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 58: views_rebuilds_the_menu-941970-58.patch, failed testing.

martin107’s picture

Issue tags: -Needs reroll
fabianx’s picture

#57: Generally we should try to avoid unnecessary work.

mpdonadio’s picture

Status: Needs work » Needs review
StatusFileSize
new17.54 KB

Reroll. Didn't look to see b/c of which commits.

$ git checkout 29429bd
$ git apply --index views_rebuilds_the_menu-941970-58.patch
$ git rebase 8.0.x
First, rewinding head to replay your work on top of it...
Applying: views_rebuilds_the_menu-941970-58.patch
Using index info to reconstruct a base tree...
M	core/modules/rest/src/Plugin/views/display/RestExport.php
M	core/modules/rest/src/Plugin/views/style/Serializer.php
M	core/modules/views/src/Entity/View.php
M	core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
M	core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
M	core/modules/views/src/Plugin/views/display/Page.php
M	core/modules/views/src/Plugin/views/display/PathPluginBase.php
M	core/modules/views/src/Tests/Plugin/AccessTest.php
M	core/modules/views/src/Tests/Plugin/DisplayTest.php
M	core/modules/views/src/ViewExecutable.php
M	core/modules/views/src/Views.php
M	core/modules/views/tests/modules/views_test_config/test_views/views.view.test_disabled_display.yml
M	core/modules/views/views.module
Falling back to patching base and 3-way merge...
Auto-merging core/modules/views/views.module
Auto-merging core/modules/views/tests/modules/views_test_config/test_views/views.view.test_disabled_display.yml
Auto-merging core/modules/views/src/Views.php
Auto-merging core/modules/views/src/ViewExecutable.php
Auto-merging core/modules/views/src/Tests/Plugin/DisplayTest.php
Auto-merging core/modules/views/src/Tests/Plugin/AccessTest.php
Auto-merging core/modules/views/src/Plugin/views/display/PathPluginBase.php
CONFLICT (content): Merge conflict in core/modules/views/src/Plugin/views/display/PathPluginBase.php
Auto-merging core/modules/views/src/Plugin/views/display/Page.php
Auto-merging core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
Auto-merging core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
Auto-merging core/modules/views/src/Entity/View.php
Auto-merging core/modules/rest/src/Plugin/views/style/Serializer.php
CONFLICT (content): Merge conflict in core/modules/rest/src/Plugin/views/style/Serializer.php
Auto-merging core/modules/rest/src/Plugin/views/display/RestExport.php
CONFLICT (content): Merge conflict in core/modules/rest/src/Plugin/views/display/RestExport.php
Failed to merge in the changes.
Patch failed at 0001 views_rebuilds_the_menu-941970-58.patch
The copy of the patch that failed is found in:
   /Users/matt/Documents/PhpStorm/drupal-8.0.x/.git/rebase-apply/patch

Simple conflicts in the use sections and where new code went.

Status: Needs review » Needs work

The last submitted patch, 64: views_rebuilds_the_menu-941970-64.patch, failed testing.

mpdonadio’s picture

I can't get Drupal\config\Tests\ConfigImportAllTest to complete locally to see what is really going on.

lendude’s picture

Status: Needs work » Needs review
StatusFileSize
new17.25 KB

Reroll, didn't apply anymore. Didn't change anything else except remove some double newlines.

Status: Needs review » Needs work

The last submitted patch, 67: views_rebuilds_the_menu-941970-67.patch, failed testing.

joelpittet’s picture

Just a guess:

+++ b/core/modules/views/src/Entity/View.php
@@ -346,9 +352,25 @@ protected function addCacheMetadata() {
+    $executable = $this->getExecutable();
...
+    }
+    foreach ($executable->displayHandlers as $display_id => $display_handler) {

Do we need to call $executable->initDisplay(); before the foreach?

lendude’s picture

Status: Needs work » Needs review
StatusFileSize
new1.53 KB
new17.43 KB

@joelpittet sounds like a good guess, lets see what it does. Like @mpdonadio I can't get Drupal\config\Tests\ConfigImportAllTest to pass cleanly locally, but fails with timeouts and stuff so seems unrelated to this.

Added missing use and cleaned up a white space error that snuck in.

dawehner’s picture

  1. +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
    @@ -345,6 +346,18 @@ public function render() {
    +    if (method_exists($style_plugin, 'postSaveView')) {
    +      $style_plugin->postSaveView($original_display);
    +    }
    

    Given that StylePluginBase has no interface it would be at least save to add it to there, so we don't need a methodExists() check. Ideally though we would have something like ViewChangesInterface which would contain all that kind of methods.

  2. +++ b/core/modules/rest/src/Plugin/views/style/Serializer.php
    @@ -139,6 +141,27 @@ public function render() {
       /**
    +   * Act on saving a view.
    +   *
    

    ... it would be really helpful to explain what we actually check in that 12 lines of code.

lendude’s picture

@dawehner something like this?

dawehner’s picture

  1. +++ b/core/modules/rest/src/Plugin/views/style/Serializer.php
    @@ -141,20 +141,16 @@ public function render() {
    +    // We need a router rebuild if the available formats have changed.
    

    ..., because the formats are stored on the route.

  2. +++ b/core/modules/views/src/Plugin/views/style/StylePluginBase.php
    @@ -826,6 +827,14 @@ public function query() {
    +   *   (optional) The original display.
    

    Do you mind documenting that the original display is empty when we just created a new view?

dawehner’s picture

Status: Needs review » Needs work
lendude’s picture

Status: Needs work » Needs review
StatusFileSize
new1.48 KB
new18.43 KB

Updated per #73

dawehner’s picture

Status: Needs review » Needs work
+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -345,6 +347,17 @@ public function render() {
+   */
+  public function postSaveView(DisplayPluginInterface $original_display = NULL) {
+    parent::postSaveView($original_display);
+
+    /** @var StylePluginBase $style_plugin */
+    $style_plugin = $this->getPlugin('style');
+    $style_plugin->postSaveView($original_display);
+  }

This sounds something which should live in DisplayPluginBase

lendude’s picture

Status: Needs work » Needs review
StatusFileSize
new3.19 KB
new17.74 KB

@dawehner yeah that makes sense. Rerolled for #76 and since #2464657: Remove unnecessary cache clear in Views tests landed, that bit can be taken out too.

dawehner’s picture

So we are adding this to DisplayPluginBase but we don't add it to StylePluginBase, but still call the method?

lendude’s picture

+++ b/core/modules/views/src/Plugin/views/style/StylePluginBase.php
@@ -838,6 +839,15 @@ public function query() {
+   */
+  public function postSaveView(DisplayPluginInterface $original_display = NULL) { }
+

@dawehner We don't? Or are you referring to something else? If so, could you elaborate a bit? Patch didn't apply to 8.1.x and 8.2.x so rerolled for that, #77 still applies to 8.0.x.

dawehner’s picture

Status: Needs review » Needs work

@Lendude No idea what I was smoking on sunday :)

+++ b/core/modules/views/src/Entity/View.php
@@ -233,7 +233,13 @@ protected function generateDisplayId($plugin_id) {
+    $empty_array = [];
+    if (isset($this->display[$display_id])) {
+      return $this->display[$display_id];
+    }
+    else {
+      return $empty_array;
+    }

I'm wondering whether we really want to do this here ... isn't it a sign of some brokenness if you call out to a display that doesn't exist? This implementation here also doesn't care about the reference properly. You would rather setup $this->display[$display_id] instead.

lendude’s picture

@dawehner haha! No worries. At first glance I'd say I agree with you that that bit looks bad/weird, I'll take a look at it when I get some time.

lendude’s picture

Status: Needs work » Needs review
StatusFileSize
new805 bytes
new17.79 KB
new17.79 KB

@dawehner so do you mean something like this?

dawehner’s picture

+++ b/core/modules/views/src/Entity/View.php
@@ -233,13 +233,12 @@ protected function generateDisplayId($plugin_id) {
+      // Setup the requested display. Since we have no information other then
+      // the ID, set it up with defaults.
+      $this->addDisplay('page', NULL, $display_id);

We add a page display by default? This feels weird

lendude’s picture

This is the default in ViewEntityInterface

public function addDisplay($plugin_id = 'page', $title = NULL, $id = NULL);

and since we want to set $id, I figured using the default for $plugin_id for that method made sense. Unless there is a 'Broken' display, but don't know if there is anything like that.

dawehner’s picture

Fair point. In general i'm still wondering why this issue requires us to adapt getDisplay in the first place.

lendude’s picture

Yeah I was wondering the same thing, it seems unrelated. It first showed up in your patch in #42 so I assumed it was causing some tests to fail.

dawehner’s picture

@Lendude
Yeah that sounds like a workaround for some missing display.

lendude’s picture

Ok so lets see what still fails, and why, when we take it out.

dawehner’s picture

+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -2679,6 +2681,16 @@ protected function isBaseTableTranslatable() {
+   */
+  public function postSaveView(DisplayPluginInterface $original_display = NULL) {
+    /** @var StylePluginBase $style_plugin */
+    $style_plugin = $this->getPlugin('style');
+    $style_plugin->postSaveView($original_display);
+  }
+

I think we should also detect whether we got new arguments ... as this will change the required route as well

lendude’s picture

+++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
@@ -536,4 +536,29 @@ public function remove() {
+      $determining_options = ['path', 'route_name', 'arguments', 'access', 'enabled'];
...
+    }

@dawehner Isn't that covered in PathPluginBase::postSaveView()? Is there ever a need to rebuild routes when not using/extending PathPluginBase?

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Yeah not sure, I was probably just blind.

alexpott’s picture

Version: 8.0.x-dev » 8.2.x-dev
+++ b/core/modules/views/src/Plugin/views/style/StylePluginBase.php
@@ -837,6 +838,15 @@ public function query() {
+  /**
+   * Act on saving a view.
+   *
+   * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $original_display
+   *   (optional) The original display. When saving a new view this will be
+   *   empty.
+   */
+  public function postSaveView(DisplayPluginInterface $original_display = NULL) { }
+

We've entered the rc phase for 8.1.x and this is an API addition therefore moving to 8.2.x.

damiankloip’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/views/src/Plugin/views/display/Block.php
    @@ -371,4 +371,16 @@ public function remove() {
    +    if (\Drupal::moduleHandler()->moduleExists('block')) {
    +      \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
    

    The module handler and block manager can be injected instead now?

  2. +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
    @@ -2679,6 +2681,16 @@ protected function isBaseTableTranslatable() {
    +    $style_plugin->postSaveView($original_display);
    

    This could be quite important that it happens, so I think maybe the style plugins should be handled in View::postSave() too. Rather than relying on the parent method being called.

  3. +++ b/core/modules/views/src/Tests/Plugin/AccessTest.php
    @@ -87,20 +88,24 @@ function testStaticAccessPlugin() {
    +    $view->destroy();
    

    Thank you! nice.

  4. +++ b/core/modules/views/src/Views.php
    @@ -535,4 +535,25 @@ protected static function t($string, array $args = array(), array $options = arr
    +    $module_handler = \Drupal::moduleHandler();
    

    Doesn't seem any point in setting a variable for the module handler here. Can just be a one liner.

  5. +++ b/core/modules/views/views.module
    @@ -459,21 +459,7 @@ function views_field_config_delete(FieldConfigInterface $field) {
     function views_invalidate_cache() {
    

    Maybe we should be deprecating this now too, with a note that code should just use Views::invalidateCache()

lendude’s picture

Status: Needs work » Needs review
StatusFileSize
new5.9 KB
new19.57 KB

Thanks for the feedback @damiankloip! So, something like this then?

dawehner’s picture

+++ b/core/modules/views/src/Entity/View.php
@@ -360,6 +361,9 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
       $display_handler->postSaveView($original_display_handler);
+      /** @var StylePluginBase $style_plugin */
+      $style_plugin = $display_handler->getPlugin('style');
+      $style_plugin->postSaveView($original_display_handler);
     }
 

+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -2680,11 +2679,7 @@ protected function isBaseTableTranslatable() {
-  public function postSaveView(DisplayPluginInterface $original_display = NULL) {
-    /** @var StylePluginBase $style_plugin */
-    $style_plugin = $this->getPlugin('style');
-    $style_plugin->postSaveView($original_display);
-  }
+  public function postSaveView(DisplayPluginInterface $original_display = NULL) { }
 

So what about supporting all plugin types?

lendude’s picture

@dawehner so something like this? Moved postSaveView to Drupal\views\Plugin\views\PluginBase and then call it for all plugins that are present in each display.

xjm’s picture

Issue tags: +D8 major triage deferred, +needs profiling

Thanks @paboden and @mortona2k triaging this issue last year, and thanks @Lendude for helping the maintainers with recent work on the issue.

@tim.plunkett, @dawehner, @alexpott, and I discussed this issue at DrupalCon New Orleans and decided to defer deciding whether it should remain major for two reasons:

  1. Whether or not this is a major bug depends of the performance impact of the change. So, marking for profiling.
  2. Rebuilding too much is better than rebuilding too little, so unless the performance cost of the extra rebuilds is significant, we are in an okay situation currently. It's still worth making the improvement, but we would need to test carefully to ensure we were not rebuilding too little after this patch.
dawehner’s picture

Status: Needs review » Needs work

Given we need profiling, let's move it to needs work.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Title: Views rebuilds the menu more than it needs to » Only set router rebuild needed when something related to routing actually changes
Category: Bug report » Task
Priority: Major » Normal
Issue tags: -D8 major triage deferred, -needs profiling +Needs issue summary update

Menu rebuilds used to take several seconds but we've done a lot to optimise that since 2010:

1. Router rebuild and link discovery are separated, so that link discovery doesn't block other request routing (and sometimes doesn't happen at all).

2. setRebuildNeeded() means that for configuration deploys instead of every views save triggering a rebuild, there should only be one.

I think that means it's time to downgrade this from bug to task. However from what I can see the approach would still help a couple of cases:

1. Speed up saving of views for admins/site builders (when they're likely to be the person triggering the next request that rebuilds the menu).
2. Optimise away that last unnecessary rebuild on config deploys even if it's just 1 instead of views saves * 1.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea

As we deprecate the procedural code, I'll, make this part of the #3566536: [meta] eliminate core .module files effort. Will try to revive it

claudiu.cristea’s picture

claudiu.cristea’s picture

Hide patches

claudiu.cristea’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

Fixed IS

claudiu.cristea’s picture

Issue summary: View changes
claudiu.cristea’s picture

Status: Needs work » Needs review

Ready for review

claudiu.cristea’s picture

Assigned: claudiu.cristea » Unassigned

Unassigning

nicxvan’s picture

Went through this a few times, I think this makes a lot of sense now. It's separating the cache invalidation and the route rebuild so each display type can handle the part they need to.

Since this isn't a straight replacement I think we should do a CR here and reference that one.

nicxvan’s picture

I created a first pass at the CR and suggestions to swap it. If the CR looks good we can apply those and update https://www.drupal.org/node/3566774 to remove the reference to this function.

Once that is done I think this is ready!

smustgrave’s picture

Small question on the MR.

nicxvan’s picture

Just answered!

nicxvan’s picture

Status: Needs review » Reviewed & tested by the community

Thank you for cleaning up the comment and block plugin change, I think this is ready!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Added some comments to the MR.

claudiu.cristea’s picture

Status: Needs work » Needs review

Ready for a new review

nicxvan’s picture

Status: Needs review » Reviewed & tested by the community

All comments have been addressed, looks good to me!

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Added some more thoughts to the MR. Not entirely sure about the answers - needs discussion.

nicxvan’s picture

The de duplication seems like a good idea.

graber’s picture

I'll add my few cents here.. This branch creates new API for basically all views plugins in the form of postSaveView(). Since it'll be always there, possibly contrib will use it now so we have no way of predicting what will happen there. Deduping is a bit dangerous as it may happen some callbacks will actually need to be called multiple times, just with different arguments or even same arguments, we have no idea how will people use the new API. I'd pass some initially empty $context array by reference in each call to postSaveView() and handle possible duplication on the plugin level.

BTW.. Seems also View::routeRebuildNeeded() can be called multiple times, even if harmless, it's not needed so if the $context solution will be the preferred one, calling View::routeRebuildNeeded() only once should be included on the plugin level too. I'd move that method to core/modules/views/src/Plugin/views/PluginBase.php probably (protected).

graber’s picture

About the base plugin class method postSaveView() concern:

Problem:
Views has two plugin families on one base class: HandlerBase extends PluginBase implements ViewsPluginInterface, so handlers (field, filter, sort, …) are plugins — but getPlugin()/getAllPlugins() only cover 'plugin' types, while handlers are reached via the separate getHandlers()/getAllHandlers(). So postSaveView(), added to ViewsPluginInterface+PluginBase, is inherited by handlers that View::postSave() never iterates (it only walks getPlugin()), and Block::postSaveView() clears the block cache once per block display (N times).

Proposed solution:
Move postSaveView() to a dedicated opt-in PostSaveViewInterface (with a by-ref $context); remove it from ViewsPluginInterface and PluginBase.
Implementers (PathPluginBase, Block, REST Serializer) still perform their own effect, but record it in $context so a peer that would repeat a view-global effect skips it.
View::postSave() iterates display handler + getAllPlugins(TRUE) + getAllHandlers(TRUE) (same coverage as calculateDependencies()), dispatches to instanceof PostSaveViewInterface, and stays generic — it seeds $context but never acts on it, as in my last comment - plugins use it to avoid duplication.

Why:
Performance: block cache cleared / routes rebuilt once per save, no static state.
Controlled API: opt-in interface instead of forcing the method onto all ~19 types via PluginBase.
Complete coverage: handlers can now participate, and $only_overrides = TRUE visits each configured plugin/handler once, not once per inheriting display.

graber’s picture

Status: Needs review » Needs work

Since there's no more discussion here, setting this to NW.