Closed (fixed)
Project:
JSON:API
Version:
8.x-2.x-dev
Component:
Code
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
9 Jan 2019 at 23:28 UTC
Updated:
19 May 2019 at 11:32 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
gabesulliceComment #3
wim leersComment #4
wim leers#3022584: Consolidate and simplify NormalizerValue objects: introduce CacheableNormalization landed!
Comment #5
gabesulliceIgnore patch: see #5 patch below.
Okay, first, we can move all of the
Relationship::normalizecode toEntityReferenceFieldNormalizer.This is as close to just copy/pasting as I can get. We'll factor it down into more readable code later.
Comment #6
gabesulliceWhoops, looks like a revealed my bag of tricks too soon! #5 was the supposed to be the last patch in a series, not the first! Here is the actual first step.
Edit: Aargh, and now I messed up the interdiffs! I was trying to make a cohesive logical progression using my git history, but I tried to get fancy and automate it in the shell. Got it wrong, and now I'm embarrassing myself lol.
Now that we've moved all that normalization logic, the
Relationshipclass is just indirection inside the entity reference field normalizer. We can factor it out pretty easily by moving the arguments to its constructor straight into arguments toEntityReferenceFieldNormalizer::normalizeRelationship.Comment #7
gabesulliceCorrect interdiff for #6.
Now that EntityReferenceFieldNormalizer isn't using the Relationship class, it isn't actually used anywhere except for its FQDN. We can prove this by deleting everything and only leaving this:
Comment #8
gabesulliceHey! Now Relationship is down to nothing, let's just make
RelationshipNormalizerhonest about itself!That's it for today. Tomorrow, I'll try to factor down the copied over code in
EntityReferenceFieldNormalizer. I'd like to then removeRelationshipItem.RelationshipItemis used by JSON API Extras though, but it looks like it'll just be a one or two line change, it's just uses the class to get the field name.Comment #9
wim leers#5:
😂
#8: JSON:API Extras only started using
RelationshipItem(Normalizer)since the last week, since #3025284: Allow field enhancers for relationships.Curious to see where this ends up! Not doing a deep review yet because that seems premature. My key critical remark right now would be:
\Drupal\jsonapi\Normalizer\RelationshipNormalizeris still there for its::denormalize(), that's confusing. I suspect it'll be renamed toResourceIdentifierDenormalizer. Looking forward to finding out :)Comment #10
e0ipsoThis will require another coordinated update of JSON:API Extras and more release babysitting :-(
Comment #11
wim leersIf this ends up happening, @gabesullice and I will provide a patch for JSON:API Extras. Just like we're doing at #3025037: Make JSON:API Extras compatible with the upcoming 2.1 release.
Comment #12
gabesulliceLet's fix the test failures. That will prove that this isn't breaking anything. Then I'll proceed again with what I said I'd do in #8. That's what will most likely to cause regressions. Re: #9 and
RelationshipNormalizer, I'm not sure whether that normalizer will be deleted or not. We'll see.Comment #13
gabesulliceSimple fix for the
RoutesTestunit test. Nothing was materially broken.Comment #14
gabesulliceOkay, now back to the meaty bits.
Let's factor away all the cruft related to the
Relationshipclass now inEntityNormalizer, that should get rid of even more LoC and set us up to deal withRelationshipItem, there's lots of logic related toNULLand virtual entity references in there. That's won't be terribly tricky, but it will makeEntityReferenceFieldNormalizerbigger. I'm thinking we can make that easier to swallow by making it use theResourceIdentifierobject. Namely, to reuse its arity logic, that'll mean we can delete deduplicate that logic and remove it fromEntityReferenceFieldNormalizer.I'm fairly confident this interdiff also means we can make
EntityCollectionstop acceptingNULLandFALSEas values!Comment #15
gabesulliceNice, okay, let's see if making
EntityCollectionstricter blows up.Comment #16
gabesulliceRemoves
RelationshipItem. I'm sorry, this is not going to be very pleasing to review :\ it's the code had to be remixed a bit when moving it intoResourceIdentifier.Comment #18
gabesulliceI don't know if this will address the last failure, but this fixes two things:
ensureUniqueResourceIdentifiersComment #20
wim leers#14:
👍 I sure like removing this! That's one of the trickier bits of code.
👍 Ah, this is why you say we can make
EntityCollectionstricter now: because we stop using it as a vessel for passing data in one particular internal-only use case. Great!#15:
👍 Huge simplification!
And once #3015438: Wrap entity objects in a ResourceObject which carries a ResourceType lands, this will be down from 3 possible classes to only 2!
#16:
Great, an explicit regression test ensures we don't reintroduce the same regression 🤘
🐛 AFAICT this is wrong, and it's most likely the root cause for the remaining failure. #2984647: Dangling entity references in entity reference field with multiple possible target bundles: results in exception/500 response introduced passing
FALSEintoEntityCollectionto model a missing entity. But the target ID is notFALSE! It's a valid target ID. It just cannot be resolved to anything.The overall condition for
NULLorFALSEto be passed intoEntityCollectionin HEAD isif ($item->get('entity')->getValue() === NULL) {. And then we inspecttarget_id: we special case a target ID of0for thetaxonomy_termtarget type: this getsNULL. Everything else getsFALSE.🤔 Rather than introducing an optional parameter, wouldn't
toResourceIdentifiersWithoutArity()that decoratestoResourceIdentifiers()be clearer?In any case, I find the changes in this method very difficult to grok.
Nit: double space after
protected.👍 All of this logic is moved into
ResourceIdentifier::getVirtualOrMissingResourceIdentifier(). Interesting. Seems like a good plan overall.Comment #21
gabesulliceHm, I'm not sure it would be clearer because it's not "without arity". It's actually without arity if no duplicate relationship exists.
What was happening in the method is that even when there were no other resource identifiers with the same type and ID combo, it'd still output an identifier with
arity: 0. This is not what a public-facing serialization is supposed to do. However, this class was not previously used during normalization, only denormalization, when we needed to compute differences/intersections with an existing set of resource identifiers (for relationshipPOST/PATCH/DELETE). Having every identifier include an arity was simpler to code and useful that operation, but it was not consistent with how arity is supposed to be serialized.What the current change does is only include arity if the resource identifier is duplicated when
$omit_single_arityisTRUE.After writing this reasoning out, I think it makes sense to keep the optional parameter, but invert it so that it only needs to be specified for the internal use case. WDYT?
I'm happy to walk you through that method to help you "see" how it works. Once it "clicks" for you, it'll be simple to understand the change.
Comment #22
gabesulliceI still have more to address from #20, but @Wim Leers and I discussed #20.2 and #21 in a video chat where we realized agreed to invert the optional parameter. After I made that change, it was clear that much of the complexity of dealing with arity when making comparisons could be completely internalized. Therefore, I marked a method
protectedand exposed some other helpers so that any code using theResourceIdentifierclass can have less knowledge of its internal details.Because of the complexity of dealing with arity and the complicated looping when doing so, I added some very verbose commentary for myself and others in the future.
Comment #23
gabesulliceFixing typos. Next patch will dig into #16.1.
Comment #24
gabesullice#16.1 was right about the problem and it helped me a lot with seeing what went wrong. Thanks!
Comment #25
wim leersGreat! 😀
18 files changed, 529 insertions, 921 deletions.✅ These changes must be accidental: they revert back to a previous iteration of #2958554: Allow creation of file entities from binary data via JSON API requests. Reverted.
✅ And these revert #3026030: [regression] Includes are no longer respected when POSTing/PATCHing, so surely accidental too. Reverted.
✅ Similar problem: this undoes #3027501: [regression] Follow-up for #2995960 and #2992833: syntax errors in PHP 5.5 & 5.6. Reverted.
Hopefully I didn't make mistakes restoring those.
Now down to
14 files changed, 505 insertions(+), 816 deletions(-), still similarly awesome!Comment #26
wim leers👏 Nice new helpers!
🤔 Is this actually true though? Because
\Drupal\jsonapi\JsonApiResource\ResourceIdentifier::compare()contains this:😮 I think this is now officially the most complex function in the JSON:API codebase! But this is just existing complexity, now better isolated, and far better documented, so +1.
👎 We lost these valuable docs.
✅ inheritdoc
✅ Same as parent, can be removed!
Comment #27
wim leersNW for points 2 and 4.
Comment #28
e0ipsoThe main goal for this was to serve as a wrapper for other kind of relationships that are not entity references. This was initially used to create virtual relationships, but after all the reactors that's no longer possible. At least not this way.
Comment #29
gabesullice@e0ipso, virtual relationships are something I'm actually still quite enthusiastic about and have been thinking about for this issue and in #3015438: Wrap entity objects in a ResourceObject which carries a ResourceType.
The idea is that you could pass anything that implements
ResourceIdentifierInterfaceinto anEntityCollectionobject (aka a document's primary data) and that could be normalized by our normalization system... whether it's a virtual relationship route, virtual related route, or a custom collection.We're not quite there yet, everything is still @internal, but I'm working to make that dream closer than ever!
Comment #30
gabesullice#26
2. The short answer is, "yes".
The not so short answer is... complicated. This comment is referring to comparing sets of resource identifiers, not just one identifier with another.
When comparing sets, we need the
existingset to all have arity values, even if they're singletons, because the::comparefunction considers any resource identifier without an arity to be equal to any resource identifier regardless of arity. This means that if a single identifier already exists (and thus has no arity) if I try to send a new relationship with arity 1, it will not be added because it will be considered a duplicate of the existing identifier even though it has a new arity.4. Restored and expanded this a bit.
Comment #31
gabesulliceComment #33
wim leers#28: I'm not sure I understand. This issue is adding more domain logic to
ResourceIdentifier. the only coupling to "entity" is::fromEntity(). Non-entity relationships can still be modeled usingResourceIdentifier: by doingnew ResourceIdentifier('something-custom', 'UUID'). What am I missing?That being said, thanks to your comment, and my grepping for "entity" in that class, I did notice something! 😀
public static function fromEntity(EntityInterface $entity)has existed since #2997600: Resolve included resources prior to normalization. This patch is addingpublic static function toResourceIdentifier(EntityReferenceItem $item, $arity = NULL)andpublic static function toResourceIdentifiers(EntityReferenceFieldItemListInterface $items). Those should be namedfromEntityReference()andfromEntityReferenceList()!And … actually … perhaps that's your concern? That we're adding that "map entity-like stuff to
ResourceIdentifier" logic to this class?If that's not it, let's talk tomorrow about it?
Comment #34
gabesulliceReally strange, I can't replicate the fail on #30 locally. I queued an 8.7 test, maybe that's it.
Comment #35
gabesulliceohhh, duh. @Wim Leers told me this yesterday and I forgot:
The patch in #30 is actually a patch for #3015438: Wrap entity objects in a ResourceObject which carries a ResourceType, not this issue!
Correct patch attached.
Comment #36
wim leersis out of scope here.
And all of the fixes I made in #25 are lost too. I don't know what's going on, but something seems very off with your dev env. I wanted to RTBC this, but can't. I wanted to quickly fix it for you, but the #25 interdiff (which I made manually) no longer works. So, marking NW and assigning back to you.
Comment #37
gabesulliceThe interdiff in #25 was not actually the interdiff for the patch, it accidentally got included in #35. This reverts it.
Comment #38
wim leers👍 Together with #3015438: Wrap entity objects in a ResourceObject which carries a ResourceType, this becomes even simpler.
🤔 Wouldn't this be more clear as:
? That would then show far more clearly that this is the same as
::isDuplicate()while keeping arity equal.🤔 Shouldn't all these typehints be
\Drupal\jsonapi\JsonApiResource\ResourceIdentifier[]?toResourceIdentifiers()does this for example. Is there a reason I'm missing?🤔 Why have a closure here, why not just
[static::class, 'compare']?🤔 You explained the rationale in more detail in #30. Thanks for that. But I think there's still a bit of confusion in here.
::toComparable…implies you're reading this array of resource identifiers for use with::compare().But
::compare()ignores arity if it's absent on either operand. So for::compare(), thehasArity() ? … : …->withArity()isn't necessary. It's only necessary because you want to use it for this particular purpose.I think that either:
::compare()should require an arity on all operands (then everything about this method makes unambiguous sense, plus::isDuplicate()wouldn't return TRUE when one operand has an arity and the other doesn't)gain an additional required parameter indicating whether arity should be considered or notbe renamed to indicate it's about comparisons including arity.I think the first makes most sense, it removes ambiguity from multiple places.
Comment #39
gabesullice#38
2. Yeah, good suggestion!
3. Another good catch.
4.
I tried to confirm this locally but realized I was wrong! TIL :)comparewould need to be a public method to use that syntax.5. I've spent a while on this and the result did not seem to result in less complexity. It just moved it around. I think I might be misunderstanding your suggestion. Given that this is internal, would you consider doing this piece as a followup? Or, perhaps we can just pair on it.
Comment #41
gabesulliceCorrect interdiff + patch for #39
Comment #43
gabesullice@Wim Leers and I discussed #38.5 on a video call. We decided that the best course of action was to revert the new "helper" methods added #22, which would eliminate the need for the methods that are causing confusion.
I still had to tweak things a bit because
toResourceIdentifiersnow returns a list with that may have some arities omitted (this is correct from an external point of view). But internally we sometimes need this to not be the case. Thus I had to make it possible to get an array of resource identifiers that all have arities.That makes the change set a lot smaller. I think we should have a followup to clear up any confusion left in the
ResourceIdentifierclass that was already present prior to this issue.Comment #45
gabesulliceTypo.
Comment #46
gabesulliceI needed to rebase too.
Comment #49
gabesulliceComment #50
wim leersI like this much better! 2.2 times more deletions than insertions! 👌
But I think we can do better still.
✅ I dropped this change because it's clear enough as is and distracts from the purpose/scope of the issue.
✅
👎The new::withoutArity()method wouldn't be necessary if we'd just usewithArity(0)like I suggested in #38.2. Made that change, because it further simplifies this patch.✅
👎This is repeating something. Fixed.✅
👎These lines can be removed. The only thing we do with$fieldis asserting it's of a certain type. But given the type restriction on the$itemfunction argument, this is guaranteed to be true anyway. So … removed this.Comment #51
wim leers13 files changed, 291 insertions, 683 deletions.— 2.3 times as many deletions as insertions 👍My chief remaining concern is this:
Could we not change this name, to further reduce the amount of change? The name change does more accurately describe what this method does though. (The confusing bit is that
toResourceIdentifiers()in HEAD is effectively renamed totoResourceIdentifiersWithArityRequired(), buttoResourceIdentifiers()is simultaneously reintroduced, for the public representation of resource identifiers, where single-occurrence relationships have their arity omitted.)The answer to that question is "no", at least as long as we keep
\Drupal\jsonapi\JsonApiResource\ResourceIdentifier::compare()'s confusing logic:And changing that is definitely out of scope here.
(I tried to move the majority of changes in the
ResourceIdentifierclass out of here, to do it in a separate issue, but I don't see how that gets us anywhere:perhaps_do_separately-3024896-do-not-test.patch.)Despite that portion, which I'd like to be simpler, I think this is still a solid step forward:
ResourceIdentifierchanges it from an anemic domain model to one that contains all of the associated behaviors/logic.So: RTBC.
Comment #52
wim leers@gabesullice I think it's now up to you to provide a patch for JSON:API Extras —
\Drupal\jsonapi_extras\Normalizer\RelationshipItemNormalizer::normalize()is impacted.Comment #53
effulgentsia commentedGreat cleanup here! This isn't necessarily a commit blocker for #2843147: Add JSON:API to core as a stable module, but IMO it's a pretty strong nice to have, so making it a child issue for now.
Comment #54
effulgentsia commentedSorry, wrong parent issue in #53. This is the one I meant.
Comment #55
wim leersAs of #53, this apparently has become a semi-blocker to JSON:API getting committed to core. So, rather than waiting for @gabesullice (who's currently asleep), I'm doing #52 in his place.
It's only been a few weeks that JSON:API Extras has had
RelationshipItemNormalizer— since #3025284: Allow field enhancers for relationships. Unfortunately, without test coverage.On it …
Comment #56
wim leersPer #55, I had to do manual testing to create a patch for JSON:API Extras to keep it compatible. So I did.
Updating #3025284: Allow field enhancers for relationships mostly is easy, with one exception: JSON:API Extras' Field Enhancers are designed to be configured per field. Meaning that it needs to know the field. In HEAD, it's able to go and introspect the Typed Data data (which by the way means that it conflicts with #28: Field Enhancers are tightly coupled to entity references), but with the patch in this issue that's no longer possible.
Fortunately, a one-line addition to the current patch makes that possible again. So this interdiff is to make it possible to keep JSON:API Extras working like it does today.
Comment #57
e0ipsoAny chance we can test Consumer Image Styles 3.x before committing this?
Comment #58
gabesulliceThe tests fail, I'm researching the cause now.
Comment #59
gabesulliceIt seems to be unrelated: https://www.drupal.org/pift-ci-job/1189746
Comment #60
e0ipsoThanks for this! +1
I broke tests in Consumer Image Styles… :-/
Comment #62
wim leers#56 was incomplete; found by running JSON:API Extras' tests. Tiny addition on commit attached.
Comment #63
wim leersBTW,
consumer_image_stylesseems to work fine here… its test passed with this + #3032451-10: Compatibility with upcoming JSON:API 2.2 release: JSON:API Extras 3.4! 🙂Comment #64
wim leersThis also allowed me to close #3029043: EntityReferenceFieldNormalizer can improperly normalize items when a reference to an internal entity is involved.!
Comment #65
wim leersUgh, I just noticed that d.o made me the author of this issue simply because of my rerolls :( d.o--
@gabesullice, in the next path that I'm the primary author of, mark yourself as the author, to counterbalance it :)
Comment #67
e0ipsoThis broke JSON:API Extras in #3048341: Enhancers are not passed context