Problem/Motivation

The menu API doesn't require one to use Menu config entities. Yet the rendering of menus is tied to that.

Proposed resolution

Fix this by using menu cache tags that aren't tied to Menu config entities, and have Menu config entities use those non-coupled cache tags too.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task because menus as a concept should not be tied to Menu config objects. That prevents developers from maintaining their own menu trees outside of Menu config objects.
Issue priority Normal.
Prioritized changes The main goal of this issue is API correctness/completeness.
Disruption Zero disruption.

Issue fork drupal-2488918

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

wim leers’s picture

Status: Active » Needs review
Issue tags: +D8 cacheability
StatusFileSize
new9.49 KB

Status: Needs review » Needs work

The last submitted patch, 1: decouple_menu_cache_tags-2488918-1.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 1: decouple_menu_cache_tags-2488918-1.patch, failed testing.

dawehner’s picture

+++ b/core/modules/system/src/Entity/Menu.php
@@ -77,4 +77,15 @@ public function isLocked() {
+  /**
+   * {@inheritdoc}
+   *
+   * Override the default cache tags to use the cache tags that are not coupled
+   * to Menu config entities (i.e. this class), to not force other parts of the
+   * menu system to couple themselves to Menu config entities.
+   */
+  public function getCacheTags() {
+    return ['menu:' . $this->id()];

Should we also add config: as well, just in case someone needs it.

wim leers’s picture

Status: Needs work » Needs review

#5: We could do that, but I don't understand why. They should just be using Menu::getCacheTags(), which would automatically be correct.


More importantly, the failure that is showing up… I don't think I can actually fix that. Config entities in the end are merely config objects. It's the \Drupal\Core\Config\Config::save() method that does the actual cache tag invalidation… and I don't see how we can make the config objects associated with Menu config entities aware of the custom cache tags we are specifying. Config is special like that.

So the failure we are seeing is a legitimate failure, because the menu:<menu name> cache tag is not being invalidated.

Ideas?

fabianx’s picture

Should Config::save() not check if the entity it is saving implements CacheableDependencyInterface and then merge those cacheTags in, too?

wim leers’s picture

No, Config::save() does not have access to the config entity.

fabianx’s picture

Issue summary: View changes
Status: Needs review » Needs work

Spoke with @alexpott in IRC:

There are 2 ways:

- Use the normal entity hooks and implement them in menu module, to invalidate tags on save
- Do it like views or VocabularyStorage and subclass ConfigEntityStorage and override the save() / resetCache() methods to do our own invalidation

After thinking about it, its still worth it to have clearer semantics here as this cache tags will be output by almost every Drupal 8 site online, so it would be good to have less coupling ...

CNW for the above changes, needs a beta evaluation.

wim leers’s picture

Issue summary: View changes
Status: Needs work » Needs review

Done.

wim leers’s picture

StatusFileSize
new10.67 KB
new1.55 KB

Now with 100% more patch.

Status: Needs review » Needs work

The last submitted patch, 11: decouple_menu_cache_tags-2488918-11.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
berdir’s picture

  1. +++ b/core/modules/system/src/Entity/Menu.php
    @@ -77,4 +78,15 @@ public function isLocked() {
    +   * {@inheritdoc}
    +   *
    +   * Override the default cache tags to use the cache tags that are not coupled
    +   * to Menu config entities (i.e. this class), to not force other parts of the
    +   * menu system to couple themselves to Menu config entities.
    +   */
    +  public function getCacheTags() {
    +    return ['menu:' . $this->id()];
    +  }
    

    AFAIK, it's officially still not allowed to mix @inheritdoc and additional docblock comments. I'd recommend to make this an inline comment to avoid discussions about this...

  2. +++ b/core/modules/system/src/MenuStorage.php
    @@ -0,0 +1,31 @@
    +   *
    +   * The menu API doesn't require one to use Menu config entities. Hence the
    +   * Menu config entity should not use config-specific cache tags, but generic
    +   * ones instead. That's what this code guarantees.
    

    Same.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Tests pass, the llama is happy, solution looks good, RTBC!

dawehner’s picture

Seems fine

fabianx’s picture

Status: Reviewed & tested by the community » Needs work

CNW per #16, cross-post (also to avoid commit conflicts with the more important cache menu links issue).

wim leers’s picture

Issue tags: +Needs reroll

#1805054: Cache localized, access filtered, URL resolved, and rendered menu trees landed, this can now be rerolled. With the trivial doc fixes in #16, this will be RTBC again.

berdir’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new10.53 KB
new2.25 KB

Did that, since it's my fault that it's needed ;)

Also not sure why the cache tag invalidation happens in MenuStorage. Trying something....

berdir’s picture

Clearing the cache tag in the method that we also use everywhere else seems like a much more logical thing to do? :)

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

berdir++ - We only used the custom storage, because of #9(I spoke with alexpott and he saw no other ways), but this way is obviously better.

RTBC!

wim leers’s picture

Berdir++
RTBC++

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I think we also need to implement invalidateTagsOnDelete. Also how about not calling the parent to save a query and couple the classes less?

lauriii’s picture

Status: Needs work » Needs review
StatusFileSize
new10.46 KB
new778 bytes

Status: Needs review » Needs work

The last submitted patch, 25: use_menu_menu_name-2488918-25.patch, failed testing.

wim leers’s picture

+++ b/core/modules/system/src/Entity/Menu.php
@@ -100,6 +101,16 @@
+    if ($update) {
+      Cache::invalidateTags($this->getCacheTags());
+    }

The if-statement is unnecessary/wrong.

fabianx’s picture

+++ b/core/modules/system/src/Entity/Menu.php
@@ -100,6 +101,16 @@
+  protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
+    parent::invalidateTagsOnDelete($update);

Yeah, that won't fly ...

The last submitted patch, 25: use_menu_menu_name-2488918-25.patch, failed testing.

lauriii’s picture

Status: Needs work » Needs review
StatusFileSize
new10.5 KB
new639 bytes

I guess I was _little_ tired last night. Fixing my own mess

alexpott’s picture

Status: Needs review » Needs work
+++ b/core/modules/system/src/Entity/Menu.php
@@ -77,4 +78,39 @@ public function isLocked() {
+    parent::invalidateTagsOnSave($update);
+    // The menu API doesn't require one to use Menu config entities. Hence the
+    // Menu config entity should not use config-specific cache tags, but generic
+    // ones instead. Drupal\Core\Config\Entity\ConfigEntityBase explicitly
+    // overrides the default implementation and does not invalidate the specific
+    // cache tag, this adds that again.
+    if ($update) {
+      Cache::invalidateTags($this->getCacheTags());
+    }
...
+    parent::invalidateTagsOnDelete($entity_type, $entities);
+    foreach ($entities as $key => $value) {
+      Cache::invalidateTags($entities[$key]->getCacheTags());
+    }

It super sub-optimal do have multiple calls to Cache::invalidateTags(). Each call is a query I think. So we can merge the arrays of tags first and do a single call to Cache::invalidateTags() for both save and delete. Plus the green patch in #21 shows we're missing test coverage.

lauriii’s picture

Issue tags: +Needs tests

Just tagging first

RavindraSingh’s picture

Assigned: Unassigned » RavindraSingh
wim leers’s picture

#32: I agree in principle, but the only way to merge the tags first in this case, is by breaking through the abstraction and duplicating the logic of the parent methods. So I think the solution in this patch is actually fine. Especially because it is for Menu entities (not menu links!), which change rarely.

Anonymous’s picture

Assigned: RavindraSingh »
Anonymous’s picture

StatusFileSize
new9.67 KB

Made patch apply to newest Drupal version

Anonymous’s picture

Status: Needs work » Needs review
Anonymous’s picture

StatusFileSize
new10.11 KB

With test

wim leers’s picture

Great, thanks!

+++ b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
@@ -104,6 +104,10 @@ public function testMenuBlock() {
+    // Verify that default cache tag doesn't invalidate the menu.

s/default/the default config entity/

We also need test coverage for the fail in #25 which was fixed by the interdiff in #31.

wim leers’s picture

Status: Needs review » Needs work

Needs work for #40.

The last submitted patch, 39: use_menu_menu_name-2488918-39.patch, failed testing.

The last submitted patch, 39: use_menu_menu_name-2488918-39.patch, failed testing.

The last submitted patch, 37: use_menu_menu_name-2488918-37.patch, failed testing.

wim leers’s picture

Can we still do this now that D8 is out?

dawehner’s picture

Version: 8.0.x-dev » 9.x-dev

I doubt to be honest.

wim leers’s picture

Title: Use 'menu:<menu name>' instead of 'config:system.menu:<menu name>' to avoid coupling » Use 'menu:<menu name>' as cache tag for menus instead of 'config:system.menu:<menu name>' to avoid coupling
Status: Needs work » Postponed

Clarifying for the distant D9 future. And postponing for the same reason.

catch’s picture

Priority: Normal » Minor

I can think of theoretical ways we could add bc for this, but it seems like the kind of @internal bc break we could save up for a major. There's no actual coupling/dependency here, it's just a string, so bumping down to minor.

Version: 9.x-dev » 9.0.x-dev

The 9.0.x branch will open for development soon, and the placeholder 9.x branch should no longer be used. Only issues that require a new major version should be filed against 9.0.x (for example, removing deprecated code or updating dependency major versions). New developments and disruptive changes that are allowed in a minor version should be filed against 8.9.x, and significant new features will be moved to 9.1.x at committer discretion. For more information see the Allowed changes during the Drupal 8 and 9 release cycles and the Drupal 9.0.0 release plan.

xjm’s picture

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

This would be a minor-only addition and deprecation. Since 8.9.x and 9.0.x are now in beta, I'm moving this to 9.1.x. Thanks!

xjm’s picture

Status: Postponed » Active

There's nothing specific this is postponed on, so marking active.

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.

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.

damienmckenna’s picture

Assigned: » Unassigned
Status: Active » Needs work

Putting back to "needs work" as there is some code here, it needs to be turned into a merge request.

damienmckenna’s picture

The code needs some further tidying up.

berdir’s picture

I have my doubts that this is worth doing. Everything that uses these strings would break unless we add some elaborate BC layer for it: https://search.tresbien.tech/search?q=config%3Asystem.menu%20-r%3Adrupal....

Also adding the return type is a BC break and unrelated. if phpstan complains, just add it to the baseline.

webflo’s picture

Status: Needs work » Needs review
Issue tags: +DevDaysAthens2026

We could rework the menu_ui module and create two separate forms. One would be used to edit the menu settings (config entity) and the other would be used to rearrange the menu links within the menu. Editing the menu settings would then become a local task.

webflo’s picture

Status: Needs review » Needs work