Problem/Motivation
\Drupal\jsonapi\ResourceType\ResourceTypeRepository::getRelatableResourceTypesFromFieldDefinition() directly reads the setting target_type from the field definition, however not all reference fields have this setting. A concrete example is the dynamic_entity_reference module, which allows for referencing multiple entity types and it lacks the setting target_type. See https://git.drupalcode.org/project/dynamic_entity_reference/blob/8.x-2.x...
Proposed resolution
Define a new interface, with a method named getReferenceableBundles(FieldDefinitionInterface $definition), which is then used to retrieve the entity types and bundles being referenced by that particular item class in a single API.
This is a pathway to effectively solving #3262385: Add an API for general entity reference field types by allowing code to introspect the field item class and determine if it implements this new interface.
Remaining tasks
Review and possibly some new tests?
User interface changes
None.
API changes
New interface.
Data model changes
None.
Release notes snippet
TBD: Draft Change Records needed.
Original IS snippets
Option 1:
Add a new method getReferenceableEntityTypes() on the interface \Drupal\Core\Field\EntityReferenceFieldItemListInterface(), which could be overwritten by \Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceFieldItemList().
jsonapi could then call the new method instead of assuming the setting target_type always exists when there is a property of the type \Drupal\Core\TypedData\DataReferenceTargetDefinition.
Option 2:
A more scalable and independent option might be an approach of adding the referenceable entity types configuration to the DataReferenceTargetDefinition property. DynamicEntityReferenceItem defines two such properties and would have to do it for both, but this should not be a problem or maybe only one of them could list the referenceable entity types configuration.
jsonapi could then retrieve the referenceable entity types configuration directly from the DataReferenceTargetDefinition properties instead of assuming there is a field definition setting target_type.
------
Option 1 does not work because the item list might be used by multiple item implementations.
Option 2 does not work because the properties are retrieved based on the field storage definition and not on the field definition for bundle fields.
This led to Option 3 (copied to proposed solution, above.)
| Comment | File | Size | Author |
|---|
Issue fork drupal-3057545
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
hchonovAn implementation of the second approach adding a setting to DataReferenceTargetDefinition.
Attaching also a core-only patch, as it would be needed for a combination with the contrib jsonapi module.
Comment #4
hchonovI've just realized that adding a setting to DataReferenceTargetDefinition is not possible, because the handler settings are placed on the field definition and not on the field storage definition and therefore when the properties are retrieved through
\Drupal\field\Entity\FieldStorageConfig::getPropertyDefinitions()the handler settings will not be present and therefore we cannot populate the property settings correctly.Comment #5
hchonovHere is another approach, where the item class could define a method named
getReferenceableEntityTypes(), which could then be used to retrieve the entity types and bundles being referenced by that particular item class. Probably we could define an interface for that method?Comment #6
jibranThis might be a duplicate of #3029090: Dynamic Entity Reference: support discovering relatable resource types.
Comment #7
wim leers@hchonov Thanks for all this work!
It's exactly what I proposed in #3029090: Dynamic Entity Reference: support discovering relatable resource types (Jan 30, 2019) and #3005274-17: [PP-1] Support for Paragraphs' ability to negate the entity reference selection (May 21, 2019)! (Which @jibran already linked, thanks Jibran!)
I'm fine with doing all that in this issue instead, because those two issues started from the DER and Paragraphs POV respectively, this one is more generic. Then we'd postpone those two issues on this one, and rename this one to . Thoughts?
This solves the challenge of both target types and target bundles in a single new API. This is much better than what I proposed in #3005274-17: [PP-1] Support for Paragraphs' ability to negate the entity reference selection. I'm +1 😀
Assigning to @Berdir for review.
Comment #8
hchonovThis works for me :)
I am glad, that you like it!
---
P.S. I've updated the issue summary to reflect the changes. The open question now is if we want to define a new interface for that method - for example
EntityReferenceItemInterfaceor maybe more general nameEntityReferenceFieldInterface, which I think is not directly bound to the item classEntityReferenceItem.Comment #9
hchonovfixing a typo in the IS.
Comment #10
wim leersI am not convinced we absolutely need an interface for this. In fact, I think we shouldn't do that. Because we've gotten this far into the Drupal 8 cycle without an interface for
\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem, and we see lots of contrib modules doingMySpecialReferenceItem extends EntityReferenceItem. The only way to provide custom entity reference-like field types and have them work with the rest of the ecosystem is subclassing, so why should we start deviating from that now?Am I missing something?
Also: would really like to hear @jibran's and @Berdir's thoughts on this — I think a +1 from both of them is required for this to reach RTBC.
Comment #11
hchonovIf we let
EntityReferenceItemimplement the new interface and provide an implementation of it inEntityReferenceItem, then sub-classes will not be affected, because they will inherit the interface and the implementation. I don't really see a change regarding the classes extending fromEntityReferenceItem.The only reason I would prefer an interface over a method not defined in any interface is that I think it is better DX to check if a class implements a specific interface instead of checking through
method_exists()for the implementation of a specific method. Also having a dedicated interface for that, kind of, makes it more straight-forward to have entity reference field types, which do not extend fromEntityReferenceItem, but do have properties of the typeDataReferenceTargetDefinition.Also please note that this would not be the first interface for a field item. We already have an example for this in core -
\Drupal\link\LinkItemInterface.However this is just a proposal and I will be okay with
method_exists()as well :).Sure, I, as well, think that it is necessary to see what both of them think about the approach!
Comment #12
wim leers#11 works for me — thanks for explaining your thinking in so much detail, it's very helpful 🙂
Comment #13
berdir+1 on having a method and also having a new, optional interface for it.
\Drupal\jsonapi\ResourceType\ResourceTypeRepository::calculateRelatableResourceTypes() (which calls this) is some pretty wild code (or how the cool kids these days call it, functional) with a combined array map and filter. It filters first by field types having a main property that implements DataReferenceTargetDefinition. DynamicEntityReferenceItem doesn't have a main property, so I think this patch alone won't actually make it work with that. IMHO, we can drop that check as a separate thing and primarily go against this interface, with a fallback for the main property. We could even consider to deprecate that fallback and trigger a @deprecated() if a field type doesn't implement it?
Not quite sure about the method name yet, it says EntityTypes but actually returns bundles keyed by entity type ID. So maybe getReferenceableBundles() as a name would actually make more sense? Since regular most fields will only return one entity type anyway.
And last, the thing is that we are still hardcoding the handler settings. But those are the settings of the selection plugin and as pointed out by Wim, this won't yet support alternative selection plugins. But we could go "one level deeper" in the referenced issue and move that logic into selection plugins.
Comment #14
berdirAlso noticed that \Drupal\jsonapi\ResourceType\ResourceTypeRepository::isReferenceFieldDefinition() doesn't check if getMainProperty() returns anything and calls getPropertyDefinition(NULL), that only works because the implementations just return nothing, but this would fail if we'd enforce the argument with a string type hint for example.
Comment #15
jibranI'm +1 on this as well.
DER has a main property https://git.drupalcode.org/project/dynamic_entity_reference/blob/8.x-2.x... I discussed setting one up, at length, with you at DrupalCon Dublin ;-)
+1
Yeah, this makes sense.
Comment #16
berdir> DER has a main property https://git.drupalcode.org/project/dynamic_entity_reference/blob/8.x-2.x... I discussed setting one up, at length, with you at DrupalCon Dublin ;-)
Riiight... the module in my D8 dev installation was 4 years old :p I think my suggestion makes sense anyway though.
Comment #17
ainarend commentedSo as I understand, there's still work to be done :)
Comment #18
hchonovI've created the interface
EntityReferenceItemInterfaceand added the new methodgetReferenceableBundles()to it.This is a task for the referenced issue, right?
As already clarified this is not the case here. I also think that it still makes sense to add the string type hint, but I don't really think that it falls into the scope of the current issue.
Comment #19
hchonovWe've just found out that the
IncludeResolverhas to be updated as well.Comment #20
gabesulliceI understand why this needs to be on the FieldItemBase but it makes it impossible to use
EntityReferenceItemInterfaceas a means to detect fields that are entity reference. Would it be too far out of scope to add anhasEntityReferencePropertymethod along the same lines asEntityInterface::isRevisionable()?Nit: Can
? $handler_settings['target_bundles']be on its own line?Nit: "The referenceable entity types and bundles."
I think this needs some expansion. Maybe: "An array keyed by entity type IDs where the values are an indexed array of bundle IDs or ??? if the entity type does not have bundles"
Comment #21
hchonovPlease elaborate on why it is impossible to use the new interface to detect entity reference fields? I think that it is quite the opposite. According to the Drupal BC policy if there is an interface and a base class one should extend from the base class. Well, we didn't have the dedicated interface
EntityReferenceItemInterfaceyet, but I think that the BC policy might be interpreted the same way -EntityReferenceItemis the base class and therefore all entity reference fields should extend from it. However I am not 100 % sure about this interpretation and I would need some backup on that :). It is just my interpretation and it should not necessarily match the views of others.EntityInterfaceis not on the field level, but on the entity level. I am confused what the requirement is.I am fine with this, but would like to know the reasoning behind it given the short size of the statement. :)
Comment #22
gabesullice@hchonov, 🤦♂️🤦♂️🤦♂️ my mistake! I mistakenly read:
class FieldItemBase implementsinstead of:
class EntityReferenceItem extends FieldItemBase implementsWhich made me think that the interface would be on all field items, making it impossible to use for detection. I was trying to work quickly and I guess I just worked sloppily :\ Now that I read it correctly, I'm very excited to have this interface and to be able to use it for detection!
I think @e0ipso taught me this code pattern a couple years ago. He told me it was a JS best practice and I've since grown to appreciate it because the style makes it easy to spot a ternary. A indented line starting with
?just stands out. If you haveag(the silver searcher) installed, try runningag -A1 -B1 '^\s*\?'and you'll find we use that style pretty much exclusively within thejsonapimodule unless the entire ternary is all on a single line.Comment #23
kfritscheThis patch still doesn't cover everything.
I noticed today that when trying to filter by a field of an dynamic entity reference it still has an error.
I tried to fix it, but couldn't get it to work, but still uploading my current progress.
What works currently is the following json api filter (target is a DER field, thats why entity:node is required):
filter[target.entity:node.title]=helloWhat currently doesn't work is:
filter[target.entity:node.type.meta.id]=articleI'm unsure how the filter should look like, but I can't get the bundle filter working.
Using taget.entity:node.type I get "detail: "Invalid nested filtering. The field `type`, given in the path `target.entity:node.type` is incomplete, it must end with one of the following specifiers: `id`, `meta.value`.""
Using target.entity:node.type.id results in "Ambiguous path. Try one of the following: target.entity:node.type.entity:node_type.id, target.entity:node.type.entity:block_content_type.id ,... in place of the given path: target.entity:node.type.id" - thats the point where I see my fixes are working as I get all possible referenced entity types
Using target.entity:node.type.entity:node_type.id results in "Filtering on config entities is not supported by Drupal's entity API. You tried to filter on a Content type config entity."
Using meta.id at the end results in a 500 with "Invalid specifier 'value'
So somehow I didn't catch the case yet. I think somehow the $candidate_definitions needs to filtered in the loop again when something like "entity:node" is specified.
Some strange side note where I'm unsure if bug or intended design of entity queries and DER.
fails with an error to get it working I need to add the target_id property of the DER field to get it working.
Thats why I needed to introduce $data_reference_target_property_name in the patch.
Comment #24
jibranRE: #23
As per https://www.drupal.org/node/2835034 and https://www.drupal.org/node/2825947, this shouldn't be the case. DER and core have some tests to check this functionality see https://git.drupalcode.org/project/dynamic_entity_reference/blob/8.x-1.x... and https://git.drupalcode.org/project/drupal/blob/8.2.x/core/tests/Drupal/K....
If your DER field is a basefield then #2915512: Entity query doesn't allow relationship specifier for base entity fields might be a culprit here.
Comment #26
geaseReroll of the patch against 8.9.x. The whole FieldResolver::collectResourceTypesForReference() was removed in #3014277: ResourceTypes should know about their fields so I didn't keep this part of the patch. If it still makes sense, it should be implemented differently. Also I see no use of
use Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem;in FieldResolver.Btw, doesn't the above-mentioned issue resolve this one?
Comment #27
geaseComments 2-5 from #20 addressed.
Comment #29
jibranOK seems like we have a consensus here from at least two entity API maintainers (@hchonov and @Berdir), two JSON:API maintainers (@Wim Leers and @gabesullice) and DER maintainer myself.
Let's add some tests to cover issues like #23 and also keep the #3057556: Implement DERItem::getReferenceableBundles green as well in DER issue queue as PoC.
Comment #31
larowlanThis needs to go - can't reference contrib class from core
Other than that, this approach is looking good
Comment #32
larowlanReroll after #2996114: Argument 2 passed to Drupal\jsonapi\Routing\Routes::Drupal\jsonapi\Routing\{closure}() must be an instance of Drupal\jsonapi\ResourceType\ResourceType, NULL given
Comment #33
larowlanMissed some bits in the reroll
Comment #34
gabesulliceThis looks good to me!
I only fixed a small grammatical nit in a comment. See interdiff.
Comment #35
jibranThere are a few things missing here:
\Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget::getAutocreateBundle()? Can be a follow up.Comment #37
kim.pepperComment #39
leon kessler commentedRe-rolling patch for 9.4.x.
The patch still applied but there were some offsets, here is my cli output.
Comment #40
leon kessler commentedI've been using the patch from #34 in production for quite a while, and have encountered an error.
If the referenced entity has been deleted, you get a 500.
Attaching a new patch that checks that the entity is valid. This fixes the error for me.
Comment #41
yogeshmpawarTrying to fix test failures.
Comment #44
yogeshmpawarone more try to resolve test failure & removing my old patch.
Comment #45
bbralaAwesome work. We do need to address the comments from #35 by @jibran :)
Also;
Comment #46
bradjones1Related jsonapi_extras issue needs committed to be compatible with this change - it had previously overridden some methods for D8 compatibility, including the method that determines the eligible entity/bundle targets.
Comment #48
bradjones1Converted to an MR, working to address #35:
As in, we need a test that basically provides a DER-type field in a test module?
Agree, will write/link one.
Agree, IS updated.
So as to not hold this up I think that should be a follow-up, in line with the deprecation notices I mentioned in the MR review.
Per @bbrala's feedback:
It wasn't immediately obvious to me why the test changes were made, but it traces back to #23. Which begs the question of, what patch is actually "under review." Hence the MR and a little bit of issue archaeology/cleanup here. The issue in #23 appears to be addressed in #24 and is likely due to (and fixed in?) #2915512: Entity query doesn't allow relationship specifier for base entity fields. So I have reverted these changes in the MR.
Comment #49
bradjones1Comment #50
bradjones1Comment #51
bradjones1CR: https://www.drupal.org/node/3279140
Comment #52
joachim commentedLooks good, just some fixes needed to docs.
Comment #53
bradjones1I'm going to mark this as NR as I'm curious as to whether we absolutely need more test coverage; this is a refactor of existing functionality and as such this does not break the existing ER field tests. Covering contrib, DER-type fields might be a little out of scope, now that I think of it? It would be "nice," but it seems like might not be in scope for core to test different use cases in the wild. Thoughts?
Comment #54
joachim commentedContentEntityBase::referencedEntities() will need updating, as it does:
> $property instanceof EntityReference
And generally, we should look through the whole codebase for anything which references either the EntityReference or EntityReferenceItem classes, or the entity_reference field type, and update them.
That could all be done in follow-ups though.
Comment #55
bbralaThere is precedence to test certain things contrib does. For example overriding resourcerepository I think even though the class is internal.
I don't think that is needed in the current use case. Although I would probably want to verify that with a quick contrib search for the changed method. Can't right not, I'm on mobile.
Comment #56
bbralaThere is some of the overwritten methods in contrib: http://grep.xnddx.ru/search?text=getRelatableResourceTypesFromFieldDefinition&filename=
jsonapi_extras was already mentioned.
jsonapi_cross_bundles, commerce_api and wotapi overwrites the method.
Comment #57
bbralaConclusion, i don't think we need to test. The impact on contrib should be minimal. My conclusion is we don't need to do extra testing for the custom code in contrib. Sorry for the noise :)
Note; did ping maintainers of the modules on slack, and i'm maintainer for jsonapi_extras so he is aware anyhow ;)
Comment #58
bradjones1Per #54:
I can ticket a follow up, since this one is a little more nuanced.
Edit: This is basically #3262385: Add an API for general entity reference field types
EntityReference in the method you mention is referring to the typed data DataType, and DER for instance actually extends this, so it works. Also we are not touching the DataType here, so that should be OK to keep as it is. But yes, we'd need a broader look around the codebase for typehints on the field type name or EntityReferenceItem itself. This one is a little tough too since the list class doesn't have anything to typehint on, just the item class, and I know at least in my code, I mix up the type checks sometimes assuming the list class is more or less tied 1:1 to the item type/class, which isn't necessarily the case (as we have here.)
But I think all that can be handled in a follow-up as this isn't a breaking change.
Comment #59
joachim commentedI'm going to say this is RTBC.
The things I mentioned in #54 can be done in a follow-up.
Comment #60
bradjones1Woohoo! Opened follow-up #3279986: Use EntityReferenceItemInterface::getReferenceableBundles() wherever core needs to discover reference-able entities for a field.
Comment #62
catchThis overall looks good, but we can't deprecate for removal in 10.x any more.
Comment #63
bradjones1Marking back to RTBC as this was a very small, specifically-requested change.
Comment #64
alexpottWe need to add a test of the deprecation and that the old behaviour still works as expected.
Comment #65
bbralaOk, guess i was wrong then. Will keep an eye out for this issue.
Comment #66
joachim commentedIt feels like there's still a fair bit of work to do here.
Would it be worth splitting off the changes in Core/Field to a separate issue, so that contrib can benefit from this new API sooner?
There are a TON of contrib modules that really need this so they can work with generic reference fields. It's classified as a normal bug report on jsonapi module, but in core I feel this would be a major feature request.
Comment #67
bradjones1Really? Alex's suggestion for two test cases is pretty reasonable, and I'm not sure what other work is required?
Comment #68
joachim commentedWhat is this comment about?
Currently, with the comma, it means that *all* entity reference fields don't have the thing stored in settings. But that's surely not true, since if that were the case, this comment wouldn't be inside an if() block, since *everything* here is an entity reference fields.
Does it mean "support some entity reference fields, specifically the ones which don't have the thing stored in settings"?
Comment #69
bbralaYeah the comma seems extra.
Comment #70
joachim commentedNot just extra -- it changes the meaning to something which I suspect is incorrect.
I'm changing it to 'that', which most style guides recommend for a restrictive clause.
Comment #71
joachim commented> Alex's suggestion for two test cases is pretty reasonable, and I'm not sure what other work is required?
Agreed, but those are not simple tests to write!
I assume we need a test module which defines a custom field type which is a reference in that it uses DataReferenceTargetDefinition for one of its field properties, but doesn't inherit from EntityReferenceItem.
That itself is tinkering about in the innards of the field system and the typed data system.
Then we need a kernel test which makes jsonapi requests with includes, and that requires an understanding of the patterns of the kernel tests in the module. I've had a brief look and there's not much documentation, so I'm really not clear on how to set up the test, or where to put it to keep things organised and not make a mess.
These tests require very specialised knowledge, and they're holding up something considerably simpler which has wide-ranging benefits.
Comment #72
bbralaIf someone can create the field in this structure, I'd happy to prove the jsonapi part. Perhaps you eveb have code for that in your coming site Brian?
Comment #73
joachim commentedSure, I'll make the field type -- already got the skeleton of it in place.
Comment #74
bbralaAwesome thanks
Comment #75
joachim commentedPushed to a new branch `3057545-sandbox-test-module-for-deprecation` on this issues fork.
I hope I've implemented all the methods it needs -- I wrote a quick kernel test which installs the node schema and saves a node with a value in the field, and that all passes.
Comment #76
joachim commentedI still think the reference field stuff should be done in a separate issue.
The API we're adding here is incomplete -- for instance modules that work with reference field data such as https://www.drupal.org/project/entity_reference_purger or https://www.drupal.org/project/field_referenced_delete (when I've updated it to D9!) don't just need a getReferenceableBundles() method, they need to know "how do I query for the targets of fields of this type".
We're designing a new API here, and while I absolutely agree that APIs should be designed outside-in, that is, thinking of the consumers of the API and what they need, in this issue we're only looking at one consumer of this API and one with fairly limited needs.
Comment #78
acbramley commentedThanks to everyone pushing this issue along, we had been using patch #33 in production for a long time but started hitting the exception in FieldResolver when trying to filter a node's jsonapi response by a paragraph field. With the most recent MR updates everything seems to be working nicely :)
Comment #79
dpiFWIW, for Googleability the now resolved exception was
Comment #81
acbramley commentedI've rebased https://git.drupalcode.org/project/drupal/-/merge_requests/2231/diffs onto a new branch off 10.1.x, thankfully it applied with just a little fuzz :) I'll look into implementing some form of deprecation test now.
Can we please get MR 2231 closed?
Comment #82
acbramley commentedI've added test coverage for ResourceTypeRepository::getRelatableResourceTypesFromFieldDefinition, thank you very much @joachim for your sandbox module. It was pretty much exactly what I needed, all I added was the
mainPropertyNamefunction to tie everything together (it took me WAY too long to figure that out).Now all we need is a deprecation test for the include resolver.
Comment #83
acbramley commentedThat coverage came together a little easier than expected. This should be good to go now.
Comment #84
bbralaGreat work! We do need a follow up issue eventually to make sure the deprecation test module is removed from 11. Although it might just be part of removing this deprecated code in 11.
We also need a change record if we want to move forward.
If possible i'd also like a review from the contrib maintainers that ran into this issue since @bradjones1 and @joachim do seem to understand this issue very well.
Comment #85
acbramley commentedThanks :) there's already a draft CR attached
Comment #86
joachim commentedSee MR comment.
Comment #87
acbramley commentedAdded removal link and base class.
Comment #88
smustgrave commentedLeft a comment for the deprecation message.
Comment #89
acbramley commentedFeedback addressed
Comment #90
smustgrave commentedChanges look good and feedback addressed.
Comment #92
larowlanLeft some comments on the MR, looks very close, keen to get this into 10.2.0 early
Comment #93
bbralaI've addresses the issues mentioned by Larowlan. He gave a go to RTBC. Also updated CR to point at 10.2.
Did a local rebase on 11.x without a problem, but cannot change the target of the MR to 11.x so have not pushed.
Comment #94
bbralaTests failed.
Comment #95
bbralaFixed the test. Checked for the wrong datadefinition. Tests are green locally.
Comment #96
bbralaComment #98
bbralaGreen, so yay.
Also openened a MR to 11.x, it could rebase without any changes, so i guess the original MR will also merge into 11.x without a problem.
Comment #100
larowlanCommitted da5ea0a and pushed to 11.x. Thanks!
Been a long time coming, great work all 🙌
Comment #101
bbralaYay! Well done eveyone!
Comment #102
wim leersYAY! Epic work here!
Unpostponed: