Problem/Motivation

The Taxonomy term's 'parent' field never returns a value when requested via the API (e.g. $term->parent->getValue();), even when the term does have a parent.

Proposed resolution

Implement the logic for getting the Term's parent(s) when needed.

Remaining tasks

Review.

User interface changes

Nope.

API changes

Nope.

Data model changes

Nope.

Comments

amateescu created an issue. See original summary.

amateescu’s picture

Status: Active » Needs review
StatusFileSize
new25.03 KB
new11.75 KB
new4.78 KB

Here's a patch which makes the field computed and includes the logic for getting the term's parent. The patch depends on #2392845: Add a trait to standardize handling of computed item lists so I'm posting a combined patch as well.

The interdiff contains only the changes that were made to the tests from #2543726-200: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration.

amateescu’s picture

Issue summary: View changes
StatusFileSize
new25 KB
new11.72 KB
new630 bytes

On second thought, the field doesn't necessarily have to be computed if it wants to implement the trait.

The last submitted patch, 2: 2921048-combined.patch, failed testing. View results

The last submitted patch, 2: 2921048.patch, failed testing. View results

The last submitted patch, 3: 2921048-3-combined.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 3: 2921048-3.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new24.84 KB
new11.56 KB
new1.6 KB

This should do it.

The last submitted patch, 8: 2921048-8-combined.patch, failed testing. View results

amateescu’s picture

StatusFileSize
new24.87 KB
new11.59 KB
new1 KB

Sigh.. this should be green.

amateescu’s picture

wim leers’s picture

Status: Needs review » Needs work

This looks nice! 👏

Except for one thing:

+++ b/core/modules/hal/tests/src/Functional/EntityResource/Term/TermHalJsonAnonTest.php
@@ -47,6 +54,29 @@ protected function getExpectedNormalizedEntity() {
+    if ($has_parent) {
+      $normalization['_links'][$this->baseUrl . '/rest/relation/taxonomy_term/camelids/parent'] = [
+        ['href' => $this->baseUrl . '/taxonomy/term/2?_format=hal_json'],
+      ];
+      $normalization['_embedded'][$this->baseUrl . '/rest/relation/taxonomy_term/camelids/parent'] = [
+        [
+          '_links' => [
+            'self' => [
+              'href' => $this->baseUrl . '/taxonomy/term/2?_format=hal_json',
+            ],
+            'type' => [
+              'href' => $this->baseUrl . '/rest/type/taxonomy_term/camelids',
+            ],
+          ],
+          'uuid' => [
+            ['value' => Term::load($this->entity->get('parent')->target_id)->uuid()],
+          ],
+        ],
+      ];
+    }
+

+++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
@@ -90,6 +90,22 @@ protected function createEntity() {
+    $has_parent = !$this->entity->get('parent')->isEmpty();
+    if ($has_parent) {
+      $parent_id = $this->entity->get('parent')->target_id;
+      $parent = [
+        [
+          'target_id' => $parent_id,
+          'target_type' => 'taxonomy_term',
+          'target_uuid' => Term::load($parent_id)->uuid(),
+          'url' => base_path() . 'taxonomy/term/' . $parent_id,
+        ],
+      ];
+    }
+    else {
+      $parent = [];
+    }

This is wrong, this is only adding _links and _embedded (hal_json format) or a non-empty parent field (json format) for $term->parent if and only if the parent is not <root> (or term zero).

We need those to be always present, otherwise API clients would have to hardcode that logic themselves. We want to be explicit about the fact that a term doesn't have a parent.

#2543726: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration gets this right.

amateescu’s picture

Actually, I think that this patch does the right thing, IMO. Adding _links and _embedded or a parent reference to an entity with the ID 0 means (logically) that there is a resource with the ID 0 that can be requested via a REST call. Otherwise, we're just putting the burden on API clients to *know* that 0 is a special resource ID which means <root>.

The approach of this patch is to not send any details about a parent resource for root terms, with the logic being explained by "there is no parent therefore this term is at the root level".

However, after writing the two paragraphs above, I just realized that there is a flaw in my explanation, which is the multi-parent architecture of taxonomy terms. Since the root level is not explicitly determined by a "magic" ID, there is no way for a REST POST request to say: make this term a root-level one as well as a child of term X. Or for a REST GET request to know whether a term with a parent of Y is also a root term.

Do you happen to know of any examples of REST API's dealing with a multi-parent hierarchy resource like we have here?

The answer might very well be that we need to keep the magic <root> = 0 assumption that we have in #2543726: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration, but I just want to be sure there's no other better way for it.

wim leers’s picture

Actually, I think that this patch does the right thing, IMO.

I strongly disagree. This patch causes the normalizations/API responses to communicate information implicitly rather than explicitly, which can lead to misinterpretations, and requires all API clients to build in this understanding.

In #2543726-205: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration the _links and _embedded entries look like this when the parent term is the root term (term zero):

'_links' => [
  'http://d8/rest/relation/taxonomy_term/camelids/parent' => [
    NULL
  ],
],
 '_embedded' => [
  'http://d8/rest/relation/taxonomy_term/camelids/parent' => [
    NULL
  ],
],

The absence of a URL explicitly communicates that there is no parent resource to request.

multi-parent

This is a very good point, and something we're missing test coverage for. Working to add that… stay tuned.

wim leers’s picture

Added that multi-parent test coverage at #2543726-216: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration — now you can inherit it here.

Do you happen to know of any examples of REST API's dealing with a multi-parent hierarchy resource like we have here?

Not immediately, no.

The answer might very well be that we need to keep the magic <root> = 0 assumption that we have […]

I think so, yes.

amateescu’s picture

Actually, I think that this patch does the right thing, IMO.

I strongly disagree.

It would be nice if you could read the entire comment before disagreeing so strongly..

wim leers’s picture

(Typing on my phone from 🏓 competition.)

Huh? :( I really did not intend to upset you! I did read everything you wrote, and I tried to constructively explain why I disagree. The explicit versus implicit.

EDIT: oh, I see, I think you mean that you ended up kind of agreeing with me, yet my comment still answers part of your comment before you agreed. You’re right. I should’ve edited that away. It’s just how I gradually answer a comment, and if I switched tasks in between I sometimes forget to go back and re-read and revise the entire comment. My apologies!

amateescu’s picture

Status: Needs work » Closed (won't fix)
jibran’s picture

wim leers’s picture

Title: $term->parent always returns a NULL value, even when the term has parents » Introduce temporary work-around to make $term->parent work in REST API + JSON API in Drupal 8.5 — 8.6 ships with root cause fix
Status: Closed (won't fix) » Needs work

@jibran: yes, indeed, and @amateescu asked the same question at #2543726-341: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration. We'll have to be VERY careful to ensure it behaves exactly the same though. We'd need this to work exactly the same as 8.6.

Marking NW because #11 does not include the same test coverage that #2543726: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration committed to 8.6.

wim leers’s picture

Status: Needs work » Closed (won't fix)

Just chatted with @damainkloip about this.

We agreed that it's probably better to not try to land this issue in Drupal 8.5, because:

  1. The general recommendation from #2543726 is to just apply the latest patch to Drupal 8.5, site owners will simply be able to drop that patch once they update to Drupal 8.6 (see #2543726-339: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration). The benefit of applying that patch is that it also works for other components in the API-First ecosystem: https://www.drupal.org/project/jsonapi and https://www.drupal.org/project/graphql. If we'd commit a custom temporary normalizer here, then that temporary normalizer would have to be removed if they are using JSON API or GraphQL and want $term->parent to work!
  2. There is significant risk in ensuring that the temporary normalizer this issue would introduce behaves exactly the same. If we get even one detail wrong, we'd be having another BC break to worry about in the update path from 8.5 to 8.6! And even if we do that now, how can we ensure that behavior stays perfectly in sync between 8.5 and 8.6 during the lifetime of 8.5? (Admittedly the risk is lower then, but not zero.)
  3. In general, the effort vs shelf life ratio doesn't look very attractive. Quite a lot of effort, with quite a bit of risk, with quite a bit of ecosystem downsides, whereas "just apply the core patch in 8.5" doesn't involve extra risk nor effort.
amateescu’s picture

#21 seems to imply that this issue will introduce a custom normalizer, but that's not what this patch does. It simply provides an item list class for the parent field which makes its value available to the "outside world", including every other component from the API-first ecosystem (JSON API, GraphQL, whatever..)

Since all the arguments "against" from #21 are about a non-existing thing.. sounds like you need to chat about it again :)

damiankloip’s picture

Indeed - this patch does not add a normalizer of any kind.. yet. I think we were thinking that if we add this, we would need to also add a normalizer (or change how the new list class handles the computed items for no parent) to return values for the parent = 0 case. The patch would currently just return an empty array instead of a value of 0. Which is different to the new behaviour in #2543726: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration.

Otherwise, we shoot ourselves in the foot a little bit and make an additional API BC break for ourselves, if we're not careful.

Thoughts? :)