Problem/Motivation

MenuLinkInterface::isCacheable()'s docs say:

  /**
   * Returns whether the rendered link can be cached.
   *
   * The plugin class may make some or all of the data used in the Url object
   * and build array dynamic. For example, it could include the current user
   * name in the title, the current time in the description, or a destination
   * query string. In addition the route parameters may be dynamic so an access
   * check should be performed for each user.
   *
   * @return bool
   *   TRUE if the link can be cached, FALSE otherwise.
   */
  public function isCacheable();

But In addition the route parameters may be dynamic so an access check should be performed for each user. is wrong; this is independent of access checking. All this indicates, is whether the title, the route name or the route parameter (in short: the associated Url object) is dynamically changing.

So, the docs are wrong.

However, we should actually remove ::isCacheable. Because despite what all docs & comments every written about it say, which is that the Url object's properties may be dynamic, the only actual use case for it was MyAccountMenuLink, which used it to dynamically set the "enabled" flag on the menu link. That was fixed in #2294571: Redirect anonymous users to login page from an exception listener instead of in MaintenanceModeSubscriber and restrict access to the my-account link to authenticated users. So we're chugging along this complexity for no good reason.
That'd also be a big DX win, because it's impossible to understand how to use ::isCacheable(), really.

So, let's remove this to regain sanity.

#1805054: Cache localized, access filtered, URL resolved, and rendered menu trees needs this to work correctly, otherwise render caching of menus is not possible.

Proposed resolution

Remove it.

Remaining tasks

TBD

User interface changes

None.

API changes

  • MenuLinkInterface::isCacheable() removed.
  • MenuLinkInterface now implements CacheableDependencyInterface

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because the existing interface method makes no sense at worst, is misleading at best.
Issue priority Major because it blocks the critical #2335661: Outbound path & route processors must specify cacheability metadata
Prioritized changes The main goal of this issue is performance
Disruption Close to zero disruption; only affects MenuLinkInterface implementations in contrib/custom code, of which there are likely still zero at this time

Comments

wim leers’s picture

The one instance that we used to have that used isCacheable() { return FALSE; }:


/**
 * Provides custom logic for the user.page menu link.
 */
class MyAccountMenuLink extends MenuLinkDefault {

  /**
   * {@inheritdoc}
   */
  public function isEnabled() {
    // The path 'user' must be accessible for anonymous users, but only visible
    // for authenticated users. Authenticated users should see "My account", but
    // anonymous users should not see it at all.
    // @todo Re-write this as a link to entity.user.canonical with dynamic route parameters
    //   to affect access since hidden should not be dynamic.
    //   https://www.drupal.org/node/2306991
    return $this->pluginDefinition['enabled'] && !\Drupal::currentUser()->isAnonymous();
  }

  /**
   * {@inheritdoc}
   */
  public function isCacheable() {
    return FALSE;
  }

}

(Was removed in #2294571: Redirect anonymous users to login page from an exception listener instead of in MaintenanceModeSubscriber and restrict access to the my-account link to authenticated users.)

fabianx’s picture

I am not 100% sure on that, yet.

MenuLinkInterface is a beast already in terms of provided functions (many many many ...), but given I can write a 'Hello Wim' menu link plugin, how would I specify the 'user' cache context upstream then?

[ And yes I realize that for this special example plugin contexts might be enough and would maybe bubble that information automatically, but even then the caller would need to be aware of it. ]

So what would needs to happen so that I can support MySpecialPlugin use case with proper cacheability? If it can be work arounded, lets remove it, but if it cannot I think its better we add CacheableDependencyInterface instead and make it cacheable by default.

dawehner’s picture

Its just funny how people fight for small changes which actually matters for DX, but this seriously, there aren't many people on earth which will implement that interface :)

What about just ignoring that method, mark it as deprecated, and assume menu links are cachable by default, and an make opt in CacheableDependencyInterface usage?

fabianx’s picture

Opt-in cacheable dependency interface works for me. :)

wim leers’s picture

First and foremost: then we still have the problem that it's not clear the cacheability of what exactly is being described: only URL properties, or also menu link tree properties (such as whether it is enabled, expanded, resettable, deletable, translatable, the parent, the menu name)?


Let's assume we want to retain cacheability metadata.

Then opt-in means we have the situation that we have with AccessResult: code will assume the typical case, and ignore the other.

For access results, the typical case is that CacheableDependencyInterface is implemented. Which means it's easy to forget that not all of them implement it.

For menu links, if we do this, the typical case will be that CacheableDependencyInterface is NOT implemented. Which means it's easy to forget that sometimes, it may be not cacheable. But, in this case, very, very few things will ever interact with MenuLinkInterface objects, so the potential damage here is much lower.

Still, wouldn't it then be better to then just always implement CacheableDependencyInterface? That's more consistent. MenuLinkBase is used by all menu link plugins anyway (at least in core), so the DX impact will still be zero.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new1.72 KB

First, let's prove by simply removing it that zero test coverage exists for this.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Alright, we can make major API changes again, cool

wim leers’s picture

Status: Reviewed & tested by the community » Needs review

The "first" only meant "as a first step in this issue".

Let's continue the discussion.

It's easy to support CacheableDependencyInterface, but it will remain very confusing IMO. Nevertheless, there *are* use cases.

We just have to make a choice.

fabianx’s picture

I think lets support CacheableDependencyInterface and have all things be cacheable by default.

Just bubble up whatever it returns.

That empowers developers to make a MenuLink uncacheable or add additional cacheable meta information, but is nothing that core uses by default or any class extended from the Base Menu Link and hence very transparent.

The API change from isCacheable => CacheableDependencyInterface should be okay for beta as its one of those changes you cannot know before starting on the performance work, which is traditionally (and rightfully so) late in the cycle. [i.e. all the performance optimizations we did for l() have been completely useless as that code was ripped out completely, the same for some other things.]

We currently finalize these API's by ensuring that all use cases in core can be adequately covered, but its hard (and takes some time) as you need to pretty much understand everything in core that can render something, which is obviously a lot.

wim leers’s picture

StatusFileSize
new2.98 KB
new1.88 KB

Done.

This will be utilized during menu rendering in #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees; it's much easier to add support for it there than here, so let's do it there. This was not yet being used anywhere, nor tested anywhere anyway (as demonstrated in #8), so this does not represent a regression.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC, we need a beta evaluation.

Unless core committers think that we should keep a BC layer on the interface this should be good to go.

wim leers’s picture

Title: Fix MenuLinkInterface::isCacheable(): remove it, no uses remain » Fix MenuLinkInterface::isCacheable(): remove it in favor of implementing CacheableDependencyInterface
Issue summary: View changes
catch’s picture

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

I don't see any use for a BC layer - this shouldn't be used by any contrib modules at all and it's an easy update if it is. However in case for some reason there's a module out there relying on this, we should have a change record.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs change record
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 67f0d68 and pushed to 8.0.x. Thanks!

Thanks for adding the beta evaluation to the issue summary.

  • alexpott committed 67f0d68 on 8.0.x
    Issue #2479767 by Wim Leers: Fix MenuLinkInterface::isCacheable():...

Status: Fixed » Closed (fixed)

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