Problem/Motivation

Surprisingly I did not found a pre-existing issue for this: https://www.drupal.org/project/issues/drupal?text=parent+target_bundles&....

Quoting #2977879: Regression in #2940339: when multiple vocabularies exist, normalization of Terms fails:

Discovered via #2977848: Update to JSON API 1.20.

#2940339: Port reference field support for non-empty entity reference fields not pointing to an entity from #2543726 added this:

    if ($target_entity === NULL) {
      $host_entity = $parent->getHostEntity();
      $relatable_resource_types = $resource_type_repository->get(
        $host_entity->getEntityTypeId(),
        $host_entity->bundle()
      )->getRelatableResourceTypes()[$parent->getPropertyName()];
      if (count($relatable_resource_types) !== 1) {
        throw new \RuntimeException('Relationships to virtual resources are possible only if a single resource type is relatable.');
      }

Having multiple vocabularies causes $relatable_resource_types['parent'] to have multiple resource types. But why?

Turns out that the parent field definition does NOT restrict the parent to be in the same bundle. That means that the Politics term from the Tags vocabulary could have BMW from the Cars vocabulary as its parent term. This of course makes zero sense.

Proposed resolution

Implement \Drupal\taxonomy\Entity\Term::bundleFieldDefinitions(), much like \Drupal\comment\Entity\Comment::bundleFieldDefinitions().

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None. Except that Term entity type's parent field definition is now more precise, and doesn't allow nonsensical/forbidden values anymore!

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Active » Needs review
StatusFileSize
new940 bytes
wim leers’s picture

This inaccuracy in Typed Data metadata results in the latest JSON API release being unable to normalize terms when there are multiple vocabularies on a site. So I ported #2 over to JSON API as a work-around until this lands: #2977879-5: Regression in #2940339: when multiple vocabularies exist, normalization of Terms fails.

Status: Needs review » Needs work

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

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new698 bytes
new951 bytes

This must be one of the most obscure failures I've ever seen, along with one of the silliest fixes.

This is why I like having assert()s all over, along with strictly typed objects. This is super painful to debug.

wim leers’s picture

The #5 interdiff is wrong. This is the interdiff:

+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -179,6 +179,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
-    $fields['parent']->setSetting('handler_settings', ['target_bundles' => [$bundle]]);
+    $fields['parent']->setSetting('handler_settings', ['target_bundles' => [$bundle => $bundle]]);
jibran’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

Fix looks fine to me. Let's add some test if possible.

wim leers’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new999 bytes
new1.87 KB

Done! Failing test-only patch is also the interdiff.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, for updating the patch

The last submitted patch, 8: 2977882-8-tests_only_FAIL.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • catch committed e60979f on 8.6.x
    Issue #2977882 by Wim Leers: Term entity type's "parent" field does not...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed e60979f and pushed to 8.6.x. Thanks!

wim leers’s picture

🎉

jibran’s picture

Status: Fixed » Needs work
Issue tags: +Needs upgrade path, +Needs upgrade path tests
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -179,6 +179,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields['parent']->setSetting('handler_settings', ['target_bundles' => [$bundle => $bundle]]);

Sorry, I forgot about it. ::bundleFieldDefinitions is only called when a bundle is created so we need an upgrade path, to add this setting, and upgrade path test, to verify this setting, for the existing parent field for all the existing vocabularies.

jibran’s picture

I think we should revert the commit because we are fixing the bug for new vocabularies not for all existing vocabularies. Sorry, @Wim Leers!

wim leers’s picture

Status: Needs work » Active

@jibran AFAICT that's not true. I could reproduce the bug with Drupal core's tags vocabulary and simply applying this patch and clearing caches fixed it.

Looking at the caller of ::bundleFieldDefinitions() seems to confirm this — \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions() does this:

      $cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->getId();
      if ($cache = $this->cacheGet($cid)) {
        $bundle_field_definitions = $cache->data;
      }
      else {
        // Rebuild the definitions and put it into the cache.
        $bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle, $base_field_definitions);
        $this->cacheSet($cid, $bundle_field_definitions, Cache::PERMANENT, ['entity_types', 'entity_field_info']);
      }

And $this->buildBundleDefinitions() does this:

    // Allow the entity class to provide bundle fields and bundle-specific
    // overrides of base fields.
    $bundle_field_definitions = $class::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions);
jibran’s picture

Assigned: Unassigned » berdir

Let's ask @Berdir.

amateescu’s picture

Assigned: berdir » Unassigned
Status: Active » Fixed
Issue tags: -Needs upgrade path, -Needs upgrade path tests

@Wim Leers is right in #16, bundle fields are computed on demand and cached, they are not only added when a new bundle is created.

jibran’s picture

@Wim Leers is right in #16, bundle fields are computed on demand and cached, they are not only added when a new bundle is created.

At least we need an update hook to clear the cache.

So I think moral here is as long as you are updating the field definition or settings of an existing bundle field in either hook_entity_bundle_field_info or $class::bundleFieldDefinitions clear the cache will update the FieldDefinitions but if you are creating a new field(computed or otherwise) in either hook_entity_bundle_field_info or $class::bundleFieldDefinitions you need to either implement hook_entity_bundle_create or update hook to fire \Drupal\Core\Field\FieldDefinitionListener::onFieldDefinitionCreate for example what entity_schema_test_entity_bundle_create is doing.

wim leers’s picture

Thanks, @amateescu! But also thanks, @jibran, for asking the right critical questions :)

At least we need an update hook to clear the cache.

Hm, do we? I forget, but I thought that every update of Drupal core already would invalidate all caches.

jibran’s picture

Hm, do we? I forget, but I thought that every update of Drupal core already would invalidate all caches.

if that is true then why do we have so many empty update hooks :D

jibran’s picture

Status: Fixed » Needs work
Issue tags: +Needs upgrade path

We do need an update hook to clear the entity_bundle_field_definitions:* cache.

jibran’s picture

Status: Needs work » Needs review
Issue tags: -Needs upgrade path
StatusFileSize
new560 bytes

Here we go.

jibran’s picture

StatusFileSize
new549 bytes
new528 bytes

Better docs.

jibran’s picture

StatusFileSize
new586 bytes
wim leers’s picture

Status: Needs review » Reviewed & tested by the community

  • catch committed bc2d4bf on 8.6.x
    Issue #2977882 by Wim Leers, jibran, catch: Term entity type's "parent"...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed bc2d4bf and pushed to 8.6.x. Thanks!

jibran’s picture

As per https://www.drupal.org/core/backport-policy, @catch can you please state your reason to not backport this to 8.5.x? I don't think this is disruptive at all.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

pwolanin’s picture

Version: 8.6.x-dev » 8.5.x-dev
Status: Closed (fixed) » Patch (to be ported)

Should be backported since it breaks jsonapi

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

larowlan’s picture

Status: Patch (to be ported) » Fixed

8.5 is now security only

wim leers’s picture

Should be backported since it breaks jsonapi

Besides, JSON API already backported this:

/**
 * Implements hook_entity_bundle_field_info().
 */
function jsonapi_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields = [];

  if (floatval(\Drupal::VERSION) < 8.6 && $entity_type->id() == 'taxonomy_term') {
    // Only terms in the same bundle can be a parent.
    $fields['parent'] = clone $base_field_definitions['parent'];
    $fields['parent']->setSetting('handler_settings', ['target_bundles' => [$bundle]]);
    return $fields;
  }

  return $fields;
}

That's why I forgot this was even still open. Thanks for the attempt though, @pwolanin :)

@larowlan++

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.