Problem/Motivation

This issue was originally Comment-specific. But further investigation has revealed that the problem is not specific to comments. It's a general Entity/Field API problem. Entity constraints are validated before field constraints. See #12 and later.

Still, the Comment use case is a useful guide through the discovered problems:

While working on #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method, it took me hours to figure out what the minimal set of fields is that one must send for a Comment entity to be successfully created. See #2737719-75: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method.

In doing so, I had to do extensive debugging using Xdebug to figure out the incredibly obtuse error responses, which are categorically HTTP 500 responses instead of 422 responses. And not only that, but the message in each of those responses is absolutely worthless. It doesn't give any indication about what is wrong.

AFAICT key problems are:

  1. \Drupal\comment\CommentStatistics::update() depends on entity_type, which is neither required nor validated
  2. CommentNameConstraintValidator depends on entity_id and field_name, but runs before either of those can even possibly be validated (entity_id is required, but that's not validated before CommentNameConstraint is validated)
  3. Both of these result in failures outside of what the Entity API validation system is designed for, hence resulting in undecipherable errors in REST responses

Another use case was reported by @joachim in #59+#61, this time the problem is not "entity constraint validated before field constraint", but "field constraint validated before property constraint":

Over at Duration Field module, we have a field type with two properties:

- duration, which is a PHP duration string such as 'P1M1D'. This uses a custom data type.
- seconds, which is an integer count of seconds.

The duration data type has a constraint to check its value is a correctly-formed PHP duration string. So '1D' is not valid (must start with 'P'), 'P1X' is not valid ('X' is not a valid interval letter), 'cake' is not valid for obvious reasons. Setting it as deeply as on the data type makes sense, because the data type should enforce this wherever it's used.

I wanted to add a constraint to the field item as a whole, to enforce that the durations and the seconds values both match. In other words, prevent a field being saved with something like 'P1D' for the duration and '60' for the seconds (one day is not 60 seconds long!)

However, the constraint on the field item runs BEFORE the constraint on the duration property. So the field item constraint might run with badly-formed values for the duration property! It would therefore need to perform its OWN check on the format of the duration value, which is just repeating the work that the duration data type constraint is going to do again later on.

More generally, this problem will occur anytime you have a compound field type where:

- one or more of the field's properties properties need validation for their value alone
- the whole field item needs validation as a whole

Proposed resolution

Unknown, see comments.

In both use cases above, the observed pattern is: the more granular thing should be validated before the more coarse thing, because otherwise the data in the more coarse thing cannot be assumed to be valid, thus invalidating its validation 🤓

Remaining tasks

  1. Make Comment's entity_type base field is required: #2885809: The 'entity_type' and 'field_name' base fields on Comment are required
  2. Make Comment's entity_type base field have a validation constraint, to ensure a valid entity type is set. Test coverage must include a non-existing entity type to ensure good DX.
  3. Also ensure that \Drupal\Tests\rest\Functional\EntityResource\Comment\CommentResourceTestBase::testPostDxWithoutCriticalBaseFields() is no longer sending an Accept request header.
  4. … more, but yet TBD!

User interface changes

TBD

API changes

TBD

Data model changes

TBD

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Title: Forgetting comment base fields 'entity_type', 'entity_id' and 'field_name' leads to impossible to debug error responses » [PP-1] Forgetting comment base fields 'entity_type', 'entity_id' and 'field_name' leads to impossible to debug error responses
Status: Active » Postponed

Note that #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method introduced test coverage to show the current pain, and show the responses that should be sent.

Postponing on that landing.

wim leers’s picture

Title: [PP-1] Forgetting comment base fields 'entity_type', 'entity_id' and 'field_name' leads to impossible to debug error responses » Forgetting comment base fields 'entity_type', 'entity_id' and 'field_name' leads to impossible to debug error responses
Status: Postponed » Active
wim leers’s picture

The assertions introduced by #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method to verify that this is indeed broken on all PHP versions was itself broken, due to behavioral differences between PHP 5.5, 5.6 and 7.0. That resulted in failing tests on 5.6. Hence there's a critical issue to make the assertion of fatal failure work on 5.6: #2832013: CommentResourceTestBase::testPostDxWithoutCriticalBaseFields() always fails on PHP 5.6 & MySQL 5.5 (works fine on other PHP versions).

Now working on this fix, so we can get rid of that "fatal failure assertion" entirely.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new2.04 KB

So, this is the goal. But in order for this patch to be green, we'll need to fix the underlying bug.

wim leers’s picture

StatusFileSize
new3.34 KB
new2.39 KB

That is the most nasty one that we need to fix (per #2832013: CommentResourceTestBase::testPostDxWithoutCriticalBaseFields() always fails on PHP 5.6 & MySQL 5.5 (works fine on other PHP versions)), but there's actually more to fix.

This is the complete goal.

The last submitted patch, 5: 2820364-5.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 6: 2820364-6.patch, failed testing.

wim leers’s picture

The first of those (forgetting to specify entity_type) results in a failure at

$this->entityManager->getStorage($comment->getCommentedEntityTypeId())->resetCache(array($comment->getCommentedEntityId()));

Because $comment->values['entity_type']['x-default'] === [], which results in getCommentedEntityTypeId() === NULL, which then results in \Drupal\Core\Entity\EntityTypeManager::getDefinition() throwing a PluginNotFoundException.

But, note that that code is called by \Drupal::service('comment.statistics')->update($this);(), which is called from \Drupal\comment\Entity\Comment::postSave(), i.e. after the Comment entity was already saved! So, frighteningly, setting no entity_type value throws no exception whatsoever! Fortunately, \Drupal\Core\Entity\Sql\SqlContentEntityStorage::save() then rolls back the DB transaction. So, no harm is done. It throws an EntityStorageException with the message The "" entity type does not exist..

That EntityStorageException is caught by EntityResource::post(), which converts it to a 500 response:

    catch (EntityStorageException $e) {
      throw new HttpException(500, 'Internal Server Error', $e);
    }

Next: figuring out a solution for this.

wim leers’s picture

You can test this locally by importing this config:

uuid: adde12d4-5598-41bc-8136-0e54a161110b
langcode: en
status: true
dependencies:
  module:
    - comment
id: entity.comment
plugin_id: 'entity:comment'
granularity: resource
configuration:
  methods:
    - GET
    - POST
    - PATCH
    - DELETE
  formats:
    - json
    - hal_json
  authentication:
    - basic_auth
    - cookie

and running this CURL command:

curl -X POST -u root:root -H "Content-Type: application/json" -d '{"comment_type":[{"target_id":"comment"}],"entity_id":[{"target_id":"1"}],"field_name":[{"value":"comment"}],"subject":[{"value":"Dramallama"}],"comment_body":[{"value":"Llamas are awesome.","format":"plain_text"}]}' http://d8/entity/comment?_format=json

which should result in this output:

{"message":"A fatal error occurred: Internal Server Error"}
wim leers’s picture

Issue summary: View changes
StatusFileSize
new4.45 KB
new2.2 KB

Making the entity_type base field required already helps. The first assertion now works.

But… it's not enough. Making a field required does not ensure it contains a valid value yet. And unfortunately, that's exactly the problem: if we now pass a non-existing entity_type, it will still fail:

$ curl -X POST -u root:root -H "Content-Type: application/json" -d '{"comment_type":[{"target_id":"comment"}],"entity_id":[{"target_id":"1"}],"field_name":[{"value":"comment"}],"subject":[{"value":"Dramallama"}],"comment_body":[{"value":"Llamas are awesome.","format":"plain_text"}],"entity_type":[{"value":"foobar"}]}' http://d8/entity/comment?_format=json
{"message":"A fatal error occurred: Internal Server Error"}

Again because of a PluginNotFoundException (because the foobar entity type plugin does not exist), which is converted to a EntityStorageException et cetera.

wim leers’s picture

Assigned: wim leers » Unassigned
Issue tags: +SymfonyWTF

Moving on to the next one: missing entity_id.

$ curl -X POST -u root:root -H "Content-Type: application/json" -d '{"comment_type":[{"target_id":"comment"}],"field_name":[{"value":"comment"}],"subject":[{"value":"Dramallama"}],"comment_body":[{"value":"Llamas are awesome.","format":"plain_text"}],"entity_type":[{"value":"node"}]}' http://d8/entity/comment?_format=json

We've got a serious infrastructure problem here…

  1. entity_id is already marked as required. But, unfortunately, the CommentNameConstraint (introduced in #2002158: Convert form validation of comments to entity validation, with additional test coverage in CommentValidationTest) constraint runs first. And it uses entity_id (as well as field_name). But at this point, no validation has run yet to verify that entity_id is set! So, it's pretty much completely pointless that entity_id is required, because you could never ever get the corresponding validation error (entity_id: This value should not be null), since the CommentName validation error will always be shown instead, since its constraint validator runs first.
  2. AFAICT, the entity validation system is specifically designed to validate from the top down. So, first it validates entity-level constraints, then field-level constraints, etc.
  3. We can't "move down" the CommentName constraint, because it covers multiple fields: name and uid. So we can't move it to the field level instead of the entity level.

So… how to fix this? The only thing I can think of is to expand what CommentNameConstraintValidator validates: let it also validate the entity_id and field_name fields. But the right solution would be to let CommentNameConstraintValidator somehow indicate it requires the entity_id and field_name fields to already be validated before it can run, because those being valid is a hard requirement.

Unassigning, this needs a Symfony 2 Validation component expert.

wim leers’s picture

Title: Forgetting comment base fields 'entity_type', 'entity_id' and 'field_name' leads to impossible to debug error responses » Forgetting comment base fields 'entity_type', 'entity_id' and 'field_name' leads to impossible to debug error responses due to CommentStatistics::update() and CommentNameConstraintValidator
Priority: Normal » Major
Issue summary: View changes
tstoeckler’s picture

the entity validation system is specifically designed to validate from the top down.

Well it does that, simply because Symfony's Constraint Iteration does it that way. It does seem like a pretty severe issue, and I do think we should at least consider turning that around. Not sure if that's possible with BC, though.

Will look at the patch in detail tomorrow.

wim leers’s picture

I don't think we need to turn it around!

I think it needs to be possible to express dependencies. And in fact, we already have much of the necessary infrastructure!

\Drupal\comment\Plugin\Validation\Constraint\CommentNameConstraint contains this:

  public function coversFields() {
    return ['name', 'uid'];
  }

What we need on top of that is ConstraintDependencyInterface (better name TBD), which allows that class to also specify:

  public function dependencies() {
    return ['entity_id', 'field_name'];
  }

Or perhaps this belongs in the @Constraint annotation:

 * @Constraint(
 *   id = "CommentName",
 *   label = @Translation("Comment author name", context = "Validation"),
 *   type = "entity:comment",
 *   dependencies = {
 *     "entity_id",
 *     "field_name",
 *   },
 * )

That would ensure that when validating the CommentNameConstraint, we'd first run any constraints for those lower levels (fields). Which could absolutely be a pure API addition, without BC break.

tstoeckler’s picture

Issue tags: +dcmuc16
dawehner’s picture

StatusFileSize
new6.67 KB

Well it does that, simply because Symfony's Constraint Iteration does it that way. It does seem like a pretty severe issue, and I do think we should at least consider turning that around. Not sure if that's possible with BC, though.

Isn't the patch below enough to turn around the validation process to go deep into the tree first, before validating entries at the upper level?

tstoeckler’s picture

Yes, I think so. If that's not considered an API change, I would be fine with that.

The most prominent entity-level validator we have is EntityChangedConstraintValidator so with this all field-level validation would run for entities with changed-tracking only to then realize that regardless of any violations the entity cannot be saved anyway because it has been changed in the meantime.

I guess that's not really a problem, because the 80%-case is that there is no changed violation, so that the field-level validators are run anyway, but I'm also neither an expert in nor a heavy user of the validation system, so I'm not sure if there are other cases that I'm missing.

wim leers’s picture

fago’s picture

Status: Needs work » Needs review

The current processing order is something we just inherited from symfony, it wasn't done by any purpose. Thinking about it, I'd agree that a dept-first approach makes more sense. So the individual fields are covered first and combined-validation second.

However, the changed validation is a good example that this ordering is not the perfect fit always. It would be nice if one could prioritize EntityChanged validation and run others second. We might be able to use the validation groups feature for that - right now we do not use them. E.g. we could define some "Priority" validation group and run constraints assigned to this group first. See https://symfony.com/doc/current/validation/groups.html for more about groups.

Still, that's an optimization which would be a nice to have, but does not seem to be really necessary. That said, I think a good way to move forward here is to turn validation order around and to create a follow-up feature request for the priority validation group.

Speaking of API changes, I think this it's ok to do. The traversing order is something not documented, any nothing should rely on it. So what can and will change in some situations, is the order of the violations received - but as this issue shows, the depth first traversal should lead to the better violation ordering in *most cases*.

Setting to needs review, to see what the test bot says.

Status: Needs review » Needs work

The last submitted patch, 17: 2820364-17.patch, failed testing.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

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

wim leers’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new11.54 KB
new4.75 KB

@fago: thanks for the review!

Only 6 failures. That means this is causing few regressions, if any, and it's probably feasible.

Now let's see if can help us fix the terrible Comment POSTing DX. As I explained in #11, making entity_type required allows us to pass the first test in testPostDxWithoutCriticalBaseFields(). The second test is testing a missing entity_id. And that's what I pointed out in my analysis in #12 as being the one to suffer from the current validation order (top-down instead of bottom-up).

Unfortunately, that's not what happens. The patch in #17 does result in a "This value should not be NULL" (aka required field has no value) constraint violation message for entity_id. But … it still allows CommentNameConstraintValidator to continue, despite that needing entity_id to have a correct value. This makes me suspect that in this Symfony Validator component, there's no concept of "only execute this constraint validator if all prior/basic ones did not complain"?

But then if I go and look at the docs, they do support this: http://symfony.com/doc/2.8/validation/sequence_provider.html seems a perfect fit, or otherwise http://symfony.com/doc/2.8/validation/groups.html. Entity validation experts, back to you!


In this patch, I:

  1. bring back the changes from my earlier patches to make entity_type a required base field; this allows me to make the first test in testPostDxWithoutCriticalBaseFields() to pass again. This does NOT exercise the changes proposed by @dawehner.
  2. now also update the second test in testPostDxWithoutCriticalBaseFields() to its desired state. I made this pass by modifying CommentNameConstraintValidator to only run if both entity_id and field_name are not empty (note that this is not sufficient — they should be guaranteed to be valid at this point). The correct solution would be to use a "sequence provider" AFAICT.
  3. The changes in CommentNameConstraintValidator should be reverted.
  4. The patch should have just as many failures as #17 — addressing those failures is not of the utmost importance right now. What matters more, is making sure only the appropriate constraint validators run, and only when necessary.

Status: Needs review » Needs work

The last submitted patch, 23: 2820364-23.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.25 KB
new12.72 KB

This also causes the following change in error response. I'm not sure this is correct/desirable. But that's just a detail in the grand scheme of things here.

Status: Needs review » Needs work

The last submitted patch, 25: 2820364-25.patch, failed testing.

wim leers’s picture

Assigned: Unassigned » fago
wim leers’s picture

amateescu’s picture

+++ b/core/modules/comment/src/Entity/Comment.php
@@ -307,6 +307,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+      ->setRequired(TRUE)

Note that it's not enough to change the base field definition, you also need an update function that does the same thing. See aggregator_update_8200() for an example.

wim leers’s picture

#29: thanks :) But until we have a patch that actually works just for fresh installs, that is not something we need to think about yet! Hopefully we'll be able to write an update hook soon!

dawehner’s picture

+++ b/core/modules/comment/src/Plugin/Validation/Constraint/CommentNameConstraintValidator.php
@@ -52,6 +52,12 @@ public function validate($entity, Constraint $constraint) {
+    // Return early if no entity_id is set; without it, we can't perform an
we can't perform an

... yeah what?

wim leers’s picture

Title: Forgetting comment base fields 'entity_type', 'entity_id' and 'field_name' leads to impossible to debug error responses due to CommentStatistics::update() and CommentNameConstraintValidator » Entity + Field validation constraints are processed in the incorrect order
Issue tags: -Needs framework manager review +Triaged core major
Related issues: +#2885809: The 'entity_type' and 'field_name' base fields on Comment are required

We just discussed this in an Entity/Field API API-First Initiative blocker call. The consensus is that this is major due to the incorrect order in which validation constraints are checked.

However, it's difficult to see what the original problem (wrt Comments) ends and the general problem begins. So it was determined that the first task is to have a separate issue for making the entity_type base field on the Comment entity type required. Did that: #2885809: The 'entity_type' and 'field_name' base fields on Comment are required.

Reading this issue now, that means doing a subset of the patch in #11. But it'll still retain the majority of the original problem. At least it will be slightly smaller.

Also retitling to reflect the intended scope.

wim leers’s picture

Component: comment.module » entity system
Issue summary: View changes
Issue tags: +Security improvements

IS updated.

dawehner’s picture

I tried to understand this change again. Could it be that we need to skip validating in case there has been any errors on a more deeper level to make this fix actually properly?

wim leers’s picture

Yes — if there's an error at the field-level constraints, the entity-level constraints should not be executed, because the entity-level constraints assume valid field values.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new12.68 KB
new4.91 KB

So what about this particular change?

Status: Needs review » Needs work

The last submitted patch, 36: 2820364-36.patch, failed testing. View results

dawehner’s picture

Version: 8.3.x-dev » 8.4.x-dev
Status: Needs work » Needs review

Oops, this is filed agains the wrong version.

Status: Needs review » Needs work

The last submitted patch, 36: 2820364-36.patch, failed testing. View results

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new12.68 KB

... Upload a new patch

Status: Needs review » Needs work

The last submitted patch, 40: 2820364-40.patch, failed testing. View results

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

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

wim leers’s picture

+++ b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php
@@ -134,13 +134,18 @@ protected function validateNode(TypedDataInterface $data, $constraints = NULL, $
+    // Don't validate the current level if there has been any violations on the
+    // deeper levels, as the current level might require for example the
+    // existence of some values.

Great comment.

Might also be good to add a comment to the class-level docblock, explaining that this validates depth-first.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new15.9 KB
new5.53 KB

CommentNameConstraintValidator depends on entity_id and field_name, but runs before either of those can even possibly be validated (entity_id is required, but that's not validated before CommentNameConstraint is validated)

I tried to, but this throws exceptions all over the place.

In the meantime I fixed the failing validation in comment module, ... we no longer need to stop early.

Status: Needs review » Needs work

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

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

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mohit_aghera’s picture

Status: Needs work » Needs review
StatusFileSize
new16.66 KB

Re-rolling patch for 8.6.x branch.
Patch was failing for CommentResourceTestBase.php and UserResourceTestBase.php files.

There are some failures in issue https://www.drupal.org/project/drupal/issues/2580551
I think fixing this may fix failures in https://www.drupal.org/project/drupal/issues/2580551 as well.

Status: Needs review » Needs work

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

mohit_aghera’s picture

Status: Needs work » Needs review
StatusFileSize
new17.63 KB
new1.96 KB

Fixing couple of test case failures.

Status: Needs review » Needs work

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

borisson_’s picture

  1. +++ b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php
    @@ -132,21 +132,26 @@ protected function validateNode(TypedDataInterface $data, $constraints = NULL, $
    +    // Don't validate the current level if there has been any violations on the
    +    // deeper levels, as the current level might require for example the
    +    // existence of some values.
    

    This comment reads weird, but I'm not sure how to improve it either.

  2. +++ b/core/modules/comment/src/Plugin/Validation/Constraint/EntityTypeConstraint.php
    @@ -0,0 +1,26 @@
    +
    +
    

    Should be only one empty line.

  3. +++ b/core/modules/comment/src/Plugin/Validation/Constraint/EntityTypeConstraint.php
    @@ -0,0 +1,26 @@
    +
    +
    

    ^

  4. +++ b/core/modules/comment/src/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
    @@ -0,0 +1,52 @@
    + * Validates the ValidEntityType constraint.
    

    /s/ValidEntityType/EntityTypeConstraint/

  5. +++ b/core/modules/comment/src/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
    @@ -0,0 +1,52 @@
    +class EntityTypeConstraintValidator  extends ConstraintValidator implements ContainerInjectionInterface {
    

    Double space before extends

  6. +++ b/core/modules/comment/src/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
    @@ -0,0 +1,52 @@
    +
    +
    

    Double empty lines.

  7. +++ b/core/modules/comment/src/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
    @@ -0,0 +1,52 @@
    +    if (!$this->entityTypeManager->getDefinition($value)) {
    +      $this->context->addViolation($constraint->message);
    +    }
    

    Should we catch the exception here and add that as a violation instead? Also, are we ok with relying on php's cast of NULL to FALSE here? We could be more explicit by doing if ($this->... === NULL) {}

  8. +++ b/core/tests/Drupal/KernelTests/Core/Entity/FieldWidgetConstraintValidatorTest.php
    @@ -147,7 +147,6 @@ public function testEntityLevelConstraintValidation() {
         $entity->save();
    -
         $errors = $this->getErrorsForEntity($entity);
    

    This is unrelated, I don't really mind, but I think we should remove this.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new20.13 KB
new6.96 KB

Thank you @borisson_ for the review! I fixed your points and started to look into the test failures.

-{"message":"Unprocessable Entity: validation failed.\nfield_dateonly.0: The datetime value must be a string.\nfield_dateonly.0.value: This value should be of the correct primitive type.\n"}
+{"message":"Unprocessable Entity: validation failed.\nfield_dateonly.0.value: This value should be of the correct primitive type.\n"}

This one is hard. As we now validate on the innermost level we stop validation on higher levels. I think its fine to change the expectation.

Status: Needs review » Needs work

The last submitted patch, 52: 2820364-52.patch, failed testing. View results

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

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now 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.

berdir’s picture

Uploaded a patch to #2885809: The 'entity_type' and 'field_name' base fields on Comment are required that works for the existing test coverage for comment.

From the entity API perspective, only very few fields are really critial (bundle, language, ..). I'm not convinced that we need a complex technical solution that has its own drawbacks (e.g., before you got more or less every validation error at once, now it's more likely you need multiple iterations to figure out everything that's wrong).

So I'm not convinced that we need or even can create a generic solution for this. I'd be perfectly happy to just document that specifically validation constraints (but kind of everywhere, because especially entity reference can easily get out of sync as we have no generic system to purge reference data if entities are deleted) should make sure that all the data they require is present and be written in a resilient way. Fixing the one specific validator for comments is trivial as my patch over there shows.

It will never be perfect, it would also be easy to work around the entity type validation here, you just need an invalid entity type/id combination or even entity type + id + field name combination and that validator will result in exceptions.

amateescu’s picture

Agreed with @Berdir here. This change is somewhat hard to achieve, has drawbacks as detailed in #55 and the benefits are therefore questionable.

My opinion is that we should close it as "won't fix".

wim leers’s picture

Priority: Major » Normal
Issue summary: View changes

#2885809: The 'entity_type' and 'field_name' base fields on Comment are required landed!

+++ b/core/modules/comment/src/Entity/Comment.php
@@ -311,7 +311,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+      ->addConstraint('ValidEntityType')

+++ b/core/modules/comment/src/Plugin/Validation/Constraint/EntityTypeConstraint.php
@@ -0,0 +1,24 @@
+  public $message = 'This field requires a valid entity type.';

+++ b/core/modules/comment/tests/src/Kernel/CommentValidationTest.php
@@ -76,6 +76,19 @@ public function testValidation() {
+    // Provide an invalid entity type.
+    $comment = $this->entityManager->getStorage('comment')->create([
+      'entity_id' => $node->id(),
+      'entity_type' => 'invalid-entity_type',
+      'field_name' => 'comment',
+      'comment_type' => 'comment',
+      'comment_body' => $this->randomMachineName(),
+    ]);
+    $violations = $comment->validate();
+    $this->assertCount(1, $violations, 'Violation found on entity type');
+    $this->assertEquals('entity_type', $violations[0]->getPropertyPath());
+    $this->assertEquals(t('This field requires a valid entity type.'), $violations[0]->getMessage());

But I think we still want this?

To be fair, the most important part of this issue has now already been solved in #2885809: The 'entity_type' and 'field_name' base fields on Comment are required, meaning that I think we can downgrade this from major to normal.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

The issue summary doesn't seem to reflect the work on the patch, where there is discussion about allowing constraints to define dependencies on each other.

This affects other things than comments too. I wanted to add a constraint to the duration field type (#3067478: add validation that the duration in seconds and the PHP duration match), but because the data type constraints on properties are run first, it's not going to work. Generally, there are cases where you want constraints on the properties of a field, and then also a constraint on the field value as a whole to check it makes sense. The deeper constraints should run first.

wim leers’s picture

Priority: Normal » Major

#59: Excellent, I was hoping more people would comment here and report other cases where this causes problems!

I think that's the additional impact we needed to hear about to justify bumping this to Major.

It'd be wonderful if you could update the issue summary with your perspective, with the use cases you see. 🙏

joachim’s picture

Ok I'll try to expand on the use case I saw here first, as I don't feel competent to wade into the issue summary...

Over at Duration Field module, we have a field type with two properties:

- duration, which is a PHP duration string such as 'P1M1D'. This uses a custom data type.
- seconds, which is an integer count of seconds.

The duration data type has a constraint to check its value is a correctly-formed PHP duration string. So '1D' is not valid (must start with 'P'), 'P1X' is not valid ('X' is not a valid interval letter), 'cake' is not valid for obvious reasons. Setting it as deeply as on the data type makes sense, because the data type should enforce this wherever it's used.

I wanted to add a constraint to the field item as a whole, to enforce that the durations and the seconds values both match. In other words, prevent a field being saved with something like 'P1D' for the duration and '60' for the seconds (one day is not 60 seconds long!)

However, the constraint on the field item runs BEFORE the constraint on the duration property. So the field item constraint might run with badly-formed values for the duration property! It would therefore need to perform its OWN check on the format of the duration value, which is just repeating the work that the duration data type constraint is going to do again later on.

More generally, this problem will occur anytime you have a compound field type where:

- one or more of the field's properties properties need validation for their value alone
- the whole field item needs validation as a whole

wim leers’s picture

Title: Entity + Field validation constraints are processed in the incorrect order » Entity + Field + Property validation constraints are processed in the incorrect order
Issue summary: View changes

Don't underestimate yourself! :)

I think this is plenty clear as-is. I'm just quoting the entirety of #61 into the issue summary 😀

joachim’s picture

It occurs to me that the way to summarize this issue is this:

Validation constraints for complex data (such as entities, fields with sub-properties, etc) should be able to rely on the values of the different data elements being valid themselves.

wim leers’s picture

Please add that to the issue summary! 👍

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

jantoine’s picture

I've also come up against this issue.

We have a Time Entry entity type that has Start Time, End Time and Duration fields, all of which are required.

We have field level constraints ensuring that the start time does not come before the end time. We also have entity level constraints ensuring that a new time entry doesn't overlap an existing time entry in any way. In total we have five different constraints at different levels, all of which are run upon validation.

If the Start Time, End Time or Duration fields have invalid data, it makes no sense to run validation further up the chain testing for sequential datetimes and overlaps with other time entry entities.

For now we'll have to implement the same checks in all of our constraints and skip the current constraints checks if the previous constraints checks don't pass, similar to how #2885809: The 'entity_type' and 'field_name' base fields on Comment are required was resolved.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

wim leers’s picture

Came up again in \Drupal\ckeditor5\Plugin\Validation\Constraint\SourceEditingRedundantTagsConstraintValidator.

berdir’s picture

Assigned: fago » Unassigned

My opinion is still #55 and that this is a non-major documentation issue. The title is also wrong IMHO. It's not (only) about the order. What we do is run *all* validation plugins and return all their results and what the old patch here proposes is stopping early and not running all of them.

As mentioned in #55, that comes with drawbacks, specifically that you don't know if you got all the validation failures when you call validate once(), which could result in possibly multiple iterations of fixing things.

wim leers’s picture

Agreed that we should continue to return *all* validation errors!

But wouldn't depth-first still be preferable? 🤔

I apologize for rigidly pushing for a certain direction ~5 years ago. Let's get this back on track, and let's retitle this issue as you ask! But how?

  1. The example I gave in #23 is that of CommentNameConstraintValidator needing a valid entity ID. I realize that not every validator needs other things to have validated already. Maybe we can optionally allow a validator to indicate which nested values need to have been validated already?
    → title could be "Allow validation constraints to specify which nested values must be validated first"
  2. Or … perhaps you're saying: we should not add more infrastructure for this in Drupal, the few scenarios that need it should just use https://symfony.com/doc/5.4/validation/sequence_provider.html? Or … perhaps the new-since-Symfony-5.1 https://symfony.com/doc/5.4/reference/constraints/Sequentially.html?
    → title could be "Update CommentNameConstraintValidator to specify a validation sequence"
berdir’s picture

> But wouldn't depth-first still be preferable? 🤔

I don't really see what difference it would make. Even if the deeper constraints fail, we still run the others and they still need to consider invalid data?

I don't know if there are any constraint features that we could depend on for specific dependencies or something, but being able to return all validation results in one go seems to contradict any attempt at not running some constraints under some conditions?

joachim’s picture

> Even if the deeper constraints fail, we still run the others and they still need to consider invalid data?

In the cases here, no, you wouldn't.

For example, suppose you have a datetime field, and on the field you have a validation to say that it must fall on a Sunday.

User enters '34' for the day of the month. Validation fails on that property. You don't validate the whole date, because the data is meaningless.

The same applies to the comment problem. If the entity_type is invalid, there is nothing you can work with.

wim leers’s picture

User enters '34' for the day of the month. Validation fails on that property. You don't validate the whole date, because the data is meaningless.

+

The same applies to the comment problem. If the entity_type is invalid, there is nothing you can work with.

Exactly!

wim leers’s picture

I wrote this in #73.2:

Or … perhaps you're saying: we should not add more infrastructure for this in Drupal, the few scenarios that need it should just use https://symfony.com/doc/5.4/validation/sequence_provider.html?

I investigated this today, to check if this is viable to use today 🤓

Conclusion: unfortunately not.

Because Drupal core does not use \Symfony\Component\Validator\Validator\RecursiveContextualValidator implements ContextualValidatorInterface but \Drupal\Core\TypedData\Validation\RecursiveContextualValidator implements ContextualValidatorInterface, and the Drupal implementation does:

  1.     if (isset($groups)) {
          throw new \LogicException('Passing custom groups is not supported.');
        }
    
  2.     if (isset($constraints) || !$this->context->isGroupValidated($cache_key, Constraint::DEFAULT_GROUP)) {
          if (!isset($constraints)) {
            $this->context->markGroupAsValidated($cache_key, Constraint::DEFAULT_GROUP);
            $constraints = $metadata->findConstraints(Constraint::DEFAULT_GROUP);
          }
          $this->validateConstraints($value, $cache_key, $constraints);
        }
    

    meaning: Drupal hardcodes the default group and doesn't auto-resolve it into the concrete set of groups specified for the class, which is what Symfony's implementation does:

                // Replace the "Default" group by the group sequence defined
                // for the class, if applicable.
                // This is done after checking the cache, so that
                // spl_object_hash() isn't called for this sequence and
                // "Default" is used instead in the cache. This is useful
                // if the getters below return different group sequences in
                // every call.
                if (Constraint::DEFAULT_GROUP === $group) {
                    if ($metadata->hasGroupSequence()) {
                        // The group sequence is statically defined for the class
                        $group = $metadata->getGroupSequence();
                        $defaultOverridden = true;
                    } elseif ($metadata->isGroupSequenceProvider()) {
                        // The group sequence is dynamically obtained from the validated
                        // object
                        /* @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */
                        $group = $object->getGroupSequence();
                        $defaultOverridden = true;
    
                        if (!$group instanceof GroupSequence) {
                            $group = new GroupSequence($group);
                        }
                    }
                }
    

This means that today, it is impossible to use GroupSequenceProviderInterface today. Even though that would AFAICT be possible without changing any other API. If we'd port the if (isGroupSequenceProvider) branch into Drupal's recursive validator, it would be possible. (EDIT: that's not possible 1:1 because Drupal uses class TypedDataMetadata implements MetadataInterface, and MetadataInterface does not have "group sequence" methods, only ClassMetadataInterface does.)

Why? Quoting @fago from 2015 in #2343035-155: Upgrade validator integration for Symfony versions 2.5+:

The implemenation does not cover groups at all - as we do not need them and did not implement them before in our Metadata implementations either. This allows our RecurisveValidator to save quite some code over the symfony one. If we decide we want to add this feature later on, we'd have to add a respective implementation also.

I think this issue proves there is a reason to support it. Which @dawehner also said at #2343035-159: Upgrade validator integration for Symfony versions 2.5+.2:

As probably said personally, I think not implementing groups at the moment is totally fine. Its a feature which we don't use really and could add later, if it really would be needed.

Note that that

wim leers’s picture

Component: entity system » typed data system
Related issues: +#2343035: Upgrade validator integration for Symfony versions 2.5+

Considering #77, I think we need to move this to the typed data system component.

berdir’s picture

Really not familiar with that code, but are you sure that will allow us to actually define groups or anything else more complex across field/property boundaries?

From a quick glance, I very much doubt it. \Drupal\Core\TypedData\Validation\RecursiveContextualValidator::validateNode uses recursion to first validate itself and then its children. At no point is there ever a constraint list for every field and their properties.

wim leers’s picture

I think you're right 😔 The "group" functionality only works for executing multiple validation constraints on one node in a specific order. But in this case, it's child nodes that would need to be validated first. 😬

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

jonathanshaw’s picture

It's not clear what the problem is that we are trying to solve here.

Lower-level constraints are still being validated recursively even if high-level constraints report violations, so changing the sequence of validation will not lead to changes in what violations are reported for high-level validation. Nothing valid becomes invalid, or vice versa, if we change the order of execution.

I can see 2 problems that are still relevant:

The DX problem: Entity/field Validators have to be coded defensively, they cannot assume their fields/properties are themselves valid and in some sense this is not DRY. (see #61)

The UX problem: Higher-level entity/field constraint violations are reported to users in addition to and prior to the lower-level field/property violation that is the root cause of the higher-level constraint failure; this could be seen as confusing - it is more helpful to focus the user's attention on the lower-level violation.

Here's a proposal for addressing this without an elaborate system of explicit dependencies between constraints:

1. Use try/catch around the call to each constraint validator, and add a violation "Could not verify '{$constraint_message'" if the validator blows up and throws an exception or error. This would mean that validators did not need to be coded defensively; they could generally assume lower-level data was valid.

2. Only add this new kind of violation, if the child node validators do not add any violations. This would lead to the user typically only being exposed to a single violation message that correctly identified the root cause. The only drawback to this is that a poorly coded validator, which breaks not because lower-level data is invalid but because of some other unrelated bug, will not have its failure surfaced to the user until after any other lower-level violations are fixed. I think that's acceptable - bugs cause suboptimal UX by nature, we can't prevent that.

3. Store/retrieve/display the violations in the opposite sequence to now. If we think it's often better UX for the lower-level violations to be reported first and acted on first, then we can just handle that either at the level of the violation internal storage, retrieval, or even display. We don't need to change the sequence of execution to address this.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

wim leers’s picture

The problem we're trying to solve here is two-fold:

  1. all of Drupal core, for both content and config, lumps all validation constraints together in one bucket
  2. all of Drupal core, for both content and config, pays no attention to the validation sequence: it always uses the same top-down (breadth-first, see \Drupal\Core\TypedData\Validation\RecursiveContextualValidator::validateNode()) recursive validation strategy, which results in the entity-level validation constraints not being able to assume valid field values and field-level validation constraints not being able to assume valid property values

I suspect

are part of the solution.

\Drupal\Tests\jsonapi\Functional\ResourceTestBase has thorough test coverage, but it does NOT assert that the validation error messages in case of a nonsensical value (for example, the string hello world being specified for a boolean "status" field or an integer "weight" field).
That test (and the REST equivalents) are specifically testing access control and basic shape of the representation, not detailed validation.

I have recently started encountering this problem again, but now in the world of Config, because I've been working on config validation. There, it's likely that you'll see multiple errors for the same config property path, for example:

[
  0 => "The '' plugin does not exist.",
  1 => "This value should not be null.",
]

It goes without saying that the latter (generated by the NotNull constraint) should result in none of the other validators running, because there literally is no point in them running. The consequence: noisy, confusing validation errors.

mxr576’s picture

Wow, wonderful thread! :) I learned a lot from reading this and I am also still unsure how many problems we have and what are those... :D

I also encountered the problem in the past that in validators you have to use defensive coding or you have to "fail silently" in Validator A because Validator B will fail too and that is enough.

I have checked the EmailValidator from Symfony 7.1 and they also use defensive coding there.

It goes without saying that the latter (generated by the NotNull constraint) should result in none of the other validators running, because there literally is no point in them running.

Maybe we should just forget stacking independent validator units and instead concrete and complex requirements should be covered by a specific validator... which goes against composability and (again) goes against what we can see in Symfony... BUT as it was highlighted and proved with historical context in #77, we already have a Drupalism in typed data validation due to the implementation does not support validation groups (which are essentially a solution for considering a set of independent validators as one validator unit).

mxr576’s picture

Maybe we should just forget stacking independent validator units and instead concrete and complex requirements should be covered by a specific validator... which goes against composability

Well there is https://symfony.com/doc/7.1/reference/constraints/Compound.html for that in Symfony, but that would still expose all validation errors independently and maybe what I would expected a "composite" would have its own dedicated validation error that would be displayed when any of the validators failed.

Again... preferences and use cases.

bbrala’s picture

Kinda like what the AnyOf of symfony does. Have 2 places where something similar is coming:

#3432353: Add validation constraints to core.extension
ValidSequenceKeysConstraintValidator

And #3461720: Create AtLeastOneOf constraint.

Kinda like the compound validation errors there. We did need a added __cone method there, but seems a resonable way to do multiple.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.