Proposed commit message:
Issue #2525910 by dawehner, Berdir, lauriii, effulgentsia, larowlan, timmillwood, Wim Leers, chx, arlinsandbulte, Fabianx, Gábor Hojtsy, Dave Reid, alexpott, catch: Ensure token replacements have cacheability + attachments metadata and that it is bubbled in any case
Problem/Motivation
Tokens can use e.g. [node:title] but then no cacheable information is provided.
Therefore this could theoretically lead to:
- Cache invalidation bugs
- Security bypass
Proposed resolution
- Automatically collect cacheable metadata for all the data that is passed in. This covers mostly cache tags for entities.
- Add cacheable metadata to token processing hooks, so each token replacement can add metadata as needed. For example tokens based on the current date, node:author (which needs to depend on the user), the current user, tokens that rely on configuration and so on. Update all core tokens to do so.
- If caller does not support cacheable metadata, push it to the stack. This ensures that we collect it even if code isn't updated to do it properly.
This does not guarantee that all contrib and custom code provides correct cacheability metadata but core does now and serves as a good example for others. It is a good tradeoff to keep the API changes minimal and easy to use (As opposed to forcing every object to return a cacheability metadata object with the token replacement value).
Combined with issues like #2493033: Make 'user.permissions' a required cache context, this should be enough to keep possible security issues and cacheablity bugs due to this to a minimum.
Remaining tasks
- Review and RTBC
User interface changes
- None
API changes
hook_tokens()
and the corresponding alter hook get an added optional argument to pass in cacheable metadata. This is not an API change by itself but they are also required to to pass the $cacheable_metadata along for nested tokens.
It would technically be possible to avoid this, by introducing state on the token service and "remember" the current cacheablity metadata object. That is not without drawbacks and we think this API change makes sense to force hook implementations (at least those that have recursive calls) to think about cacheablity.
Token::replace() also has this as a new argument and callers within a render context are strongly recommend to explicitly handle it instead of relying on the fallback.
Data model changes
- None
Comment | File | Size | Author |
---|---|---|---|
#169 | interdiff.txt | 1.83 KB | effulgentsia |
#169 | 2525910-169.patch | 78.78 KB | effulgentsia |
#166 | interdiff.txt | 8.63 KB | effulgentsia |
#166 | 2525910-166.patch | 78.78 KB | effulgentsia |
#165 | interdiff.txt | 1.76 KB | effulgentsia |
Comments
Comment #1
Fabianx CreditAttribution: Fabianx as a volunteer commentedIt could be we also decide that its the callers responsibility to add the entity, etc. objects as cacheable dependencies to the output, but I do not know enough about how token works in D8 to confirm or deny that.
Comment #2
Wim LeersAFAICT this must be critical. E.g. a node with
Hello, [user:name]
token needs to bubble theuser
cache context, but currently won't. This needs to be fixed.Comment #3
larowlanwhy not
Comment #4
larowlanPosting broad concept for review before I start converting hook_token implementations.
Basic concept, new TokenReplacementResult which extend from CacheableMetadata and wraps the raw replacement text in a value object with cacheability data.
Thoughts?
Comment #5
Fabianx CreditAttribution: Fabianx for Drupal Association commentedOverall approach looks good to me:
Not sure we should have that.
The problem is that the cacheable metadata gets lost at that point.
If we wanted to support that (IMHO getReplacementText() is enough) we should probably push the metadata to the stack at that point - see CSRF issue.
Comment #6
Wim Leers#4: wow, excellent first patch! :)
s/information/metadata/
s/data/metadata/
s/is/are/
s/language/languages:language_interface/
s/language-specific/interface language-specific/
Is that a word? :D
[…] E.g. 300, if the text will become outdated after 5 minutes.
+1 for the similarity (even in docs) to
\Drupal\filter\FilterProcessResult
:)s/processed/replacement/
s/information/metadata/
::merge()
returns a new instance; it does not update the object it is invoked upon.So, these need to be:
#5:
Good point — but
FilterProcessResult
also has this; so whatever we decide here, we should also apply there.Comment #7
Wim LeersComment #8
timmillwoodRe-rolling with changes from #6
Comment #9
Wim Leers#8: an interdiff seems to be missing; and it looks like there's a whole bunch of unrelated changes in your patch :(
Comment #10
timmillwoodAh &%£$%, git branch fail.
Comment #11
timmillwoodTry again to update with comments from #6
Comment #12
Wim LeersWe should not allow a raw string; because that means certain token providers would not be forced to think about cacheability metadata.
This is also the reason we require filters to return
FilterProcessResult
s.Comment #13
BerdirWe discussed this for quite some time in a call with @WimLeers, @dawehner, @alexpott, @catch, @effulgentsia and @GaborHoitsy, @Fabianx.
Trying to quickly summarize what we discussed.
Unlike the filter API which has a handful of classes and limited amount of things that need to be changed, hook_tokens() consists of *huge* switch statements and with the API in the above patches, every single one of them would have to change. That's a big API change and a very painful one, since there is a lot of repetitive code around what we have now.
We were basically discussing two conceptual things here:
1) How to officially/globally treat token replacement in terms of rendering and cacheability. We basically have three options:
a) Enforce that everything returns cache metadata
b) Go with a best effort approach of making core return cacheability metadata and documenting it as such and that everyone who uses it and is concerned about always returning correct data should consider using placeholders.
c) Always treat token replacements as uncacheable (max-age: 0)
The decision is b) best effort. The reason for this is that we have a few ideas to auto-detect many dependencies and API's to make it relatively easy to add additional ones. Core itself does not use it anywhere in rendered output and we believe that security issues are going to be rare (e.g. combined with the fact that we want to make user.permissions a required context).
As for the other options, we were concerned about the disruption of a) and we want to avoid forcing that token replacements are non-cacheable, when in many cases they will be. (e.g. node related tokens in a rendered node).
2) We also discussed how to design the API here and support token implementations. And we basically came up with 3 things:
a) We check each entry in $data for CacheableDependencyInterface and add the metadata automatically. So if you pass in ['node' => $node], we will automatically add the cache tag for it, and if you have a token like node:author:name that re-enters the replacement system, we'll also add the tag for the user. It will however not work when using node:author directly.
b) To simplify that, instead of forcing them to create a response object as implemented by the previous patches and return it, we pass along a $cacheablity_metadata as a new optional argument. So node:author can do $cacheable_metadata = $cacheable_metadata->merge(CacheableMetadata::createFromObject($account).
c) We're also considering an declarative way to define cacheablity metadata in hook_token_info(), which would basically look like this:
That will only work for static dependencies, but it is especially useful for config dependencies like this.
We think that those three things combined and making sure that core implements it properly to a) actually work and b) serve as a good example will result in contrib/custom code picking this up as well.
Comment #14
Wim LeersFor that, see #2493033: Make 'user.permissions' a required cache context.
Comment #15
dawehnerThis is a starter with a) and b)
Comment #16
Wim LeersLooks like a great first patch!
Needs to be assigned to
$cache_metadata
, otherwise the merged data is lost. You got it right in the many more additional places where merging happens :)Comment #18
dawehnerThis should be reivewed now...
Comment #19
Wim LeersLooking great!
Hrm not sure I like this; the casting shouldn't be necessary if it'd comply with the interface.
The context manager is something different :) Let's write "cache context manager" instead.
Comment #20
dawehnerThere we go.
Comment #21
Wim LeersJust one more nit. This IMO now needs review from Berdir.
The class says "contexts", plural, yet here it is singular.
Comment #22
Berdirhuh?
missing docs for the new method, also for replace().
I was wondering if we should ensure that the cachableMetadata object is not lost by storing it on the token service and re-using it on re-entrance in replace().
I see you solved that by making it required. We can have API changes here I guess so that's OK as well.
missing assignemnt ?
I guess this needs a bit more documentation on how it is supposed to be used. possibly even a merge code snippet.
full namespace here but not elsewhere.
This looks great to me. We have a bunch of kernel tests for token replacements, we could expand them a bit to also collect this and assert it a bit? to test the actual implementations. And I think it would be great to have something like a test controller that actually prints tokens out and cares about cacheability.. we could then also use that code as an example for the change record.
system_tokens() also has date that default to NOW if no date is given. In that (and only that) case, we need to disable caching. I think.
user_tokens() has a current-user token. If that is used, then we should add the user cache context.
Comment #23
dawehnerThank you berdir for the review!
Comment #24
Wim LeersThat sounds like a great plan.
And if it prints the current user's name then we need to also associate the User entity's cache tag.
Comment #25
Fabianx CreditAttribution: Fabianx for Drupal Association commentedOverall looks great already.
Not sure it was discussed, but I think the replace() should push data to the render_context if the supplied cacheable_metadata is not existing as a parameter.
Maybe count(func_get_args()) == x could be used for that?
Comment #26
BerdirThat happens automatically again, current-user just re-enters it with 'user' tokens and provides the user entity for it. Which then triggers the generic code to get cacheability metadata from it. :)
Comment #27
dawehnerWhile this looks great, there have been not 0 bugs in the existing patch.
Comment #29
Wim LeersDown to 5 failures, yay!
Right :) Great!
Love the sarcasm here.
Ugh, no. We used to have a constructor and removed it for good reasons.
Comment #30
dawehnerWell yeah, I feared I need that for easier readable test coverage, but it turned out, this was not the case.
Less failures, less code
Comment #32
dawehnerBack to green.
Comment #33
dawehnerTo be clear, this is in a reviewable state.
Comment #34
larowlanThis looks awesome, just some nits and a request for some extra docs.
nit: wrong indent
%s/ablity/ability
of or off or from?
nit: %s/here into account/into account here
Should this also include the cache tags from the medium date format? Or is that what 'rendered' is? In which case can we add a comment to that effect.
leftover debug?
nice
should we add a test for the raw node:created to check the medium date format tags like the other entity types - or is that overkill?
%s/actula/actual
sweet - this serves as a great example - can we add this to the docblock of Token::replace, in an @code block - so others know the best way to collect and apply the metadata? I'd expect api.drupal.org for Token::replace will be the place most will goto in the future, rather than the change notice.
nit: should depend on node and user
doesn't appear to be used?
Comment #35
larowlanworking on change record
Comment #36
larowlanChange notice draft: https://www.drupal.org/node/2528662
Comment #37
lauriiiFixed all from #34 except 5 & 8. Also updated the issue summary since some of the typos existed there also.
Comment #39
lauriiiSorry for this..
Comment #40
dawehner@larowlan++
@laurii++
These changes look great! Do you think we need something more?
Comment #41
larowlanMinor nits - other than that rtbc in my books
therfore » therefore
an » a
for » to
Comment #42
Fabianx CreditAttribution: Fabianx for Drupal Association commentedYes, CNW for #25, if no one passed cacheability metadata to collect, then (same as URLs) we still want to collect it and push it to the current render context.
This is needed to ensure, while this keeps BC, that legacy code does not introduce security issues.
Comment #43
dawehnerGood point. Sure, we can do that.
@berdir and I discussed that and though we should just do that inside a render context. Outside of that, there are good usecases
to not care about cacheablity, like tokens for a generated email.
The helper is presented by #2351015: URL generation does not bubble cache contexts
Comment #44
dawehnerThis time with a patch
Comment #46
dawehnerMaybe that
Comment #48
dawehnerIt is amazing, if things actually work as they are supposed to do.
Comment #49
Wim LeersNit: missing trailing period.
Missing the ampersand.
Missing the
|null
.Also: I think this should mention that if none is given (i.e.
NULL
is passed), that the cacheability metadata for tokens will then be bubbled using the render system, if this token replacement is happening as part of the render systemOopsie :)
Also: we add a
\n
before@return
.Missing ampersand.
s/cache/cacheability
s/cacheable/cacheability/
Let's put single quotes around
system.site
andsite-name
for clarity.Missing ampersand.
s/cche/cacheability/
Should have a
\n
in between.The token replacement system.
This is a test for the correct/intended usage.
But we also want to repeat this test where we don't collect cacheability metadata. And it should have the same end result, thanks to the render context detection/integration.
Nit: cache contextS manager — plural.
Yay! :) :) Finally!
Comment #50
dawehnerSo yeah this was useful, as it uncovered a bug.
Comment #51
Wim LeersJust nits now. Assigning to Berdir for review.
Great, that's what I thought I remembered from #2351015: URL generation does not bubble cache contexts (which is the origin of this code) :)
Great, but still missing the "also" from #49.2.
Should be
Also: 80 col violation.
Comment #52
BerdirThis looks very close to me. Very nice test coverage, @dawehner++
I'd just say something like the implementation instead of the module developer.
Probably also avoid "you" here but use it or the alter hook?
I like cacheablity :)
As discussed, should be cacheable_metadata everywhere instead of cacheablity_metadata/cache_metadata.
current request *time*.
can we add a comment here somewhere that we explicitly don't apply the cacheability metadata?
Why initialize #markup if we always set it?
token.module already has a token_test.module, should we maybe name this one token_render? or token_test_render.
Comment #53
dawehnerOh missed that ....
Too bad for token module I'd say :)
Comment #54
Wim LeersVerified that all feedback in #52 and #53 has been addressed. Assigning to Berdir, because he wanted to take another look before RTBC'ing.
80 cols. Can be fixed on commit.
Comment #55
Fabianx CreditAttribution: Fabianx for Drupal Association commentedThat is an interdiff review (I used the wrong link):
I wonder why we don't have a mergeTo() method ...
Lets add a $build = [] before that perhaps?
I hope we have tests for that :).
--
$passed_in_cacheable_metadata
please though
Why is it optional for generate?
Is that public API?
Should we throw an Exception if that is used wrongly?
Nice!
Comment #56
dawehnerWell, that is a good question. Its used to traverse into nested tokens, like node:author:uid. So yes we could require the cacheablity metadata as an earlier parameter, as you could make less mistakes.
Comment #58
dawehnerJust a reroll for now.
Comment #61
BerdirUpdated the issue summary. Reviewing the change record next.
Comment #62
BerdirUpdated the change record, also included the hook changes (which will affect more people).
To be able to RTBC this myself, I'd like to one more detailed review. I won't find time for this during the day, maybe tonight.
We've had a number of reviews from different people, all agreeing with the approach and implementation. We've also had plenty of "nitpick reviews". What I've seen so far and before is also fine.
So it's more about being confident enough to actually set it. If someone else feels bold enough then that's fine with me too.
Comment #63
chx CreditAttribution: chx commentedI understand the push to get Drupal 8 released and now I know there has not been anyone for a long time enforcing the document gate. Still, you can not, simply can not document hasRenderContext as it is documented here. It would be better to have no doxygen because it's much easier to find those with automated tools than these fake "yay we have doxygen" things. Seriously, what does the doxygen for hasRenderContext tell you that the function name does not?? At least crosslink to RenderContext itself.
Comment #64
dawehnerI'm curious about changing the signature of generate to:
Comment #65
dawehnerThere we go.
Comment #67
dawehnerThat should fix it
Comment #68
Gábor HojtsyYet one more "nitpick review":
Remove empty line.
I think you made the hook signature different because the values are always passed on anyway, right? If so, the docs need to be changed above.
Comment #69
larowlanFixes #68 and makes the example hook implementation more valid.
Comment #70
BerdirThis looks great, I few questions/feedback below. More than fine to move most of it to non-critical follow-ups.
Setting to needs work for some of comment things and what can be addressed here. I can open follow-ups later for some of the suggestions below if you agree with doing them.
I'm wondering what we should do for places that use token replace for non-output, like sent mails (which *is* the primary use case for it at least in core). That shouldn't be added to the render context even if there is one. Should that maybe explicitly be passed and then discarded? That seems a bit weird...
$passed_in_cacheable_metadata sounds like the actual object, not a boolean. Don't have a better idea though.
It's not the tokens that end up there but the cacheability metadata of the token replacements.
This line can be removed now that the argument is required.
note to myself: need to check the change record whether this is also updated there.
We should also check for the cache contexts of this page, which should contain the user context now.
Wondering if we should do a second request with a different user. That wouldn't really do anything right now, but it would be additional test coverage for smartcache. We probably should add quite a few additional tests there for cases like this.
There's an issue to use JS in since formatting e.g. in views so that it can be cached. obviously not possible here.
Interesting, this doesn't work through ViewExecutable. Should we maybe open an issue to implement that interface and pass calls to the storage, maybe even invoke something else?
Can see that people would do createFromObject($view).
Comment #71
dawehnerOn CLI, this does not matter, as you are outside of a rendering context.
On uncacheable request (Cron is probably one of them), it also doesn't matter.
For usecases like form submissions, it also doesn't matter because you are dealing with a POST request.
Went with a slightly better variable name.
#2530572: Implement cacheable dependency interface for ViewExecutable
Comment #72
BerdirOh, I'm not sure why I missed this :)
You actually don't need the first line, generate() should add that automatically, only the user cache context.
Quickfix for that and then RTBC :)
Comment #73
dawehnerGood point.
Comment #74
BerdirOk, I think this is ready.
We've had plenty of reviews, both architecture and nitpicks, lots and lots of tests, updated issue summary and change record.
dawehner++
Comment #75
mbrett5062 CreditAttribution: mbrett5062 commentedSome minor nits that can be corrected on commit.
Missing "so they"
"This is passed to lower token implementations so they can attach their metadata."
s/token/tokens
Comment #76
mbrett5062 CreditAttribution: mbrett5062 commentedBy the way I am no big fan of the use of contractions in formal documentation.
I know this is nothing to do with this issue, and so I am sorry about the noise.
Maybe I will open an issue for 8.1.x or something, to go through and change all those to the full English.
I.E. aren't = are not
And so on.
A quote from everythingenglishblog.com
Emphasis is mine.
Comment #77
mondrakeNit - looks like a typo - it should be $cacheability_metadata or $cacheable_metadata to follow token.api.php, isn't it? There are several instances of that.
Comment #78
BerdirI thought we had fixed that, missed that.
Yes, as written above, always $cacheable_metadata AFAIK.
Let's fix that and the two nitpicks above.
Comment #79
dawehnerMeh.
Comment #80
mbrett5062 CreditAttribution: mbrett5062 commented@dawehner thanks for also changing the aren't to are not.
I did not intend for that to be a change here, was just noting my aversion to contractions in documentation.
But the change is welcome.
I have opened an 8.1.x issue for this to be done throughout the code.
Remove usage of contractions in all documentation
Comment #81
dawehnerMh.
Comment #82
BerdirMake sure there there's no cacheablity left in the patch :)
Back to RTBC.
Comment #83
mondrake:)
Comment #84
catchIs it worth providing a method on CacheableMetadata that operates directly on $this so we don't have to overwrite the variable?
Like CacheableMetadata::combine() or CacheableMetadata::addCacheableMetadata()?
Comment #85
dawehnerYeah especially because it creates an object and then throws it away, its kinda a level of indirection which makes the code harder to read.
Comment #86
BerdirWhat if we add addCacheableDependency() to CacheableMetadata? We're actually doing the same to AccessResult in #2524082: Config overrides should provide cacheability metadata I believe. With another similar method for a render array or maybe even the same?
Comment #87
Wim Leers#86: Or even just add it to
\Drupal\Core\Cache\RefinableCacheableDependencyInterface
!Comment #88
catchKnocking back to CNR. Those changes are a big part of the patch and it's what contrib modules will need to do to update for the API addition, so might as well make the change once.
Comment #89
BerdirMakes sense.
+1 to adding it to RefinableInterface in general but we're not actually implementing that yet, so I think we should do that in a separate issue, otherwise we'll have to change Entity here as well. We can just implement it in a consistent way?
We could even consider to not pass it by reference then, that would result in fewer changes in the token service to support the by-reference thing?
Comment #90
Wim Leers+1
I'll leave that to the Token system experts. I trust your judgment.
Comment #91
dawehnerWell, indeed CacheableMetadata has a todo for RefineableInterface, but well, this would not really help the point mentioned by catch in #84
I went with mergeInto() for now but feel free to discuss a better name. This was just the first one I came up one and it was short enough.
Comment #92
BerdirForgot about RefinableCAcheableDependencyInterface for this issue. That said, we should definitely go with addCacheableDependency() here, that saves us another object and makes it even simpler:
$cacheable_metadata->addCacheableDependency($object);
Comment #93
Fabianx CreditAttribution: Fabianx for Drupal Association commented+1 to #92
Thanks for that, it makes sense for merge() to be idempotent, but at the same time it also makes sense to have the usual addCacheableDependency().
Comment #94
dawehnerMh, the problem is that once we deal with interface, the optimization of CacheMetadata::merge() is no longer as straightforward as you need to call the methods at least
Comment #95
Fabianx CreditAttribution: Fabianx for Drupal Association commented#94: That gets offset by the case that the caller would have called CacheableMetadata::createFromObject() anyway, so except that we don't need to do that if the object is of CacheableMetadata instance, we should be neutral in terms of effort ... (i.e. we can only win by encapsulating)
Comment #96
dawehnerYeah this is indeed a good point.
Comment #98
dawehnerups
Comment #99
BerdirI think we could now use invokeAll() again since we no longer have a special by reference argument and save a few lines of code there.
Looks great otherwise, I really like how addCacheableDependency() simplifies the code. We might want to open a normal follow-up task to convert existing code to this method when possible.
Comment #100
Wim Leers+1
This is wrong; it's not only
CacheableMetadata
objects!Let's change this to:
— this is what\Drupal\Core\Render\RendererInterface::addCacheableDependency
's docs and\Drupal\Core\Cache\CacheableResponseInterface::addCacheableDependency
's docs say.Are we sure we don't want to do
… just like those two other places?
Nit: unnecessary
\n
.Comment #101
dawehnerThere we go.
Thank you for the good reviews!
Comment #102
BerdirThis doesn't actually match the implementation or the forced type hint. You need to remove that and do that inline and in else, set max age to 0.
If that's what we want to do?
Comment #103
dawehnerGiven that we want to make the API as easy as possible we probably should check inside the method?
Comment #104
Fabianx CreditAttribution: Fabianx for Drupal Association commented#103: Yes, as in the implementation of e.g. addCacheableDependency() in CacheableResponse. :)
Comment #105
BerdirThis does the instanceof check and should work the same way as createFromObject().
I moved the optimization to the addCacheContexts()Tags() methods. I'm not sure we do that here since it means we e.g. don't run the cache context validation in some cases and then you'll only get an exception when something else is merged in or it is merged into something else.
Can also provide a patch without that, or we can do it in the trait in the follow-up to use that in CacheableMetadata/AccessResult.
Comment #106
dawehnerI think this is totally fine, given that the actual cost of those objects has been in the validation (see previous profiling on the assert issue)
Sorry for not finding the motivation to work on it.
Comment #107
webchickMight be a moot point since it's RTBC, but tagging as a beta target since it has some contrib impact and would be nice to get in by then.
Comment #108
Wim Leers+1
So this means a worse DX for performance. Worse so than https://www.drupal.org/node/2259531, which still allows the better DX to be enabled; here you cannot enable a better DX.
I see the reasoning, but I think doing that optimization belongs in a separate issue with its own discussion; it shouldn't be done as part of a critical.
OTOH,
\Drupal\Core\Cache\CacheableMetadata::merge()
already follows this pattern. IOW: we're already inconsistent about this principle in the area of cacheability; we're basically blocked on #2408013: Adding Assertions to Drupal - Test Tools. to provide a better solution, that does maintain good DX.Comment #109
BerdirThought you'd say that ;)
I actually think it makes sense to do this, if we want to we can still do the validation in the optimized case. Even if some of that logic is moved to an assert then we're still doing a bunch of method calls, sorting and so on even if we're merging nothing with nothing or only one side has something.
But I agree with not doing it here. We can optimize it without losing the validation in the methods in the trait when we use that everywhere in the follow-up.
Her'es a patch without that optimization.
Comment #110
Wim LeersRTBC +1
Found a few nits that can be fixed upon commit.
Nit: s/to the render context./to the render context, if any./
Nit: indented one space too many.
Nit: s/cacheable/cacheability/
(For consistency.)
Comment #111
dawehnerRTBC +1
Comment #112
arlinsandbulte CreditAttribution: arlinsandbulte as a volunteer commentedOnly fixed Wim's Nits from #110
Comment #113
arlinsandbulte CreditAttribution: arlinsandbulte as a volunteer commentedBack to RTBC if green.
Comment #114
dawehner@arlinsandbulte always provide an interdiff, if possible. Here is one
Comment #115
alexpottNot used.
It's not just global tokens - it's related objects... like authors of nodes.
Comment #116
dawehnerThank you alex.
Addressed the review, I hope
Comment #117
Wim LeersEDIT: cross-post with dawehner… I'll just post mine too, then we can choose which we prefer.
Comment #118
BerdirLooks like testbot likes Wim's version more, doesn't want to test the patch from @dawehner :) I think it's slightly better explain in #117.
Change record looks better. Wondering if we want to move more of the explanations into the intro chapter above the long examples.
Comment #119
Gábor Hojtsy@Berdir: the longer explanations seem to explain with concepts in the code. So either way, people need to read them with the code. Reading it, I think its fine now below.
Comment #120
dawehner+1 for #117
Comment #121
Wim LeersGiven #118 + #119 + #120, and the fact that it's just a very minor reroll, re-RTBC'ing.
Comment #122
alexpottin views.tokens.inc is not passing the cacheability metadata along and looks broken and untested. Hmmm relies on token module... now that views is in core I think the token module needs to provide this.
Comment #123
catchComment count is currently broken for node links as well, but we should be able to use the same approach as #2530846: Fix and improve comment cache tag usage - have to invalidate the entity cache tag when a comment is posted.
Nit on the patch:
+ * (optional) The cacheablity metadata. In case its not specified it will
s/cacheablity/cacheability/
Comment #124
Fabianx CreditAttribution: Fabianx for Drupal Association commentedI think we should be using bubbleable metadata here everywhere instead of cacheable metadata - the difference to config overrides and entity runtime cacheability is that here something is early-rendered but needs cacheable metadata.
In that this is comparable to Filter formats, which also specifically support placeholders.
We already had that problem of missing the ability to add attached metadata in #2512132: Make CSRF links cacheable, where it turned out using placeholders is better.
In the same way, someone could want to use e.g. a placeholder for the max-age = 0 [entity:comment-new], which would make a lot of sense ...
Using just cacheable metadata here, makes that impossible.
Comment #125
Wim LeersI think @Fabianx is right.
Comment #126
dawehnerTrying to address some of the feedback
Comment #127
dawehnerRegarding the following tokens: [views:total-rows] [views:items-per-page] ... this would ideally get the cacheability metadata from the View object itself, which tracks them over time,
like for example in
\Drupal\views\ViewExecutable::setItemsPerPage
I agree, in case you want to bubble up placeholders you need bubbleable metadata ... the question is, where does that stop? Don't all objects potentially want to bubble up placeholders?
Comment #128
Wim LeersNo: because not all objects are renderable.
E.g. access results never render anything, you just can't render them; it doesn't make sense. Hence just cacheability metadata is fine.
E.g. tokens do render into something. Hence bubbleable metadata (cacheability + attachments) makes sense.
Comment #129
alexpottI think that adding the account is not necessary - as the token system should handle everything in $data - no?
Comment #130
Fabianx CreditAttribution: Fabianx for Drupal Association commentedComment #131
dawehnerThank you for your actual helpful comment!
Thank you wim for your kind comment.
Well, while you think nodes aren't renderable, but views are. What blocks nodes from implementing
\Drupal\Core\Render\AttachmentsInterface
.Comment #132
Fabianx CreditAttribution: Fabianx for Drupal Association commented#131: The difference however is that Nodes and Views are usually not early-rendered to strings that are then concat'ed with other strings. So you have lots and lots of opportunities to change the node or view render array via other means and hooks.
Only urls and tokens have the property of being early rendered to strings in core right now - as far as I can see.
Comment #133
dawehnerOther examples are for example date formats. What I'm actually not worried about is core but rather contrib and customland.
Comment #134
Wim LeersOh, heh, that is very interesting :)
Yep, think "time ago" formatting that uses JS to keep it in sync with the current time.
I think @dawehner's comments basically mean "+1 to #124" ?
Comment #135
dawehneryeah for sure.
Comment #136
dawehnerAlright, let me big renaming start ...
Comment #137
dawehnerBubble bubble bubble
Comment #139
dawehnerThis could fix more than 0 of the failures.
Comment #140
lauriiiJust some random nits I found while waiting for a train
I guess the standard is {@inheritdoc}
replacement fits now on the first line
This is missing at least package: testing, but maybe we want to add also version?
Comment #141
dawehnerThank you for the quick review laurii!
Comment #143
dawehnerLet's reupload the patch, looks like a random test failure ...
Comment #144
Wim LeersGreat, thanks @dawehner!
Unused interface.
BubbleableMetadata::addCacheableDependency()
-> kind of unfortunate naming, but also not the worst.
Just
addDependency()
would've been better probably. Thoughts?s/cacheability/bubbleable/
Shouldn't this now check both
instanceof CacheableDependencyInterface
andinstanceof AttachmentsInterface
?s/cacheability/bubbleable/
These are correct!
s/cacheability/bubbleable/
s/$cache/$bubble/
We want a similar test in
BubbleableMetadataTest
now.Comment #145
Dave ReidArgh, as the component maintainer, could someone have pinged me about this at least?
Comment #146
Dave ReidThis seems like a code smell to me. Why can't individual hook_tokens() handle if $bubbleable_metadata is set or not. Also, why isn't this passed in via $options?
Objects don't need to be marked with & in documentation.
Comment #147
dawehnerWell, in other words
::mergeInto()
was not the worst name to start with :)It seems to be that this should be ideally an OR?
Nope, they have a typo.
Ha, I thought about introducing one ...
Comment #148
dawehnerFixed #146.2 ... We no longer pass it by reference, but in difference places I have seen both and someone asked in this issue earlier.
Mh, I think passing it as options would make it less visible than it should be, given that callers should ideally pass it in. In options, it would be way easier to miss it.
Especially for ->generate() it would also make the api a bit harder to use.
Comment #149
Fabianx CreditAttribution: Fabianx for Drupal Association commented#145: Sorry, I did not realize we had a dedicated token system maintainer.
Most criticals had just been generic 'cache system' component, hence it did not feel different.
Thanks for taking the time to review it.
The issue tries to avoid API changes where possible, but instead to empower core / contrib to collect the data.
However for generate() the API change was the better solution.
Given the many optional arguments for hook_tokens(), the optional argument was the best solution with pushing to a render context in the case when it is not given (and hence the user still uses the D7 mind set).
We felt at this stage of the release this was the best compromise.
Comment #150
dawehner@davereid
Do you want to give more feedback or are you more like: Meh okay its fine?
It seems to be that this issue feels ready for the rest of us, so we hope we can move forward, because waiting is a little bit pointless :)
Comment #151
Dave ReidI still think it should go in $options because we do that with other optional things like langcode/language, etc. But it feels like I'm being overridden by this being a critical and just needing a fix, so this should probably just get in as is.
Comment #152
dawehnerThank you for your quick response. No, its not that you are overridden, I think getting feedback from you is really valueable.
Yes I agree that ideally all that cacheablity should have been first designed and not put on top of everything afterwards.
In the following I try to make my previous point a bit better.
We have two kinda similar APIs out there which already take that object as parameter:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21PathProce...
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21RouteProc...
Another point I try to make is that we should rice awareness of it, if possible. If you forgot to add cacheabilitity metadata, you can easily get into bugs (by having old data in the cache entries) or even worse security problems by serving cache entries for users, which aren't supposed to
Comment #153
Berdir$options would have been a good solution if we had to add this in 8.1.x and make it as BC compatible as possible.
But a separate argument has a number of advantages IMHO:
a) $options in a hook (or $context or stuff like that) is actually really bad for DX. Just look at our implementations, almost all of them need identical ~10 lines of code to initialize stuff based on options, check if it's set or not. It's even worse for objects. A separate argument can be type hinted and then it is clear that it is always there and what it is.
b) As discussed before, It forces implementations to think about it when the implement that hook. The chance that contrib/custom modules, especially those that are upgraded from 7.x would consider it would be close to 0 if it would be just another thing in $options that they don't have to care about.
c) As mentioned, it's consistent with how we do it in other places now. Although we also have other patterns like return value objects, but that would be way too painful hook_tokens().
I've made some small changes to this issue but that was quite some iterations ago and most of that changed again or was reviewed in detail by others. So I think it's OK if set it back to RTBC.
However, I haven't checked yet if the feedback from @alexpott in #122 has been addressed yet. I don't think we need to do everything here, I think we should open a follow-up to check some of the remaining special tokens since there is no reason to block this issue on having perfect cacheability metadata for every single token.
Comment #154
dawehnerThe feedback #122.1 and #122.3 has been addressed.
Well, they should be ideally cacheable by the comment list tag for example, right?
Comment #155
BerdirOk. I've opened #2536638: Dynamic tokens don't have correct cacheability, let's see if @alexpott agrees with getting this in and then look into those special tokens.
As commented over there, if we wait a bit for that comment issue then we can improve cacheablity a lot (or add a @todo on the other issue if we don't want to wait).
Comment #157
dawehnerThere we go.I try to disagree with the testbot.
Comment #159
dawehnerOh right, we had parts of another issue included.
Comment #161
Gábor HojtsyAll the fails are "Unwanted cache tags in response: config:system.site" which seems to be bogus. Why would we not expect the front page view to have that cache tag? It uses the site name token in the view.
Comment #162
Fabianx CreditAttribution: Fabianx for Drupal Association commented#161: Yup, those look to me to be false test assumptions as we fixed things :).
Comment #163
dawehnerYeah, I simply could not merge this file at 2 am in the morning, that happens
Comment #164
Gábor HojtsyYay, back to RTBC then. As per #155 and above. Yay! This also informs the API for the other critical at #2524082: Config overrides should provide cacheability metadata, so its good to see it landing consensus.
Comment #165
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI want to fix some docs as well, but first, here's some minor code cleanup.
Comment #166
effulgentsia CreditAttribution: effulgentsia at Acquia commentedSome docs cleanup. Leaving at RTBC rather than downgrading to NR, because this is a beta target and the beta might be tagged soon, but an RTBC+1 from someone, or feedback on what I got wrong, wouldn't hurt.
Comment #167
Wim Leers#165:
AFAICT you did this because
$account = $data['user']
, and we automatically take care of iterating over what's in$data
and if the values are instances ofCacheableDependencyInterface
, we add their cacheability metadata already.So: +1.
#166:
Nit: s/$bubbleable_metadata/BubbleableMetadata/
Nit: s/renderer/Renderer/
Nit: The comma after "e.g." seems wrong?
Nit: s/$bubbleable_metadata/BubbleableMetadata/
Given only tiny nits that can be fixed upon commit: RTBC+1.
Comment #168
Fabianx CreditAttribution: Fabianx for Drupal Association commented+1 to #165 and #166, the docs around the bubbleable metadata parameter have been the weakest point of the patch (in my re-review yesterday), so I am very glad to see that improved! :)
=> Still RTBC
Proposed commit message:
- alexpott, Gabort and catch had been on the original call that discussed this.
- Dave Reid reviewed this as subsystem maintainer.
- I have done lots of reviews on this issue.
Comment #169
effulgentsia CreditAttribution: effulgentsia at Acquia commentedFixed #167. Checked the commit credit boxes suggested in #168.
In school, I learned it as always needing a comma, but I don't know what's "right" these days, so left it alone.
Comment #170
effulgentsia CreditAttribution: effulgentsia at Acquia commentedIf someone could update the CR to reference BubbleableMetadata instead of CacheableMetadata, that would be helpful.
Comment #171
Gábor HojtsyI believe I updated it properly: https://www.drupal.org/node/2528662/revisions/view/8695782/8699842
Comment #172
Wim Leers#171: thanks! I added further clarifications: https://www.drupal.org/node/2528662/revisions/view/8699842/8699972.
Comment #173
dawehnerThank you gabor! Should we describe that the other object could also implement AttachmentsInterface?
Comment #174
Wim Leers#173: I added that, see the diff in #172.
Comment #176
effulgentsia CreditAttribution: effulgentsia at Acquia commentedPushed to 8.0.x and published the CR. Thanks all!