Problem/Motivation

Ran into this while working on #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support.

#3341682: New config schema data type: `required_label` introduced type: required_label, and it looks like this:

required_label:
  type: label
  label: 'Label'
  constraints:
    NotBlank: {}

NotBlank has a $allowNull option that defaults to FALSE. In other words: it disallows both NULL and the empty string ''.

Unfortunately, this means that any config property that is explicitly marked required by setting the NotNull constraint, this triggers a double error:

Steps to reproduce

See tests.

Proposed resolution

  1. Write tests.
  2. Adjust type: required_label to work fine both when NotNull is present and when it is absent. — per @alexpott and @longwave in #16 and #19: automatically detect when both NotNull and NotBlank are present and if so, set NotBlank's allowNull: true option.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None.

Release notes snippet

N/A

Issue fork drupal-3404061

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Active » Needs review
Issue tags: +Needs Review Queue Initiative
wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs review » Needs work

borisson_ made their first commit to this issue’s fork.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
borisson_’s picture

Status: Needs review » Reviewed & tested by the community

Looks great, we have sufficient coverage and this is a small bugfix.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

There are some comments to resolve.

wim leers’s picture

Addressed!

I'm wondering if at this point a separate test method with @dataProvider or @testWith wouldn't be clearer though 😅

alexpott’s picture

One of the kernel tests is failing do to this change... see https://git.drupalcode.org/issue/drupal-3404061/-/pipelines/56281/test_r...

wim leers’s picture

Forgot to update that one. Green again 👍

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Reran the failing javascript test and it passed so it was random.

1) Drupal\KernelTests\Config\TypedConfigTest::testSimpleConfigValidation
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'This value should not be null.'
+'This value should not be blank.'
/builds/issue/drupal-3404061/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php:94
/builds/issue/drupal-3404061/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php:174
/builds/issue/drupal-3404061/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
FAILURES!
Tests: 2, Assertions: 47, Failures: 1.

Ran the test-only feature and got this so test coverage is there.

Crediting @phenaproxima for the MR review but his name wasn't appearing on the ticket.

Seems all feedback has been addressed though.

borisson_’s picture

Agreeing with @smustgrave, all feedback has been addressed, and this is a good improvement.

alexpott credited longwave.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

@longwave, @Wim Leers and I have discussed this issue at length. The discussion resulted in wanting to explore some other options, namely:

1. Do it at a different level, for example \Drupal\Core\TypedData\Validation\TypedDataMetadata::getConstraints(), which is what the validation system always calls — and detect presence of both and configure the other one correctly
2. Override NotBlankValidator and just make allowNull: true do nothing.
3. Look into \Symfony\Component\Validator\Constraints\Sequentially
4. Change getDefaultConstraints() to configured NotBlank correctly instead of adding NotNull when $definition->isRequired() and NotBlank is present already.

wim leers’s picture

Status: Needs work » Needs review

👍 Thanks, @alexpott!

Just pushed an implementation of option 1.

Why?

  1. Option 1 is the most tightly scoped systemic fix.
  2. Option 2 is a BC break.
  3. Option 3 is the most interesting choice, but also a far bigger change. Essentially, we could automatically convert
    - Foo
    - NotNull
    - Bar
    

    to

    Sequentially:
      - NotNull
      - 
        - Foo
        - Bar
    

    https://symfony.com/doc/current/reference/constraints/Sequentially.html

    i.e. if NotNull is present, run it first, and if it doesn’t pass, don’t execute any of the other constraints; if it does pass, run all other constraints like we do today.

    But that's a huge change, with unpredictable ripple effects, and there's much more nuance to it than that — there's other validation constraint execution ordering issues, and they have been known for >7 years: #2820364 — see #2820364-85: Entity + Field + Property validation constraints are processed in the incorrect order for a recent update.

    IOW: I think special-casing NotNull only for now (prior to fixing that entire massive issue) might be okay, but I’m not 100% certain.

  4. This alternative to option 1 is insufficient if both NotNull and NotBlank are declared simultaneously (like for string__not_null__not_blank in the test coverage), but @alexpott says we could disallow this and trigger a warning. I'd be fine with that, but it's technically a BC break too, and we historically have no verification whatsoever that data types or config schema types make sense — that's why #3401837: Add basic validation to config schema definitions and #3404431: Filter settings schema types are incorrect exist. I'd definitely be in favor for starting to add validation for them though, that'd be an important first step to improving their DX!
wim leers’s picture

I just tried implementing Sequentially.

Looks like my fear was unfounded: the "unpredictable ripple effects" did not happen, thanks to \Drupal\Core\TypedData\Validation\RecursiveContextualValidator::validate() being very narrow in what it accepts:

  public function validate($data, $constraints = NULL, $groups = NULL, $is_root_call = TRUE): static {
    if (isset($groups)) {
      throw new \LogicException('Passing custom groups is not supported.');
    }

    if (!$data instanceof TypedDataInterface) {
      throw new \InvalidArgumentException('The passed value must be a typed data object.');
    }

… because \Symfony\Component\Validator\Constraints\SequentiallyValidator::validate() calls Drupal's RecursiveContextualValidator::validate() (the first ~10 lines of which are displayed above), and $data === 'en' at this point (for the langcode), it throws the exception with the The passed value must be a typed data object. message.

IOW: to adopt Symfony's Sequentially, we need to revise a lot about how Drupal uses the symfony/validator component. For #2820364: Entity + Field + Property validation constraints are processed in the incorrect order we probably will need validation groups, which as you can see above are also explicitly not supported.

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs review » Needs work

alexpott 32 minutes ago
I think option 4 is better than option 1.

alexpott 31 minutes ago
I think we should be doing as little as possible as changing you constraints is quite surprising.

Will figure out how to make that work 😊

alexpott’s picture

Re #18 - I think we'd need our own Sequentially.

wim leers’s picture

Assigned: wim leers » Unassigned
Issue summary: View changes
Status: Needs work » Needs review

Implemented option 4.

Due to

  public function getConstraints() {
    $constraints = $this->definition['constraints'] ?? [];
    $constraints += $this->getTypedDataManager()->getDefaultConstraints($this);
…

any change I make in getDefaultConstraints() gets overwritten automatically.

So this change must happen in the quoted code (\Drupal\Core\TypedData\DataDefinition::getConstraints()).

phenaproxima’s picture

Only one comment about a comment, but otherwise this looks right to me and I will RTBC it once my feedback is addressed in some way. :)

wim leers’s picture

Done 😄

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Ship it!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 5cd249e and pushed to 11.x. Thanks!

  • alexpott committed 5cd249e9 on 11.x
    Issue #3404061 by Wim Leers, borisson_, alexpott, phenaproxima, longwave...
wim leers’s picture

Thanks!

This unblocked #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support but also simplified #3379091 (see #3379091-25: Make NodeType config entities fully validatable) as well as any future work on adding validation constraints where NotBlank is relevant! 👍

Status: Fixed » Closed (fixed)

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