1

Client has a simple and consistent hierarchical taxonomy.

Cities at the top level and Schools at the second level.

.
├── City1
│   ├── School1
│   ├── School2
│   └── School5
└── City2
    ├── School3
    └── School4

The User account entity bundle contains an Entity Reference field to the School taxonomy terms.

I am trying to enforce a constraint to prevent creating or saving a user account if the term selected is at the top level of the hierarchy (and hence a City, not a School).

Here's some example code, skipping the dependency injection of $this->entityTypeManager for brevity.

TermParentConstraint.php:

<?php

namespace Drupal\entity_validation_examples\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * Prevent account creation if School taxonomy term has no parent.
 *
 * @Constraint(
 *   id = "TermParent",
 *   label = @Translation("Prevent account creation if term has no parent", context = "Validation"),
 *   type = "entity"
 * )
 */
class TermParentConstraint extends Constraint {

  /**
   * Message shown when trying to create account if School has no parent City.
   *
   * @var string
   *   School message.
   */
  public $schoolMessage = 'You must select both a City and a School.';

}

TermParentConstraintValidator.php:

<?php

namespace Drupal\entity_validation_examples\Plugin\Validation\Constraint;

use Drupal\user\Entity\User;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * Validates the TermParent constraint.
 */
class TermParentConstraintValidator extends ConstraintValidator {

  /**
   * {@inheritdoc}
   */
  public function validate($entity, Constraint $constraint) {
    if (!isset($entity)) {
      return;
    }

    if ($entity->getEntityTypeId() == 'user') {

      $school = $entity->get('field_select_a_school')->getValue();

      // Orphaned school taxonomy terms must be Cities, not Schools.
      $parent = \Drupal::entityTypeManager()
        ->getStorage('taxonomy_term')
        ->loadParents($school[0]['target_id']);
      if (empty($parent)) {
        $this->context->addViolation($constraint->schoolMessage);
      }
    }
  }

}

Expected behavior: When I create a new user account and select City1 as the value of School, account creation should fail and an error message should be displayed.

Actual behavior: When I create a new user account and select City1 as the value of School, account creation succeeds and no error message is displayed.

Drupal Watchdog logs contain some errors, but I am not sure they are relevant:

 drush ws
 --------- -------------- ------ ---------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
  ID        Date           Type   Severity   Message                                                                                                                                                                                       
 --------- -------------- ------ ---------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
  5298867   18/Oct 22:57   php    Warning    Warning: Illegal offset type in isset or empty in Drupal\Core\Entity\EntityStorageBase->load() (line 246 of /app/docroot/core/lib/Drupal/Core/Entity/EntityStorageBase.php) #0 /app/docroot/  
  5298866   18/Oct 22:57   php    Notice     Notice: Array to string conversion in Drupal\Core\Entity\EntityStorageBase->buildCacheId() (line 126 of /app/docroot/core/lib/Drupal/Core/Entity/EntityStorageBase.php) #0 /app/docroot/core  
  5298865   18/Oct 22:57   php    Warning    Warning: array_flip(): Can only flip STRING and INTEGER values! in Drupal\Core\Entity\EntityStorageBase->loadMultiple() (line 261 of /app/docroot/core/lib/Drupal/Core/Entity/EntityStorageB  
  5298864   18/Oct 22:57   php    Notice     Notice: iconv(): Wrong charset, conversion from `HTML-ENTITIES' to `UTF-8' is not allowed in twig_convert_encoding() (line 1009 of /app/vendor/twig/twig/src/Extension/CoreExtension.php) #0  
  5298863   18/Oct 22:57   php    Notice     Notice: iconv(): Wrong charset, conversion from `HTML-ENTITIES' to `UTF-8' is not allowed in twig_convert_encoding() (line 1009 of /app/vendor/twig/twig/src/Extension/CoreExtension.php) #0  
  5298862   18/Oct 22:57   php    Notice     Notice: iconv(): Wrong charset, conversion from `HTML-ENTITIES' to `UTF-8' is not allowed in twig_convert_encoding() (line 1009 of /app/vendor/twig/twig/src/Extension/CoreExtension.php) #0  
  5298861   18/Oct 22:57   php    Notice     Notice: iconv(): Wrong charset, conversion from `HTML-ENTITIES' to `UTF-8' is not allowed in twig_convert_encoding() (line 1009 of /app/vendor/twig/twig/src/Extension/CoreExtension.php) #0  
  5298860   18/Oct 22:57   php    Notice     Notice: iconv(): Wrong charset, conversion from `HTML-ENTITIES' to `UTF-8' is not allowed in twig_convert_encoding() (line 1009 of /app/vendor/twig/twig/src/Extension/CoreExtension.php) #0  
  5298859   18/Oct 22:57   php    Notice     Notice: iconv(): Wrong charset, conversion from `HTML-ENTITIES' to `UTF-8' is not allowed in twig_convert_encoding() (line 1009 of /app/vendor/twig/twig/src/Extension/CoreExtension.php) #0  
  5298858   18/Oct 22:57   php    Notice     Notice: iconv(): Wrong charset, conversion from `HTML-ENTITIES' to `UTF-8' is not allowed in twig_convert_encoding() (line 1009 of /app/vendor/twig/twig/src/Extension/CoreExtension.php) #0  
 --------- -------------- ------ ---------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 

I've been seeing the iconv() errors since upgrading this project from Drupal 8 to Drupal 9, so I do not think they are relevant. The top three error messages here may be related to the constraint validation, but they are also rather common errors in this project (and debugging them is a much lower priority than essential functionality due very soon).

Any ideas? Am I at least on the right track?

3 Answers 3

1

The problem is you are loading parents in an array and then check for an empty value. Terms at the top level can have a root parent (ID=0), so this would be an item in the array as well, even if not pointing at an existing term.

You can try to simplify the code, since D8.6 you don't need to use the term storage anymore to handle the parent field. empty() on the target ID catches NULL for an empty field and 0 for the root parent.

 public function validate($entity, Constraint $constraint) {
    if (!isset($entity)) {
      return;
    }

    if ($entity->getEntityTypeId() == 'user') {
      $term = $entity->get('field_select_a_school')->entity;
      if ($term) {
        if (empty($term->parent->target_id)) {
          $this->context->addViolation($constraint->schoolMessage);
        }
      }
    }

  }
1

The loadParents() method calls loadMultiple(), and that is where the array_flip() error is happening:

Warning: array_flip(): Can only flip STRING and INTEGER values! in Drupal\Core\Entity\EntityStorageBase->loadMultiple() (line 261 of /app/docroot/core/lib/Drupal/Core/Entity/EntityStorageB

So most likely something going on there.

The loadParents() method is different depending on your Drupal version.

1
  • Thanks for the tip! Here's partial drush status: Drupal version: 9.2.7 Database: Connected Drupal bootstrap: Successful Default theme: projectname Admin theme: projectname_admin Drush version: 10.6.0 Install profile: standard Drupal root: /app/docroot Oct 19, 2021 at 3:09
0

In this case, it turns out that the quickest solution is to configure the Simple Hierarchical Select widget correctly. (The SHS module is already installed, so I may as well use it!)

For the record, this is the problematic widget configuration.

BEFORE:

Checkbox labeled "Force selection of deepest level" unchecked

AFTER:

Checkbox labeled "Force selection of deepest level" checked

Thanks for all the answers, everybody! I learned quite a bit about constraint validation and hierarchical taxonomy, which will help me next time I have to deal with taxonomy trees or implement a ConstraintValidator class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.