Problem/Motivation
Per #2671964: ContextHandler cannot validate constraints the ContextHandler cannot properly evaluate plugin constraints as it currently stands. That issue attempts to rectify this problem but cannot for various architectural concerns of those involved. In order to compensate for this, I opted to build an entity generating utility that could be universally useful.
A useful entity generation utility could:
- Help solve this issue with the ContextHandler validating plugin constraints
- Simplify contrib utilities like Devel Generate
- Potentially do cool stuff like generate previews of an entity w/o the need to actually create and save an entity. (just spit-balling here)
Proposed resolution
Write an entity generation utility that accommodates all fieldable content entities and at least opens the door for config entities to be generated as well with additional developer input.
Remaining tasks
Review and iterate.
User interface changes
N/A
API changes
We introduce a new entity generator service and wrote test coverage. This test coverage revealed that the User entity (specifically) had custom value constraints applied to username and timezone which were not being respected by the field's generateSampleItems() methods. In order to solve this, I added or overrode classes specific to those properties and included a custom generateSampleValue() method on them. Once I did this, tests still did not pass at which point I noticed that \Drupal\Core\Field\FieldItemList::generateSampleItems() didn't retrieve the class set by base field definitions, rather it delegated directly to the "type" class (a plugin class specific to the type, so never more specific than say "string"). I updated this to get the field definition's item definition class, which implements the same interface, but could have been overridden by the entity's base field definitions directly. This gave my custom classes the ability to generate reliable sample values that would pass validation.
Data model changes
N/A
Comment | File | Size | Author |
---|---|---|---|
#37 | 2905527-37.interdiff.txt | 619 bytes | EclipseGc |
#37 | 2905527-37.patch | 11.3 KB | EclipseGc |
#35 | 2905527-35.interdiff.txt | 3.42 KB | EclipseGc |
#35 | 2905527-35.patch | 11.29 KB | EclipseGc |
#32 | 2905527-32.interdiff.txt | 2.58 KB | EclipseGc |
Comments
Comment #2
EclipseGc CreditAttribution: EclipseGc commentedpatch!
As mentioned in the IS, there are some minor changes to the User entity in order to ensure it can reliably generate a user entity. These changes didn't make much sense in isolation because the test I'd have had to write would have been pretty similar to the actual service we introduced and tested in this patch.
Eclipse
Comment #4
EclipseGc CreditAttribution: EclipseGc commentedLet's see if this makes testbot happier.
Eclipse
Comment #5
EclipseGc CreditAttribution: EclipseGc commentedTagging this as Blocks-Layouts since it's a blocker in a long chain of dependencies for doing administrative UI for building defaults.
Comment #6
EclipseGc CreditAttribution: EclipseGc commentedComment #7
tim.plunkettdouble blank lines
Since this is a service, please provide an interface
"Constructs a new EntityGenerator."
I don't think we're using lowerCamelCase in core like this yet
Generates
s/id/ID
This should be a classed exception. And don't end exception methods with a .
a) Usually we call this local variable
$entity_type
.b) Use something like
$entity_type->entityClassImplements(FieldableEntityInterface::class)
(or ConfigEntityInterface), because the current checks do not guarantee that.There is no $entity in this method. Also, not fieldable
This change is not immediately clear to me. Why does this help?
TimeZoneItem::class
Can we get a comment explaining this?
Any reason to hardcode 60? Is there a setting on the field definition that would be better to check? A comment would help.
@coversDefaultClass would be nice here, and @covers on the individual methods below.
{@inheritdoc}
Super nit, but you can chain the ->save
This tests generateFieldableEntity, needs coverage for the config one.
And also needs coverage for the thrown Exceptions.
A coverage report of this would be telling. I don't see any GeneratorHandlers being used, even test ones.
So far this seems more like a Unit test than a Kernel test?
Comment #8
EclipseGc CreditAttribution: EclipseGc commentedOk, I worked on this a bit today to fix the issues Tim pointed out. Overall I think the changes clean it up nicely, but in order to test the configuration entity generation, I went ahead and created a NodeType generator just to have one and to facilitate testing. People can easily copy what I did here and take it further if need be. This will at least give some variety for testing purposes.
Eclipse
Comment #9
EclipseGc CreditAttribution: EclipseGc commentedCleaned up the stuff I added a bit to make it better and more core-worthy.
Eclipse
Comment #10
EclipseGc CreditAttribution: EclipseGc commentedCleaned up whitespace issues.
Eclipse
Comment #11
tim.plunkettNit: should be $entity_type
s/id/ID
This should also be $entity_type not $definition, and should also use entityClassImplements as the method above does.
I'm not sure I've ever seen it done this way, most of the time I see the sprintf call where the exception is thrown.
Even in the case of \Drupal\Component\Plugin\Exception\PluginNotFoundException, the sprintf is for the fallback.
Just struck me as odd.
The oneline doc should start with (optional)
In this case I would mimic \Drupal\Component\Plugin\Exception\PluginNotFoundException, and allow an optional $message and use this sprintf as a fallback
(optional)
I still think this change deserves dedicated test coverage, that doesn't use the EG
The interdiffs look great!
Comment #12
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedFrom what I see in the patch, this is basically a "Entity::createWithSampleValues()", right? In which case, can't we simply call it that (or something similar) and put it in the storage handler?
Comment #13
EclipseGc CreditAttribution: EclipseGc commented1-3: OK
4: Was copying stuff I saw here: \Drupal\Core\Entity\Exception\AmbiguousEntityClassException
5-7: OK
8: I chose to place this test into UserEntityTest since we are in fact testing that the user entity will properly validate. I've included a test-only version here which I expect to fail.
Amateescu:
So my reasoning on this is that while yes functionally we're generating an entity with sample values, I chose not to do this on storage handlers because they are already an established thing, and more over (and more importantly) not all entities need a custom generator class. Currently only config entities need that, and ideally we'd be able to describe our entities and their requirements well enough that we can delegate generation to a centralized generator (like I've done for content entities in this patch).
Eclipse
Comment #15
EclipseGc CreditAttribution: EclipseGc commentedPosted patches in the wrong order heh.
Eclipse
Comment #16
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedCan't we use config schema to derive sample values for config entities on a best-effort basis? I guess what raises an alarm bell for me is the naming: generator is very broad and.. strong (can't really describe it better, sorry :)), so how about SampleValuesEntityGenerator or something like that?
What do you mean by "a preview of an entity"?
Also, I'm wondering how does this help solve the issue linked in the issue summary?
Comment #17
EclipseGc CreditAttribution: EclipseGc commentedHad a long conversation with amateescu in slack. We hashed out some of the details and I've removed a lot of the code that existed in this patch in order to really trim it down. We might want to move tests around a bit, but config generation is gone as a concept, content entity generation moved to the storage container and corresponding interface and base class were updated as necessary.
Eclipse
Comment #18
jibranThis looks much better now.
Comment #20
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedLooks much better indeed!
I'm not sure whether the
$bundle
parameter should exist or not, so let's hear more opinions on this one :)You have to take into account that an entity type might not provide a bundle key, in which case the value for the bundle is the ID of the entity type.
There is no fallback anymore ;)
Creates an entity...
This is not thrown anymore.
This change is not needed anymore.
These strings should be updated to not mention the generator approach anymore.
Comment #21
tim.plunkettThis looks much cleaner!
I don't see this thrown anymore, figure it's leftover?
Leftover change. Trailing commas are okay in annotations.
Pretty incredible that this wasn't needed already!
entityClassImplements() please
EDIT: Crosspost! Lots of similar feedback
Comment #22
EclipseGc CreditAttribution: EclipseGc commentedTook care of all the feedback that was obvious and actionable.
20.2:
So you can clearly see what I chose to do for this in the new interdiff. I also added back an exception here for requesting a bad bundle. I wanted to keep as much of this code in the method as possible because error checking this stuff requires the caller to understand the entity system pretty in-depth and I'd like the method to be as concise and easy to use as possible. To that end...
20.1:
I REALLY think we want this parameter because 20.2 becomes harder to do properly w/o separate bundle params plus the caller of this method would have to move a bit of the code that figures out the proper bundle key name out of this method and repeat it everywhere this method is called. To that end, I really think we should keep it as is.
Eclipse
Comment #23
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedYou also need to take into account that an entity type might not have bundles (e.g. User), so the first method argument should be optional and
$bundle_key
needs to be added to the forbidden keys only if it exists.Which makes me wonder, why do we forbid passing an entity ID and revision ID in the $values array?
Entity types with string IDs will not have an ID generated automatically by the DB layer, so we need to allow passing an initial ID for them.
This is too complicated and not really correct because not all bundles are managed through a config entity type. You should use
getBundleInfo()
method from theentity_type.bundle.info
service, and throw a regularEntityStorageException
like\Drupal\Core\Entity\ContentEntityStorageBase::doCreate()
.As mentioned above, this needs to be a
EntityStorageException
.We can use
assertCount()
here.This shows the problem with having $bundle as an explicit argument, it forces the caller to know that an entity type doesn't use bundles and guess that it can use the entity type ID for the bundle name.
Also, the caller can also be wrong in assuming that an entity doesn't have bundles, since the entity type definition can be altered by any other module to add bundle support for that entity type.
Comment #24
EclipseGc CreditAttribution: EclipseGc commented23.1
We're not forbidding passing, just forbidding generation. There are a host of reasons that this makes sense, but the most obvious is if someone were to call save on these objects, we'd want their ids to be generated appropriately, and for non-serial id entities, an id can absolutely be passed in the $values array.
Eclipse
Comment #26
EclipseGc CreditAttribution: EclipseGc commentedOk, fixed the tests and code standards issues.
Eclipse
Comment #27
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThese changes will force every overridden content storage handler to require an update, including contrib and custom ones, so I think we're better off using
getBundleInfo()
from the deprecated entity manager service, since we already have that injected.This method still uses the old "generate" naming.
And.. last but not least, we need to change the issue title :)
Comment #28
EclipseGc CreditAttribution: EclipseGc commentedWish I'd known about that method on the entity manager previously. That would have saved some time and test coverage :-D
Eclipse
Comment #29
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI think it looks perfect now! Except for this small nit:
This leftover from the previous patch should be removed.
Comment #30
EclipseGc CreditAttribution: EclipseGc commentedfixed
Comment #31
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedReally sorry for yet another nitpick review, I found two more things on a second pass :/
This is not the exception that's being thrown anymore.
This is a weird namespace for this test, why is it in the Element subdirectory?
Comment #32
EclipseGc CreditAttribution: EclipseGc commentedOk, fixed a couple additional things I found from what you pointed out.
Eclipse
Comment #33
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedPerfect :)
Comment #34
larowlanUpdating issue credits to include @tim.plunkett and @amateescu for their reviews.
We need a change record here.
nit: these are strings, we should use the third argument
If this yields the maximum, we'll get an error here?
e.g. if keys are [0, 1, 2]
count()
is 3.$timezones[3]
does not exist.php.net/rand tells me that
rand
is inclusive of the maximumWe don't have any coverage for passing the second argument - $values
Comment #35
EclipseGc CreditAttribution: EclipseGc commentedOk :-D
Eclipse
Comment #36
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe nitpick police strikes again:
Missing spaces before and after '-' here :D
And we still need the change record requested in #34.
Comment #37
EclipseGc CreditAttribution: EclipseGc commentedChange record: [#2909464]
Nit's addressed :-D
Eclipse
Comment #38
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedCool, let's ship it :)
Comment #39
larowlanupdating issue credits, adding myself for catching the off by one error
Comment #40
larowlanCommitted as dff09a9 and pushed to 8.5.x.
Published the change record.
Tagging for 8.5.x release notes.
Comment #42
EclipseGc CreditAttribution: EclipseGc commentedAWESOME!
Comment #43
Wim LeersGreat to have this capability!
Devil's advocate: why is this not a BC break?
Comment #44
larowlanTwo months ago, I said the same thing, but then I was pointed to this bit in our BC policy.
Comment #46
xjmI don't think there's anything here that site owners must know when upgrading, so switching to the highlights tag.