Problem/Motivation
This is blocking #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed as it prevents that issue from getting cacheable metadata when context is missing.
When defining the context that a plugin needs, it can be defined as required (default) or optional.
The expectation is that the ContextHandler does check the required information and does not result in an array when a context is optional.
Right now, this information is not checked at all. ContextHandler just calls getContextValue(), which throws an exception when there is no value and the context is required.
The problem is, getContextValue() is called on the context object that is *provided*, not the one from the plugin (Obviously). But that is also obviously wrong. The current user context that is provided should not even have the concept of being required or not. It's just there.
Proposed resolution
ContextHandler needs to check the context definition of the context object of the plugin. If it's optional and no context is available, then we do not need to throw an exception.
The problem with that is that there is no way to do that, there is no method to check if a Context object *has* a value. There are a number of ways to do this, likely we actually want to do multiple things if not all.
a) We can add a hasContextValue() method. Makes sense anyway conceptually, and we did that with plugin managers too.
b) We can remove the thrown exceptions in getContextValue(). I think they should not exist, it just doesn't make sense to me, but if we have the hasMethods() then we can also leave them be.
c) Force all provided contexts to not be required. It would be even better if they wouldn't be able to be required or optional at all I guess that would require different definition objects which would be a considerably definition object.
Comments
Comment #1
berdirExtracting the relevant parts from #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed, let's see what happens.
Probably needs some integration tests as well and not just unit tests.
Comment #2
berdirComment #3
wim leersLooks good.
Just a small remark about the code structure.
Wouldn't "if/else" be clearer then "if,continue,if"?
This "continue" seems similarly unnecessary.
Comment #4
berdirYou're right, that can be simplified.
Added some kernel tests, to have some useful failing tests. Found that I missed one case before, I also need to unset the mapping if a context is optional and completely missing.
Comment #5
berdirNote. Unlike the implementation in #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed, thhis currently only implements a). Not sure what to do about b/c yet.
Comment #6
eclipsegc commentedNeed some explicit (bool) castings.
Worth noting that berdir's code tip-toes around the plugin's implementation of setContextValue() because that method can throw an exception. It needs to continue to throw that exception in the case of someone not using the ContextHandler to set contexts on an object, so I approve whole-heartedly of the approach here. ++ all around, change the boolean problems and I'm very on board.
Eclipse
Comment #7
berdirThe || actually forces the return value to a bool, but I wasn't sure myself and had to test it. So, making that clear in the code doesn't hurt I think.
Comment #9
berdirMessed up the test-only patch.
Comment #11
berdirFailed as expected.
Comment #12
wim leersHere's a review with minor stuff. This looks great to me, but I think only EclipseGc/tim.plunkett can RTBC this. At least definitely not me.
Nit: I'd have expected
Nit: s/if/If/
We use
sprintf()for these.(Yes, much code is doing this incorrectly, but Alex Pott will push it back for that.)
Name mismatch?
Comment #13
berdir1. I don't think that makes sense. As written above, || actually forces the return value to bool already, we just added the type casts to make it really obvious. Your suggestion would cast bool to bool.
3. Always the same problem then, should we also update the existing call below? I hate those exceptions and would love to remove them completely but that's a different discussion.
4. Discussed, wasn't sure what mismatch you mean exactly. It's OptionalContext vs. a specific context type (entity:node). I kind of like my name and I chose node because we actually have an optional context for them, so we could do a real web tests with block conditions for it. To be honest, I'd prefer to keep it, maybe add a comment why we use that. Would make it easier to write web tests for it in case we ever need that. Other opinions?
Comment #14
wim leerssprintf()for this one. It's not as jarring as seeing different array syntax.Comment #15
dsnopekComment #16
berdirComment #17
berdirI hope that comment is enough to address 4. :)
Comment #18
eclipsegc commentedLooks good to me and is passing. This is a big oops, the sooner this is fixed the better.
Eclipse
Comment #19
tim.plunkettThis method already has @throws, so we're good here.
The unit and KTB tests are also looking good.
+1 for RTBC!
Comment #20
fabianx commentedUber nit - As the logic is now the $contexts[$context_id]->hasContextValue() will be executed two times.
Using:
would avoid that.
----
Written that the whole logic can be even more simplified:
- Move unset() of the mappings out of condition.
=>
That should make that code way simpler to read ...
Leaving at RTBC as a pure refactoring this could be follow-up.
Also: RTBC + 1
Comment #21
berdir@Fabianx: The problem with your suggestion is that the key in $mappings must not be unset when the context doesn't exist and is required. Also, #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed will need to add the cacheability logic in case the context exists, even if there is no value. One goal of this issue/patch was to make the additional changes there as small as possible.
Comment #22
fabianx commentedOkay, could we still try to avoid calling ->hasContextValue() twice? (e.g. the first suggestion)
Comment #23
berdirYes, that should work. Like this?
Comment #24
fabianx commentedBack to RTBC, thanks for fixing!
Comment #25
eclipsegc commentedDoesn't check if it's required if it's present +1 to rtbc.
Eclipse
Comment #26
alexpottThis issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 05aa5ea and pushed to 8.0.x. Thanks!
Comment #28
klausiInteresting, this broke the Rules unit tests for node conditions: https://travis-ci.org/fago/rules/builds/69448449
Thanks to git bisect I was able to trace it down to this issue.
Not sure what is going on at all here since https://github.com/fago/rules/blob/8.x-3.x/tests/src/Integration/Conditi... only makes use of setContextValue() and nothing else. What I found so far is that the ->isPromoted() method on my mocked node has already been called twice when the condition is evaluated, which does not make sense. Will report back here once I make progress in tracking this down.
Comment #29
klausiActually the test fails started with the commits from #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed.
This is now fixed in Rules: https://github.com/fago/rules/commit/cfa9337d05d5b7944d50a372d33e14d14a5...
Moral of the story: don't use $this->at() in a PHPUnit test unless you know what you are doing. It can behave in unexpected ways. In our case additional methods are called on the node mock object ("getCacheContexts" and others) which PHPUnit matched to the configured method calls with ->at().