Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
In TypedData land, you deal with DataDefinition objects:
DataDefinition::create('string');
EntityDataDefinition::create('node');
With ContextDefinitions, you don't have that luxury:
new ContextDefinition('string');
new ContextDefinition('entity:node');
With #2671964: ContextHandler cannot validate constraints, there is new entity-specific logic that should not be intermixed with the generic ContextDefinition class.
Proposed resolution
Deprecate using the constructor directly (is this possible?)
Add ::create()
Use EntityDataDefinition where possible
Remove all strpos()
checks for 'entity:'
Remaining tasks
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#67 | Screen Shot 2018-07-16 at 13.20.25.png | 23.69 KB | mtodor |
#61 | interdiff-2932462-58-61.txt | 1.85 KB | phenaproxima |
#61 | 2932462-61.patch | 64.48 KB | phenaproxima |
#60 | interdiff.txt | 3.31 KB | tim.plunkett |
#59 | interdiff-2932462-56-58.txt | 28.84 KB | phenaproxima |
Comments
Comment #2
larowlanBlocker is in
Comment #4
phenaproximaHere's a first, very rough attempt. Needs a lot of work, but I want @tim.plunkett or someone to validate the approach.
Comment #5
phenaproximaUgh, sorry, my changes to composer.json were in there too. Here's the real thing.
Comment #6
tim.plunkettThis looks GREAT.
I think it could use some dedicated tests, and it needs docblocks and such.
Only one point below
<3
Shouldn't this be
return static::fromEntityType($entity->getEntityType());
?<3
Comment #9
japerryFixes from Tim's suggestions.
Comment #10
tim.plunkettGets
Is this a string or an object? Or both?
Oh, both. Hmm. This might be a bit of overkill.
If this switches to the object, please typehint EntityTypeInterface
Comment #12
japerryComment #13
japerrybah sorry 10 is wrong here is what the interdiff was actually made against.
Comment #14
japerryRemove an 'F' to 'f'
Comment #18
phenaproximaThis should bring the tests back to green.
Comment #19
phenaproximaAnd this moves the entity-specific stuff out of getSampleValues(), thus cleaning up ContextDefinition completely. Hopefully tests pass.
Comment #20
phenaproximaGiven the changes I had to make to the tests, we are now making a couple of explicit calls to EntityContextDefinition and EntityContext. I think that is probably good enough for test coverage.
Comment #21
tim.plunkettShould this check
if (!$this->getConstraint('EntityType'))
first?This needs to be a
yield
, this method has no return valueMissing trailing .
This test is covering
\Drupal\Core\Plugin\Context\ContextDefinition
, but all the entity-specific stuff has moved to a new class. So IMO a new test is needed, and anything entity-related should be removed from this testAlso, I think this deserves a CR.
Comment #22
phenaproximaThanks! All feedback fixed, except for the change record.
Comment #23
tim.plunkettLooks great! Thanks.
Just need the CR now.
Comment #24
phenaproximaChange record written: https://www.drupal.org/node/2976400. Cheers!
Comment #25
andypostreturn could be removed
Comment #26
webchick@phenaproxima and I grepped contrib for instances of Context(ContextDefinition('entity and there are quite a few instances, including one in GDPR module (not like anyone's gonna be using that! ;))
Unfortunately this patch introduces a BC break because consumers of the API aren't sub-classes and therefore can't use protected methods. So we need some kind of BC shim along with this patch to avoid breaking modules in 8.6. Sorry. :(
Comment #27
phenaproximaHere's an attempt to implement a BC layer.
Comment #28
phenaproximaOops, forgot to revert my changes to the Context class. I don't think we need a BC layer in Context (yet), because EntityContext is just a factory. The context definition is the important thing.
Comment #29
phenaproximaI think we'll need to update the CR to explain the deprecation.
Comment #30
tim.plunkettThe BC looks good.
I think this needs a !instanceof check for EntityContextDefinition, otherwise, when the new class is used, this code will still fire.
There is a missing return after the first yield.
I think this needs @deprecated or something, so that we remember to actually remove it
Please pick one format for the d.o links
Comment #33
phenaproximaExpanded the test coverage and (hopefully) added serialization support.
Comment #34
tim.plunkettThis change feels wrong to me. Because now every call to getSampleValues would need this...
What's the reason we can't use the old way? Or
yield from
if that's needed?Comment #35
phenaproximaWe cannot use
yield from
because it's PHP 7 only. I tried to do things the old way, with a test, but I simply could not get it to delegate to the inner generator. It would always return a generator-within-a-generator, which means the calling code would need to have a nestedforeach
loop to iterate over it. There's a library out there on GitHub that polyfills generator delegation in PHP 5, but it'd be another core Composer dependency, so that's a no-go.Thus, after a solid hour of trying, I settled on this being the best way to go forward. If you have other ideas, though, I'm happy to try them.
getSampleValues() is protected, and there's only one call to it in ContextDefinition. The BC layer is a private member, so anything that is subclassing ContextDefinition can't interact with it anyhow. So I'm not sure this is something we need to be concerned about.
Comment #37
phenaproximaRemoved as many calls to
new ContextDefinition('entity:
from core as I could find.Comment #40
phenaproximaForgot to fix the ContextDefinition annotation class.
Comment #42
phenaproximaOkay, let's see how this does.
Comment #43
tim.plunkettI think the implementation here is great.
I have a couple questions/suggestions for DX (since that's the whole point of the issue).
Shouldn't this change to
EntityContext::fromEntityTypeId('node')
?EDIT: we don't have that. But that seems useful, no?
I would think this could be
new EntityContextDefinition($entity_type_id, ...)
without requiring the
entity:
prefix.This as well, ideally you could do
EntityContext::fromEntityTypeId('node', $this->t('Node from URL'));
Overall my point is that we should look at the usages of this and be sure we really nailed the new class to provide good DX for the majority of use cases.
Comment #44
phenaproximaOkay, let's see how this does.
In this patch...
Comment #45
tim.plunkettstrpos($data_type, 'entity:') !== 0
and skip out on the slower regex
👍
This is a confusing difference, IMO.
Wait, is ::create() even a method?
Comment #46
phenaproximaAddressed the feedback above.
@tim.plunkett and I are in agreement that one of the biggest problems with Drupal's API is that there is always about eleventy kazillion ways to do things, and this is reflected in all the tests which are creating entity context definitions -- some call the static factory methods, some use the constructor, at least one uses ::create() (which is inherited from ContextDefinition), and it's all confusing as hell. Therefore, this patch normalizes everything to use the new EntityContextDefinition::fromEntityTypeId() method. It's obviously still possible to use create() or constructors (which is good for hacking, not for DX), but now we're endorsing one particular way to do it. And that is good for DX.
Comment #48
phenaproximaThis should fix the test failures. In the kernel tests, I just enabled the modules which provide the actual entity types under test -- I forget where I heard the testing mantra that "core should work with itself", but it seems to me that testing with the real entity types where possible is the best way to ensure that. :)
Comment #49
phenaproximaTagging as part of the Layout Initiative.
Comment #50
phenaproximaChange record updated.
Comment #51
tim.plunkettThanks for those changes.
This is really close!
I think this deserves some code comments.
Also could switch to the ::class versions
The @deprecated needs to be expanded
This could use a comment about the 7 corresponding to the length of 'entity:'
Thought we lost something here, but that
, TRUE
matches the default value so I think it's fine.Just calling out in case someone else notices.
Comment #52
phenaproximaFixed all feedback in #51, except:
We can do that for EntityContextDefinition, but not ContextDefinition, since the class which contains this logic is called ContextDefinition. So rather than make it inconsistent, I left them as strings.
Comment #53
tim.plunkettCould have aliased the import, but not really worth it in the long-run.
I think this is good to go!
Comment #54
alexpott:( This is kinda sad. Can we get an issue to fix \Drupal\Core\Validation\DrupalTranslator::trans()? That method is sad because it does
$id instanceof TranslatableMarkup
andt()
on the same line.I've opened #2979944: Remove t() usage in Drupal\Core\Validation to address this. It'd be nice to land that first.
Comment #55
tim.plunkettThis is being copied from
\Drupal\Tests\Core\Plugin\Context\ContextDefinitionIsSatisfiedTest
.It'd be a shame to hold up this on something that unrelated.
Also in
And the !function_exists pattern in tests is in 17 other tests as well.
Comment #56
tim.plunkettRerolled without function_exists.
Comment #57
larowlanif we moved this to a protected method ::getDefinitionClass we could do away with the if/elseif/else by way of early returns. I think that would make it a bit easier to parse.
Can we assume that? What about the 'current user' context?
Comment #58
phenaproximaAddressed both points. I'm not 100% pleased with the standard label of the entity context definition, but I can't think of a better alternative.
Comment #59
phenaproximaI have no idea why interdiff spat out such a big file -- I guess my git is not set up the same way as @tim.plunkett's. Anyway, here's a way-too-big interdiff in which only the first couple of kilobytes are relevant. ¯\_(ツ)_/¯
Comment #60
tim.plunkettDeja vu, but this can be an if. (see #57.1)
This is now a UX regression for Layout Builder, as we lose the " being viewed" portion that was all that allowed users to differentiate between other contexts of the same entity type.
Also, here's an interdiff for the above. No idea how you got it to be 28k
Comment #61
phenaproximaBah! Both fixed.
Comment #62
tim.plunkettComment #64
tim.plunkettBS selenium failure.
Comment #65
catchThis looks like really good clean-up.
I winced slightly at the substr() calls around entity:ENTITY_TYPE but cannot think of a way around those and not really a new issue.
Committed 099f414 and pushed to 8.6.x. Thanks!
Comment #67
mtodor CreditAttribution: mtodor at Thunder commentedI have noticed that with this commit, I'm getting multiple field blocks in the list, more or less for all content types.
I'm not sure is that ok or not?
Here is screenshot for layouting of Layout Page - custom layout for single content entity:
Comment #68
tim.plunkettOpened #2986033: [regression] The BC layer for EntityContextDefinition in ContextDefinition is incomplete
Comment #70
jonathan1055 CreditAttribution: jonathan1055 as a volunteer commentedRegarding the commit in #66 this has broken Rules daily testing at 8.6 since 14 July.
#2989417: Argument 1 passed to \EntityContext::fromEntity() must implement EntityInterface
[edit] I found the change record, thanks!
Comment #71
Wim LeersJust published the change record. 🙈
Comment #72
tim.plunkett#3126747: ContextDefinition::create() can no longer be used with an entitytype-specific datatype (like entity:user) was opened recently. Bringing this to the attention of those following this issue.