Problem/Motivation
EntityRepository::loadEntityByUuid() is documented with the following documentation comment.
/**
* Loads an entity by UUID.
*
* Note that some entity types may not support UUIDs.
*
* @param string $entity_type_id
* The entity type ID to load from.
* @param string $uuid
* The UUID of the entity to load.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The entity object, or NULL if there is no entity with the given UUID.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown in case the requested entity type does not support UUIDs.
*/
EntityRepository::loadEntityByUuid() doesn't check for invalid nor unexisting UUIDs.
Proposed resolution
Let's return NULL, in case we have a wrong UUID.
Remaining tasks
User interface changes
API changes
Beta phase evaluation
| Issue category | Bug because the behavior does not match the interface documentation. |
|---|---|
| Issue priority | Normal. |
| Prioritized changes | The goal of this issue is to clarify the entity API, and to expand testing to make sure implementations meet that expectation. |
| Disruption | No disruption. |
| Comment | File | Size | Author |
|---|---|---|---|
| #104 | 2403271-104.patch | 15.25 KB | snehalgaikwad |
| #100 | 2403271-100.patch | 15.24 KB | manav |
| #98 | 2403271-98.patch | 15.13 KB | nikitagupta |
Comments
Comment #1
dawehner.
Comment #2
damiankloip commentedNice, sounds sensible.
Comment #3
berdirwhy FALSE and not NULL? Or should it throw an exception? Node::load(NULL) btw also gives you errors.
Comment #4
rpayanmComment #5
dawehnerIt should mimic the behaviour of NodeStorageController(non-existing-ID) afaik
Comment #6
berdirOh, I see, loadEntityByUuid() is documented as return FALSE. That's inconsistent with load() in the first place :)
Also, it is consistent with load($non_existing_id), that also returns NULL.
The only problem is when you pass in NULL. That results in errors, at least with #2358079: Require a specific placeholder format in db_query() in order to trigger argument expansion, and require explicit 'IN' parameter for conditions. Which is also consistent with Node::load(NULL), that also gives you errors :p
Comment #7
dawehnerAh right, I'm totally fine with NULL.
It would be great to not error in the case of NULL though. This might happen here and there, won't it?
Comment #8
rpayanmPlease review.
Comment #9
pwolanin commentedShould check if the UUID is empty before even trying to load it.
Comment #10
dawehnerWell, we should use
Uuid::isValid()Comment #11
zealfire commentedPlease review.Thanks.
Comment #12
dawehnerNow that we return NULL we should update the documentation as well. Note: you need some more spaces to get the right codestyle done :)
Comment #14
berdirshould be "if (!Uuid::" instead.
Also, a comment would be useful, something like "Immediately return NULL for empty or invalid UUID's".
Also, right now, the method is documented as return FALSE. We either need to change that to return NULL (and update the reset() to return NULL if there are no entities, as that returns FALSE) or return FALSE as well...
And there is also the option to throw an exception instead. Because calling it like this is a bug and often hints at a bigger problem in the calling code IMHO. So far, I've only seen it when doing a load(NULL), then you get the famous message about array_flip() only working for scalars. And that is usually incorrect code in the caller.
Not sure what, thoughts?
Whatever we decide, I think we should update load/loadMultiple() in a follow-up to behave the same way..
The same test fails were in #2358079: Require a specific placeholder format in db_query() in order to trigger argument expansion, and require explicit 'IN' parameter for conditions, see the changes to EntityReferenceItemNormalizer. Not sure if we should let it fail like that or check for it not being a string instead (Which would have hidden this clearly wrong code, @see exception argument :))
Comment #15
zealfire commented@Berdir,so do you think will it be a nice idea to make patch for this issue by returning NULL and commenting it.Then later as a follow up task make another issue where documentation related stuff can be deal with.
thanks
Comment #16
zealfire commentedI have used both empty() and is_string() as discussed with dawehner and pwolanin on irc earlier.Also i have made some changes in the document.Not sure if i am doing this in correct manner so give your opinions.Please review.
Thanks.
Comment #18
neelam.chaudhary commentedReturning NULL if UUID is either empty or invalid. And Adding a comment for it as discussed above.
Please review.
Comment #20
prateekMehta commentedChecking if the uuid is not invalid or empty, and returning null as mentioned in the /core/lib/Drupal/Core/Entity/EntityManagerInterface.php
Comment #22
prateekMehta commentedwrong patch in previous comment. reposting.
Comment #23
prateekMehta commentedComment #24
prateekMehta commentedComment #26
prateekMehta commentedComment #27
berdirif (empty($entities) is a bit easier.
Some unit tests would be nice here, looks like we actually don't have any in EntityManagerTest. If you know a bit about unit tests and mocking, then it shouldn't be too hard. Just call that method with NULL or an invalid UUID and make sure that the stuff below is never called and it returns NULL.
Comment #28
anksy commentedHere are the tests for the patch. This is my first attempt at writing tests for drupal so please give feedback on the tests.
Comment #30
prateekMehta commentedAdded the tests to my original patch from #26. Also used
empty()instead of!reset().Adding new patch and its interdiff with the previous two patches as the patch in #28 just had the tests and not the actual changes.
Comment #31
prateekMehta commentedComment #33
anksy commented@prateekMehta Now I know why my patch failed testing. I had forgotten to add the actual changes. Thanks for correcting it.
Comment #34
prateekMehta commented@anksy, actually the way you were using
getMockwas also a little more complicated. I tried to use your test on the original changes it, didn't work out. check interdiff-28-30.txt , you will know.Comment #35
anksy commented@prateekMehta Thanks for pointing out the error. I tested the patch and it works fine.
Comment #37
rpayanmRerolled.
Comment #39
rpayanmOff course.
Comment #40
yesct commentedI do not think the concerns from @Berdir in #14 have been addressed.
especially
1.
2.
This follow-up should be created and made "related" to this issue.
Comment #41
yesct commentedI think the provider tests these two cases.
but I dont see how the empty entities case is tested.
is it?
Comment #42
disasm commentedComment #43
disasm commentedComment #44
erik.erskine commented#39 looks good to me. It catches instances in tests where
loadEntityByUuidis being called with malformed UUIDs. These currently rely on those bogus UUIDs not actually existing - see #2491989: [PP-1] Use the 'uuid' database schema type (with native PostgreSQL implementation) for UUID fields.Rerolled, along with a small typo fix.
Comment #45
erik.erskine commentedWith regards to the comments in #14 and #41:
EntityStorageInterface::loadmethod? If so that does returnNULL.EntityUUIDTestto cover testing for NULL with a non-existent, but valid, UUID.Comment #46
sorressean commentedI did not actually realize that this was done (first patch review), so I wrote the same thing before finding the last patch--looks good!
Comment #47
sorressean commentedComment #48
alexpottThis looks super defensive to me. Are we sure we want to do this? Perhaps when we have assertions in core we can use that instead.
This is what happens atm - the exception produced looks fine to me. If you provide an invalid uuid it works as you expect.
@Berdir what do you think?
Comment #49
bzrudi71 commented@berdir any feedback please? This blocks #2491989: [PP-1] Use the 'uuid' database schema type (with native PostgreSQL implementation) for UUID fields ;)
Comment #50
mile23Added beta evaluation.
Patch still applies.
Comment #51
mile23Seems to me that if we're throwing an
EntityStorageExceptionsomewhere other than anEntityStorgeInterfaceclass, we might be doing it wrong.So let's move the property-exists check to
EntityStorageInterface/Basewhere all code can benefit, and then just handle the empty-or-bad-uuid check withinEntityManager.In this PoC we rely on the query and database layers to throw exceptions validating the UUID and checking whether it's NULL. That means there isn't any validation of whether the given UUID is a valid one. It seems that should be baked in somewhere, but I'm not quite sure where.
This patch does not have tests. It passes a PHPUnit test run. Let's see what the testbot thinks.
Discuss. :-)
Comment #52
mile23Comment #54
mile23OK, yah, never mind. I forgot that we're not allowed to encapsulate things that should be encapsulated in one place. Our entity abstraction only lets us map a few special-case keys, and apparently can't just give us a list of properties that are allowable.
As an object lesson, note that a) All unit tests pass even though none of the functional ones do, and b) Nowhere in the process of writing that patch did the docs warn me I was making an error. I might be a fool, but Drupal is still an oral tradition.
Moving on...
Comment #55
mile23Interdiff from #45. Let's all ignore #51. :-)
Removed the UUID validity checks, though there should be some way to perform that check within the query, either at
loadByProperties()or some other point, since there's no point sending it off to the database to fail.Moved
loadByProperties()into atry/catchwhich then returns NULL for the NULL-UUID exception. The query builder'sConditionclass is the *only* placeInvalidQueryExceptionis thrown, specifically for empty conditions in queries. It's kind of strange, actually.Added a bunch of unit tests.
Comment #56
mile23Comment #57
daffie commentedLooks good to me. Just three observations:
Nitpick: Can we rename the dataprovider to providerLoadEntityByUuid.
Why no dataprovider for this test. You have one for the other tests (return ['apple', NULL];).
Can we add a comment that there is no assertion because we are expection an exception.
Comment #58
mile23Thanks.
#57.1: Done.
#57.2: Like it says in the inline comments, we throw an exception in all cases, so there's no point in having a data provider. I'll expand the docs.
#57.3: The
@expectedExceptionannotation *is* the comment and also the assertion. But I'll expand on it.Still need a follow-up, which might already exist, to do some kind of validation of UUIDs, or general validation before we hit the database.
Comment #59
mile23Some related issues.
Comment #60
daffie commentedNow it looks good to me.
Thanks for fixing my issues Mile23.
Comment #61
damiankloip commentedWon't you only get this exception in the condition value is empty and it's an array? Can't we just make sure the uuid is a string before hand?
Won't reset($entities) do the same here anyway?
Comment #62
damiankloip commentedComment #63
mile23#61.2:
reset()on an empty array returns FALSE. We want NULL.I misread #48.
Let's try this again...
In Drupal, UUIDs are actually arbitrary strings, and some discussion is underway as to whether we should validate UUIDs: #1805576: Add a 'uuid' database schema type (Personally, I say we should add a 'uuid' column type to the Drupal schema API and add validation constraints.)
This means it's up in the air as to whether we should validate UUIDs in
loadByUuid(). Let's do it, with a note that we may change policy later.In the case that we validate UUIDs in
loadByUuid(), should we throw an exception or just return NULL? Let's throwInvalidQueryException, as per #48.We still throw
EntityStorageExceptionif the entity type doesn't support a UUID key, and we don't catch any other exceptions, so that things explode usefully.In this patch,
EntityUUIDTestdoes a functional check of the behavior for passing in a non-existent UUID to an entity type that supports UUIDs.EntityManagerTestverifies the following:EntityStorageExceptionif the entity type does not support UUID keys.InvalidQueryExceptionfor malformed UUIDs.Patch passes PHPUnit run and Entity namespace tests. Let's see what the testbot thinks.
Comment #65
damiankloip commentedI like the new version of loadEntityByUuid much better, thanks @Mile23!
Comment #66
mile23Yah but too bad all the tests are built on non-conforming test UUIDs.
I just searched entity_test module for 'uuid' and it doesn't tell me where the UUIDs are generated or defined.
Drupal.
Comment #67
mile23So it turns out the tests tell me that ConfigManager needed some love, as well as adding try/catch to loadEntityByConfigTarget().
Note that I had to break some tests, because I don't have the time to fix views_ui or editor modules. Added @todo to those. These should either be fixed here or in follow-ups.
Comment #68
mile23Comment #69
mile23And now fixing the modules.
Summary in #63.
Comment #70
mile23Needed reroll.
Comment #71
mile23Another re-roll.
Comment #79
avpadernoComment #80
mile23Ooo. Ancient patch from before EntityManager was split into 11 classes. https://www.drupal.org/node/2549139
Comment #81
avpadernoI guess that now it's
EntityRepository::loadEntityByUuid().Comment #82
vsujeetkumar commentedRe-roll patch created, Please review.
Comment #83
avpadernoComment #84
avpaderno(Wrong comment: Please ignore it.)
Comment #86
avpadernoActually,
print_r()is used for when$uuiddoesn't contain a string, but I don't see how an exception message like Invalid UUID: (output when$uuidcontainsFALSEorNULL) or Invalid UUID: Array() (output when$uuidcontains an empty array) could be helpful. The message doesn't allow to notice the UUID is invalid because it's an empty string,FALSE, 1, orTRUE. (print_r(TRUE, TRUE)outputs 1, in the same wayprint_r(1, TRUE)does.)It would be probably more helpful for the exception message to say Invalid UUID passed to
EntityRepository::loadEntityByUuid().Comment #87
avpadernoAlternatively, the code throwing the exception could be the following one.
In this way, the exception message would make clear what exactly the value passed as argument is, as
var_dump(TRUE),var_dump(1),var_dump(FALSE),var_dump(""), andvar_dump(NULL)output different values.Comment #88
neelam_wadhwani commentedComment #89
neelam_wadhwani commentedHello @kiamlaluno,
Updated the message.
Kindly review patch.
Comment #91
avpadernoThe issue summary still needs to be updated, as it speaks of returning
NULLin case of bad UUID, while the patch is raising an exception. (I just changed the name of the method to alter, which changed because of splitting an entity class in multiple classes.)See also comment #48.
Comment #92
avpadernoThe test code seems wrong.
getKey('uuid')should return$uuid, not a literal string.loadByProperties()needs to return entities, not strings.Comment #93
jungleCoding standards must pass as well, https://www.drupal.org/pift-ci-job/1611745
Comment #94
prabha1997 commentedComment #95
prabha1997 commentedHere i upload new patch with no drupal coding standards issues. Kindly review patch
Comment #98
nikitagupta commentedReroll the patch #95.
Comment #100
manav commentedI have applied the last submitted patch #98 but it failed.
So I have tested the code and found that the culprit is DeprecatedServicePropertyTrait in EditorFileReference.php file
Comment #101
manav commentedComment #102
manav commentedComment #103
andypostNeeds reroll and it blocking #2491989: [PP-1] Use the 'uuid' database schema type (with native PostgreSQL implementation) for UUID fields
Comment #104
snehalgaikwad commentedRe-rolling the patch for 9.1.x.
Comment #106
meena.bisht commentedComment #107
meena.bisht commentedComment #108
anmolgoyal74 commentedRemoved dump statement.
Working on the other issues.
Comment #110
anmolgoyal74 commentedComment #112
meena.bisht commentedComment #113
avpadernoComment #114
avpaderno