Problem/Motivation

In order to implement the idea in #2976148-5: Layout-based entity rendering should delegate to the correct section storage instead of hardcoding to either defaults or overrides, it needs to be possible to validate an entity context by checking if it has a specific field attached to it.

Proposed resolution

Create a new EntityField validation constraint, which checks if a fieldable entity has a given field attached.

Remaining tasks

Write the patch, write tests, review, commit.

User interface changes

None.

API changes

A new validation constraint will be added to Drupal core.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

phenaproxima created an issue. See original summary.

phenaproxima’s picture

Status: Active » Needs review
Issue tags: +Needs tests
FileSize
2.67 KB

Here's an initial patch without tests (yet), to validate (hah!) the approach.

phenaproxima’s picture

Status: Needs review » Needs work

Back to NW for tests.

phenaproxima’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
6 KB
3.13 KB

And now with a test!

amateescu’s picture

Here's a quick review :)

  1. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityFieldConstraint.php
    @@ -0,0 +1,56 @@
    + *   id = "EntityField",
    

    How about renaming this to EntityHasField?

  2. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityFieldConstraint.php
    @@ -0,0 +1,56 @@
    + *   type = { "entity", "entity_reference" },
    

    Not sure why we need "entity_reference" in there, this constraint doesn't seem to do anything with references.

  3. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityFieldConstraint.php
    @@ -0,0 +1,56 @@
    +  public function getFieldOption() {
    +    // Support passing the field name as string, but force it to be an array.
    +    return (array) $this->field_name;
    +  }
    

    If the output is an array, shouldn't the method name be getFieldOptions()?

  4. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityFieldConstraintValidator.php
    @@ -0,0 +1,30 @@
    +    if (!($entity instanceof FieldableEntityInterface)) {
    +      return;
    +    }
    

    Shouldn't we expose this state as a constraint violation? Something like "The entity is not fieldable."..

  5. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldConstraintValidatorTest.php
    @@ -0,0 +1,103 @@
    +   * @param string|string[] $field
    ...
    +  public function testValidation($field, array $expected_violations) {
    ...
    +      ->addConstraint('EntityField', $field);
    

    Let's rename this argument to $field_names and always pass an array :)

phenaproxima’s picture

Sam152’s picture

Looks great, this is all I got:

  1. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityHasFieldConstraint.php
    @@ -0,0 +1,63 @@
    +  /**
    +   * The field name option.
    +   *
    +   * @var string|array
    +   */
    +  public $field_name;
    ...
    +  public function getFieldOptions() {
    +    // Support passing the field name as string, but force it to be an array.
    +    return (array) $this->field_name;
    +  }
    

    I think the feedback was intended as: lets update the constraint to only accept a list of fields instead of both. If we do indeed support both a string and a list, we should have coverage for it.

    If we do take that direction though, EntityHasFields is probably a better name. I question the need for this at all though, surely it's trivial to add multiple constraints to something in pretty much all contexts where they are used?

  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityHasFieldConstraintValidatorTest.php
    @@ -0,0 +1,123 @@
    +/**
    + * Tests validation constraints for EntityHasFieldConstraintValidator.
    + *
    

    I like connecting dots here with a @coversDefaultClass, which pretty much makes the comment redundant.

  3. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityHasFieldConstraintValidatorTest.php
    @@ -0,0 +1,123 @@
    +    $node = $this->container->get('entity_type.manager')
    +      ->getStorage('node_type')
    +      ->create(['type' => 'article']);
    

    Super nit, this variable is probably more accurate as $node_type. Also any reason not to simply NodeType::create()?

amateescu’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityHasFieldConstraintValidator.php
    @@ -0,0 +1,31 @@
    +    if (!($entity instanceof FieldableEntityInterface)) {
    

    We should have an early return before this check, something like:

        if (!isset($entity)) {
          return;
        }
    
  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityHasFieldConstraintValidatorTest.php
    @@ -0,0 +1,123 @@
    +    $node = $this->container->get('entity_type.manager')
    +      ->getStorage('node')
    +      ->create(['type' => 'article']);
    

    We could use NodeCreationTrait and $this->createNode() here :)

  3. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityHasFieldConstraintValidatorTest.php
    @@ -0,0 +1,123 @@
    +    $node = $this->container->get('entity_type.manager')
    +      ->getStorage('node_type')
    +      ->create(['type' => 'article']);
    

    Related to @Sam152's point above, we should use $this->createContentType() instead.

I'm also wondering about #7.1: does this constraint really need the ability to handle multiple field names?

phenaproxima’s picture

Addressed all feedback from #7 and #8. I removed support for multiple fields, since I agree that it's probably superfluous (at least until someone asks for it).

Status: Needs review » Needs work

The last submitted patch, 9: 2976356-9.patch, failed testing. View results

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
6.24 KB
781 bytes

Whoops. Had the silly type hint wrong.

Status: Needs review » Needs work

The last submitted patch, 11: 2976356-11.patch, failed testing. View results

gawaksh’s picture

This patch would solve the issue.

gawaksh’s picture

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
6.25 KB
603 bytes

This oughta do the trick...

Status: Needs review » Needs work

The last submitted patch, 15: 2976356-15.patch, failed testing. View results

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
6.26 KB
692 bytes

Your move, Drupal CI.

phenaproxima’s picture

Title: Add a validation constraint to check if an entity has field(s) » Add a validation constraint to check if an entity has a field
Issue summary: View changes
phenaproxima’s picture

Issue tags: +blocker

This issue may block #2976148: Layout-based entity rendering should delegate to the correct section storage instead of hardcoding to either defaults or overrides. I won't postpone it just yet, but I think it's worth tagging it as such.

amateescu’s picture

The patch looks much better now. Just a little note about the test coverage:

+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityHasFieldConstraintValidatorTest.php
@@ -0,0 +1,106 @@
+    // Create a typed data definition with an EntityHasField constraint.
+    $definition = DataDefinition::create('entity_reference')
+      ->addConstraint('EntityHasField', $field_name);
...
+    // Create a typed data definition with an EntityHasField constraint.
+    $definition = DataDefinition::create('entity_reference')
+      ->addConstraint('EntityHasField', 'body');

It's a bit weird to add this constraint to a data definition of an entity reference field, isn't the constraint supposed to live on the entity type level?

I would suggest to follow the same approach as \Drupal\KernelTests\Core\Entity\EntityTypeConstraintsTest for testing this, and use the existing entity_test_constraints module :)

phenaproxima’s picture

Okay, rewrote the test. This is definitely cleaner and more extensive test coverage. Thanks, @amateescu!

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

That looks much better indeed!

phenaproxima’s picture

Realized this will probably need a CR, so I wrote one: https://www.drupal.org/node/2980591

catch’s picture

Should this also check for the field type? Or if not, what is going to check that?

Also this seems like it could potentially help with #2981835: Forums hard-codes the comment_forum comment field, but doesn't validate it.

phenaproxima’s picture

Should this also check for the field type? Or if not, what is going to check that?

I think that checking the field type would be the kind of thing we could address in a follow-up. Right now, the constraint is meant to be very simple, and it assumes you know the name of the field to check for, and that you know the expected type already. So I think that we could open an issue to extend this constraint to implement a field type check.

The use-case that gave rise to this issue was from Layout Builder -- it needs to be able to filter plugin definitions by a context definition, and that context definition must match any entity which has a specific field which has a specific, hard-coded name. Therefore, to fulfill that use case, we only need to check for the field itself; we already know what type it will be, if it exists.

phenaproxima’s picture

Opened a follow-up to add type checking to the constraint: #2982184: Add field type checking to EntityHasField constraint.

catch’s picture

Status: Reviewed & tested by the community » Fixed

i'm wasn't 100% on committing this without field type checking, but the layout example uses a very clearly namespaced field, and we already have issues with forum module relying on a field name and doing no checking at all.

So this is an improvement, and phenaproxima updated the change record to note the limitations.

Committed 28be9f7 and pushed to 8.6.x. Thanks!

  • catch committed 944203f on 8.6.x
    Issue #2976356 by phenaproxima, amateescu, Sam152: Add a validation...

Status: Fixed » Closed (fixed)

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