core_field_views_data() provides reverse relationships for entity reference fields, but this is only for config fields.
For base fields on entities, such as the node uid field, core entity modules have to implement this themselves.
Eg UserViewsData::UserViewsData():
$data['users_field_data']['uid']['relationship'] = array(
'title' => t('Content authored'),
'help' => t('Relate content to the user who created it. This relationship will create one record for each content item created by the user.'),
'id' => 'standard',
'base' => 'node_field_data',
'base field' => 'uid',
'field' => 'uid',
'label' => t('nodes'),
);
Comments
Comment #6
joachim commentedTechnically, this is something we could do in the base entity views handler class, EntityViewsData. Even though we would be setting data for an entity type other than the one being processed by the handler, the returned data from the handler is deep-merged by views_views_data().
Comment #7
joachim commentedHere's some code I wrote for a specific base field's reverse relationship.
I stuck as closely as possible to the code in core_field_views_data() so it could be generalized:
All that's needed now is to wrap that in a loop like this:
Comment #8
joachim commentedHere's a patch.
Comment #10
joachim commentedReroll & fixed a stray hardcoded entity type.
Comment #12
joachim commentedComment #14
joachim commentedShould be:
The idea is to match this pattern for config fields:
Comment #15
joachim commentedFound another problem -- the entity_reverse relationship plugin expects there to be a bridge table between the two entity base tables. That's because it's written for config fields, where the field always has a dedicated table.
With base fields though, the field is on the base table, unless the field is multi-valued. So for most base fields, we want the normal relationship handler.
Comment #16
joachim commentedNot a brilliant label, as you often end up with a relationship shown in your UI as:
'(TARGET_TYPE) TARGET_TYPE'
if the field is named for the target type, as is often the case.
Comment #19
rosk0Not sure how to handle the situation when referenced table is not described to views yet, so just added isset check.
Lets see what testbot thinks.
Comment #21
joachim commented> Not sure how to handle the situation when referenced table is not described to views yet, so just added isset check.
Ah, you mean because we're within the entity views data handler, and so working on a particular entity, so this situation could happen:
- handler for entity type A is running
- entity type A has a reference to entity type Z, and handler for Z has not run yet to declare the Z base tables to Views.
That's fine. An isset() is not needed here. That's because the code that invokes hook_views_data(), and then the entity views handlers, does a deep merge of all the returned arrays. As long as we put the Z data in the right place, it'll just get merged in.
The isset() should be removed, and a comment added to explain why it's ok.
Comment #22
rosk0Right, I think this way is more obvious
Comment #24
rosk0Silly, this is how it supposed to be...
Comment #26
joachim commented$data[$target_base_table][$pseudo_field_name]['relationship']['relationship field'] = $data[$target_base_table]['table']['base']['field'];Ah but we need that in all cases for the relationship to work!
If we don’t have the entity type yet, we will need to deduce that value.
Comment #27
jsst commentedI've tested this patch (#24) on a simple view I have. It's a view on one entity with view_bulk_operations checkboxes. When I add a reverse entity reference field to the view and select a single row for a VBO action, VBO acts as if all rows in the view were selected.
Comment #28
joachim commentedHere's the patch with the right way to handle entity types not being there yet.
I've not had time to investigate the problem reported in #27.
Comment #30
joachim commentedThis should fix the failing test. Might also fix #27?
Comment #32
joachim commentedThis should fix all the tests, but there will still be one failure due to #3004300: EntityViewsData fails to set 'entity revision' in the table data for an entity's revision table which the changes here expose.
Comment #34
joachim commentedFixed the other failing test.
Will still have one test failure due to the other issue.
Comment #36
nadavoid commented@joachim Thank you for the great work on this. Is getting all tests passing the only thing remaining?
I was able to use the important parts of your patch in a custom class, until this patch is committed to core. Attaching it here in case anyone else finds it useful in the interim.
Comment #37
nadavoid commentedComment #39
johnpitcairn commentedThanks for your work on this @joachim.
The patch at #34 will also apply cleanly to 8.6.x, and I'm getting usable reverse relationships to an entityreference base field on a custom entity. In my case neither that entity nor the referenced entity are revisionable.
Comment #40
rlmumfordRe-running tests.
Comment #41
daffie commentedComment #42
vacho commentedOnly patch reroll
Comment #43
vacho commentedComment #45
bojanz commentedAdapted this code into CommerceEntityViewsData, so that Commerce and its contribs get reverse relationships:
#3096916: Generate reverse relationships for base entity references
Other contribs, feel free to steal it, for until the core patch lands :)
EDIT: This is now a pat of the Entity API contrib module, from version 1.0. just use Drupal\entity\EntityViewsData in your entity type annotation.
Comment #46
damienmckennaThis does not appear to work for me using ECK entities, but whether that's an ECK problem or a limitation of the patch I do not know yet.
Comment #47
ivnish#42 doesn't apply to 8.7.11 :(
Comment #48
abdhomsi commentedFor 8.8
Comment #50
rob230 commentedPatch #42 and #48 are breaking the site for me with this error:
This patch breaks backwards compatibility by changing the function definition for
mapSingleFieldViewsData()and removing the return parameter, so the entire file must be rewritten.Comment #51
rob230 commentedI see in #45 that similar work has been done in Commerce, however, this does not create the reverse relationship from an order to a subscription with that order as its initial order (an entity_reference field). And I cannot apply this patch because it breaks Commerce views integration.
I see this as a Drupal core bug rather than a Commerce bug. For a base field definition that is an entity reference, these views relationships and reverse relationships should be created automatically.
Comment #54
louis-cuny commentedUpload a D9 compatible patch. Just updated two lines.
I faced the following error to noticed the patch was not d9 compatible :
Fatal error: Uncaught Error: Call to a member function getDefinition() on null in /app/web/core/modules/views/src/EntityViewsData.php:611
Comment #55
joachim commentedThis should wait until #3116481: Convert EntityViewsDataTest from a unit test to a kernel test is in, as it will need tests.
Comment #56
gregglesFor anyone using a patch before 54 on a Drupal 8 site, perhaps using composer-patches, when you upgrade to Drupal 9 you will get an error stacktrace that starts like this:
Putting this here in the hopes the search engines will index it and it will help other people running into this problem :)
Comment #57
dqdApplies at Drupal 9.2.7 with -11 lines offset:
Comment #59
sch4lly commentedAttached patch works for Drupal 9.3, there were some minor deprecation issues which I fixed.
Comment #60
stijndmd commentedApplies and works on a clean Drupal 9.3.
Comment #61
a.sinitsa commentedDoes not apply to 9.4.x-dev
Comment #62
ravi.shankar commentedAdded reroll of patch #59 on Drupal 9.4.x.
Comment #63
andregp commentedQuick Review:
@sch4lly you may have unintentionally uploaded the wrong patch, but the patch #59 is identical to the patch #54 even the index hash code is the same.
Regarding #62. Thank's @ravi.shankar for the re-roll. :)
Just two notes:
Comment #64
ravi.shankar commentedThanks @andregp.
Added reroll of patch #59 for Drupal 9.4.x. and I have removed space as said in comment # 63.1.
Added reroll diff as well.
Please ignore patch #62.
Comment #65
yogeshmpawarResolved CSpell errors & added an interdiff.
Comment #66
yogeshmpawarKeeping it in NW as it requires tests.
Comment #68
chi commentedWhen used with Commerce module patch #65 causes errors described in #50. Also field_name parameter in mapSingleFieldViewsData() seems unused.
Comment #69
joachim commented> This patch breaks backwards compatibility by changing the function definition for mapSingleFieldViewsData() and removing the return parameter, so the entire file must be rewritten.
The BC policy says that specific entity handlers are internal:
> Particular entity handlers should not be considered part of the public API. The interfaces which define those entity handlers though are part of the supported API.
Comment #70
v.kydyba commentedAs mentioned in #69, the patch #65 causes 2 errors many times for me in dblog:
The
$table_datavariable is not defined before foreach.The method mapSingleFieldViewsData() is used as argument for NestedArray::mergeDeep() in mapFieldDefinition(), but it returns nothing.
Comment #71
darvanenJust a note that "Build Successful" is not a pass. The patch is failing badly and will likely need a reroll for 9.5
Comment #72
rymcveigh@lkacenja and I have attached a patch that works on Drupal 9.4 and 9.5.
Comment #73
johnpitcairn commentedComment #74
johnpitcairn commentedComment #75
ravi.shankar commentedFixed Drupal CS issue of patch #72.
Comment #76
rymcveighThe test fail for patch #75 is
We probably need to make sure we are adding the entity revision key to our relationship array.
Comment #79
solideogloria commentedComment #80
szeidlerRerolling patch #75 for Drupal 10.1.x.
Comment #82
jonathanshawI believe the test fail is due to #3004300: EntityViewsData fails to set 'entity revision' in the table data for an entity's revision table.
Comment #83
smustgrave commentedDid not review.
But was previously tagged for tests which still appear to be needed
Also issue summary should follow standard template.
Comment #84
jonathanshawAnyone wanting to work on the missing test should look at EntityReferenceRelationshipTest (which tests the forward and reverse relationship for configured fields) and EntityViewsDataTest (which tests the forward relationship for base fields, and is where the test for the reverse should go).
Currently EntityViewsDataTest::testBaseTableFields() has:
We probably need to add to this:
Comment #85
geek-merlinPlayed this and it looks it needs much more work.
After installing with the commerce module enabled, i get:
Uncaught PHP Exception ArgumentCountError: "Too few arguments to function Drupal\views\EntityViewsData::mapSingleFieldViewsData(), 7 passed in /home/merlin/Code-Incubator/site-c4c-dev/web/modules/contrib/commerce/src/CommerceEntityViewsData.php on line 220 and exactly 8 expected" at /home/merlin/Code-Incubator/site-c4c-dev/web/core/modules/views/src/EntityViewsData.php line 483Which is because a $data ("all views data") arg was added to mapSingleFieldViewsData method.
- 1) This is a BC break.
- 2) Looking over the code, my gut feeling is that adding this arg makes complex code even more complex and should be done differently.
Comment #86
joachim commentedAgreed, the BC break needs fixing, as clearly other code is calling this.
(We probably need our BC policy to get real and state that generic entity handlers are public API, because everyone already treats them as such.)
> - 2) Looking over the code, my gut feeling is that adding this arg makes complex code even more complex and should be done differently.
I'm not sure how!
The nature of a reverse entity reference field is that we need to add data to ANOTHER table from the one for the current field -- it needs to go on the table for the reference field's target entity.
And the way the methods in this class work is that there is a helper method for each field type.
Therefore, the field type helper method, processViewsDataForEntityReference() in this case, needs access to the whole of the Views data, not just for the field's table.
Comment #87
joachim commentedIn the meantime, I've converted the most recent patch to an MR, rebased on 11.x.
Comment #89
joachim commentedThe UI texts aren't clear when the host and target entity type are the same, as with taxonomy parent field:
> “Taxonomy term using parent” — “Relate each Taxonomy term with a parent set to the taxonomy term.
Needs a rewrite.
Comment #90
joachim commentedI've had an idea for a clean way to do this. We would need get #2337515: Allow @FieldType to customize views data in, and then the classes that provide Views data for each field type can implement two methods:
- one to add field data for the field table, with &$field_data as a param
- one to allow the class to add data ANYWHERE, with &$data as a param -- most field types would not need to use this
Comment #91
dahousecat commentedRerolling patch #80 for Drupal 11