Problem/Motivation
Breadcrumb builders are called in a specific order, the first one that returns a valid Breadcrumb object from its build() method is the one that will be returned from the manager. Before a builder is called, we first check its applies() method and only if that returns TRUE do we call build().
The problem lies within the fact that the applies check might introduce variability that needs to be represented by a cache context, but is not unless it returns TRUE. At that point does the build() method add the right cacheable metadata to the breadcrumb result. This is wrong, though, because a negative outcome also needs to have said cacheable metadata.
You can see how this breaks in #3452181-7: VariationCache needs to be more defensive about cache context manipulation to avoid broken redirects, comments 7 through 9.
Proposed resolution
Pass cacheable metadata to the applies() method so that it can properly set its variability (cache contexts) and dependencies.
Remaining tasks
Review MR and commit
User interface changes
N/A
API changes
BreadcrumbBuilderInterface::applies() now takes a second argument CacheableMetadata $cacheable_metadata
Data model changes
N/A
| Comment | File | Size | Author |
|---|
Issue fork drupal-2719721
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:
- 2719721-breadcrumbbuilderapplies-mismatch-with
changes, plain diff MR !8410
Comments
Comment #2
wim leersThat issue only optimized the path-based breadcrumb builder, so I highly doubt that that broke anything.
I bet this service is not specifying the correct cache context :)
Comment #3
jhodgdonYeah, talked to catch in IRC and it seems I didn't realize I had to add cache stuff to my breadcrumb class. We think the change in the other issue just exposed a bug that was in my module all along but was just exposed by this change.
I am repurposing this issue to add documentation to the interface to remind developers to add cache information.
Here's a patch.
Comment #4
wim leers+1 for better documentation!
Not "to see if this bread crumb builder applies", but instead what the cacheability of the built breadcrumbs is.
Comment #5
jhodgdonWell, the problem I ran into here was that without this being added to my returned breadcrumb, Drupal in its wisdom decided to apply it to all pages, even though applies() would have returned FALSE. It wasn't even being checked. ?!?
I still think this is a bug in Core, but the workaround in my class was to add the line
to my breadcrumb that the build() returned.
The applies() already said:
but that was not enough -- after checking it once, it then got applied to every admin page. Totally wrong... but adding that cache thing fixed it, so I think it needs to be documented if that is the way to fix it.
Comment #6
berdirWhat core decided is that the breadcrumb block cache was sufficient for the new patch. So it never tried to apply the builders because never rebuilds the block.
However, you do have a valid point. The problem is that only the builder that was chosen for the page is capable of adding cacheability metadata. Not those that decided that they don't apply.
As always, we also need cacheability metadata for negative results. So we need a way a way for a breadcrumb builder to say under which conditions he might or might not apply.
Comment #7
catchChanging the issue title to what I think the bug is here. applies() affects when the builder will run, but it doesn't stop the results of build() getting cached in such a way that it might apply to another page. That's not how you'd normally think a method called 'applies' would work.
Comment #8
jhodgdonNo kidding:
:)
Status back to Active as the docs patch does not fix the bug as it is now defined.
Comment #9
berdirYes. The only reason we haven't this yet and why the mentioned fix for @jhodgdon's problem is fixing it for that use cases is because the chosen builder's cacheablity metadata sufficiently covers the previous builders.
But that's not always the case. #2699627: url.path cache context for breadcrumbs is unnecessarily granular is triggering the first problems I guess, I have a feeling I will have to fix some of my custom breadcrumb builders as well.
Here is a scenario where I think will fail right now with book module:
1. Create a node that could be in a book but isn't ( guess you need some other node that is a book parent to actually see something later on)
2. You should see the standard path based breadcrumb, because book applies() returns false.
3. Then edit the node and put it in a book with some hierarchy
4. Save, you should still get the old breadcrumb because that is cached and the path didn't change.
And book.module can do nothing to fix it.
What we could do to fix this is \Drupal\Core\Cache\CacheableDependencyInterface on builders and if they implement it, collect cacheability metadata of all builders we asked and add that to the breadcrumb returned by the one we actually chose.
One problem that the example above also shows is that book needs information in those methods that is not directly available. Specifically, it needs the $node object from the route match as it needs the cache tag from it (probably that and the path?). As a quick fix, we could introduce some state into those services and have getCacheTags() only work when called after applies. The only other way that I see is that we introduce a new applies() method on a new interfac that can return something like AccessResult. A binary result + cacheablity metadata.
Comment #10
catchThis was my first thought here - new interface, extend the old interface. We could also pass in an optional CacheableMetadata object into the applies method so the return doesn't need to change.
Comment #11
dawehnerDoes this mean we need to run through all breadcrumb builders?
Comment #12
jhodgdonAny thoughts on the approach we should use here? I think we still need to fix this...
Comment #13
acbramley commentedI believe we are seeing a similar issue with the breadcrumb block but it's reproducible by clicking around the site.
The problem (probably) stems from having url aliases generated by menu structure. As I understand it #2699627: url.path cache context for breadcrumbs is unnecessarily granular changed the block to cache by url parent, wouldn't this mean that siblings (when url aliases are by menu structure) would get the same cached version?
That's basically what we are seeing, we can click through the top level navigation of our site and eventually it'll continue to show the same breadcrumbs across the same level pages.
Comment #16
benjy commentedI ran into a similar issue today where we didn't have a breadcrumb on the homepage, it was using the path based breadcrumb builder which adds the 'url.path.parent' but that isn't sufficient. The breadcrumb gets cached with no links and the cache context is empty since there isn't a parent page, then, on all other top level pages, they also no longer get breadcrumbs because the cache context is the same as the homepage.
I agree with this approach, it would solve the issue for me.
FilterProcessResultis another example of this pattern in core.Comment #19
kerby70 commentedAppears to me this may cause, at least in part, #2607920: Breadcrumb render cache not invalidated when entity label changes; possibly related to #2685637: Edited feed item breadcrumb does not reflect the change.
Comment #21
dpiComment #28
pameeela commentedAdded #2932341: Incorrect node title in breadcrumb as related and transferred credit because I've closed that as a duplicate.
Comment #29
pameeela commentedComment #36
kristiaanvandeneyndeThis surfaced as part of some extra hardening I'm trying to introduce into VariationCache: #3452181: VariationCache needs to be more defensive about cache context manipulation to avoid broken redirects
Re #11:
No, because they always run in the same order. So as long as we clear the cache when a new builder is introduced into the list, we're fine.
Let's say you have 3 builders, the 1st applies() check varies by route, the 2nd by domain and the 3rd by user roles (for whatever reason). Then, as long as the first in the list applies, all we need to care about is the 'route' cache context. Only when the first one ever fails and we fall back to the second one, do add the url.site cache context. At this point, VariationCache will write a CacheRedirect at the current value for 'route', setting the 'url.site' cache contexts. Then, if both the 1st and 2nd applies() check fail, will another CacheRedirect be created to allow VC to store items from the 3rd builder.
Comment #37
kristiaanvandeneyndeWhy note use the deprecation policy guideline on introducing a new argument to an existing signature? https://www.drupal.org/about/core/policies/core-change-policies/drupal-d...
I'll try that approach to allow a 2nd argument to be passed to applies().
Comment #38
kristiaanvandeneyndeComment #40
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue.
While you are making the above changes, we recommend that you convert this patch to a merge request. Merge requests are preferred over patches. Be sure to hide the old patch files as well. (Converting an issue to a merge request without other contributions to the issue will not receive credit.)
Comment #41
kristiaanvandeneyndeShould go green now. From the other issue:
Comment #42
smustgrave commentedThe issue summary mentions "Revert or fix #2699627: url.path cache context for breadcrumbs is unnecessarily granular" can I ask which one it is? Maybe this can be flushed out more. Don't think this is an API or model change so put those as N/A
Comment #43
kristiaanvandeneyndeThe MR is the correct approach, it's just that the issue summary needs severe updating.
The new approach is #10, #36 and #37
In essence: Allow the applies() method to set cacheability. Because the breadcrumb builders run in the same order every time and because only one can build the actual breadcrumb, we don't need to care about ALL builder's cacheable metadata, just the data up until the builder that actually applies.
I'll try to update the IS tomorrow.
Comment #44
michael.acampora commentedAttached a patch that can be used for Drupal 10.2. Thanks for fixing the issue.
Comment #45
kristiaanvandeneyndePlease don't convert MRs into patches as it creates a lot of unnecessary noise. Especially when the MR is still up for review and might change.
Comment #46
kristiaanvandeneyndeComment #47
kristiaanvandeneynde@smustgrave Updated the IS to reflect the actual bug and how it should be fixed. The initial IS was just one case of running into it, but #3452181: VariationCache needs to be more defensive about cache context manipulation to avoid broken redirects exposes the problem in more detail as it also causes incorrect cache redirects to be written.
Comment #48
bramdriesenMaybe because I'm tired, but I had a really hard time reading the doc block to try and understand what it was trying to say. Grammatically strange sentences and missing capital letters spotted.
Code wise not much to add to the review. Looks clean and the other comments are easy to read.
Comment #49
kristiaanvandeneyndeHopefully this is a bit easier on the eyes.
Comment #50
bramdriesenAlready better 🙂
Can't the "to" be removed? Or specify to what we're adding metadata?
Anything you have? ... merged
inautomatically?I'm no native english speaker, but with above changes I suggested it would read easier to me. Te addition of "have" is probably not needed.
Comment #51
kristiaanvandeneyndeThat would make it broken English. You have the infinite "to add" and then "to" as you're adding to something (the CacheableMetadata).
This documentation is for the $cacheable_metadata parameter, so I think it should be clear that that's the thing you're adding to?
You can use "Anything you specify here" or "Anything you specified here", depending on whether you want it to read like a forward-looking suggestion or vice versa. E.g.: Anything you say will be used against you (warning, you haven't said anything yet). Anything you said will be used against you (informative, you already said those things).
I wouldn't use "have", though. Present perfect doesn't seem right here. If you want we can change it to the simple present tense.
As for "merged in", it's because you merge whatever was specified into the final result. Because that sentence has no mention of "the final result", the "to" part of "into" is dropped. Example: John was about to walk into the river, as he walked in, he felt something sharp beneath his feet.
Comment #52
bramdriesenNah, it’s fine then 😇 again, not a native speaker. 😉
Comment #53
neclimdulI think I understand the reason of the change and it makes sense. Previously you'd have to return true from applies if you _might_ apply and then builder would have to do the actual check and apply the cache-ability. Yeah, that's a pretty big gotcha when building these builders.
Looking at the merge, there's a lot of duplicate adding of the route to the cache-ability. I guess maybe this is a dumb question and probably well understood in the building of BreadcrumbManager but isn't varying by route kinda a baseline implied functionality for breadcrumb builders? That's why we pass the route_manager in right? Should the manager just include that by default and this change would only be used for more exotic cases?
If it helps the discussion, and example where it doesn't cache by route in core is PathBasedBreadcrumbBuilder. I found that's broken on our site and actually decorate the core version with one that adds the route to the cache-ability of the path breadcrumb builder.
Comment #54
kristiaanvandeneyndeNo worries :) I'm not a native speaker either but I tend to want to get the grammar right, so I tend to look these things up when in doubt. You did make me think, though, and I believe the simple present is preferred here, as the implementation is arguably still to be written at the time of reading the documentation.
Will change that little detail and leave at RTBC.
Comment #55
neclimdulleft rtbc because this isn't a change this merge makes just something it highlights so could be a follow up.
Comment #56
kristiaanvandeneyndeIt could, but just like you I'd argue that that is out of scope. For now I would focus on fixing the bugs caused by not correctly setting any cacheable metadata when the applies() check returns FALSE.
Comment #57
bramdriesenRE #54 now it reads better to my mind 😅
Comment #58
alexpottI have some BC concerns added to the MR.
Comment #59
alexpottThis issue also requires a CR as implementations in contrib and custom code will need to change.
Comment #60
kristiaanvandeneyndeWill try to fix these minor issues tomorrow. In the meantime, I answered the question regarding cache operation counts.
Comment #61
kristiaanvandeneyndeOnly needs a follow-up to change the signatures in D12.
Comment #62
kristiaanvandeneyndeFollow-up for D12 here: #3459277: Enforce second parameter in BreadcrumbBuilderInterface::applies
Comment #63
mxr576Reviewed the CR and both the MR. I could only nitpicking on consistency regarding whether we have
, see [link].or. See [link], but since PHPCS is happy, I can be happy with the current comments too, they are still readable and understandable.Comment #64
alexpottCommitted a75df83 and pushed to 11.x. Thanks!
Comment #67
alexpottDiscussed with @catch and we agreed to backport this to 11.0.x and 10.4.x.
Comment #70
james.williamsThanks for this improvement! I have though found a bug for a follow-up: the cacheability is ignored if no breadcrumb builders apply, which can cause breadcrumbs to disappear across a whole site. Reported as #3482691: BreadcrumbManager ignores cacheability when no builders apply.
Comment #71
joachim commentedThis has broken several contrib modules that inherit from PathBasedBreadcrumbBuilder --
- #3497976: BusinessRulesBreadcrumb must be compatible with PathBasedBreadcrumbBuilder on Drupal 10.4.0 and above
- #3507998: Fatal error: Declaration of Drupal\entity_ui\Breadcrumb\AdminBreadcrumbBuilder::applies
Comment #72
kristiaanvandeneyndeThey're tagged services, which I think means they fall under this rule of not being covered by BC.
https://www.drupal.org/about/core/policies/core-change-policies/bc-polic...