From a0c7fa5f09a81aa679fdf793fd5e1a439e2e3734 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Fri, 14 Mar 2025 16:27:32 +0100
Subject: [PATCH 01/11] Add NoEntitiesExistYetWithHigherCardinality valdator
 from parent issue

---
 .../field/config/schema/field.schema.yml      |  4 +
 ...oEntitiesExistYetWithHigherCardinality.php | 47 ++++++++++++
 ...ExistYetWithHigherCardinalityValidator.php | 76 +++++++++++++++++++
 3 files changed, 127 insertions(+)
 create mode 100644 core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php
 create mode 100644 core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php

diff --git a/core/modules/field/config/schema/field.schema.yml b/core/modules/field/config/schema/field.schema.yml
index cdd1d4bab05c..26f98da302f2 100644
--- a/core/modules/field/config/schema/field.schema.yml
+++ b/core/modules/field/config/schema/field.schema.yml
@@ -45,6 +45,10 @@ field.storage.*.*:
     cardinality:
       type: integer
       label: 'Maximum number of values users can enter'
+      constraints:
+        NoEntitiesExistYetWithHigherCardinality:
+          entityType: '%parent.entity_type'
+          fieldName: '%parent.field_name'
     translatable:
       type: boolean
       label: 'Translatable'
diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php
new file mode 100644
index 000000000000..a2bb7bd30264
--- /dev/null
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\field\Plugin\Validation\Constraint;
+
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Symfony\Component\Validator\Constraint as SymfonyConstraint;
+use Drupal\Core\Validation\Attribute\Constraint;
+
+/**
+ * Checks if a plugin exists and optionally implements a particular interface.
+ */
+#[Constraint(
+  id: 'NoEntitiesExistYetWithHigherCardinality',
+  label: new TranslatableMarkup('No entities exist with higher cardinality', [], ['context' => 'Validation'])
+)]
+class NoEntitiesExistYetWithHigherCardinality extends SymfonyConstraint {
+
+  /**
+   * The error message if a plugin does not implement the expected interface.
+   *
+   * @var string
+   */
+  public string $message = "The field '@field_name' of entity type '@entity_type' has more entries (@max_delta) than the cardinality (@cardinality) allows.";
+  /**
+   * The entity type to check.
+   *
+   * @var string
+   */
+  public string $entityType;
+
+  /**
+   * The field name to check.
+   *
+   * @var string
+   */
+  public string $fieldName;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRequiredOptions(): array {
+    return ['entityType', 'fieldName'];
+  }
+
+}
diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
new file mode 100644
index 000000000000..d251dab2854d
--- /dev/null
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\field\Plugin\Validation\Constraint;
+
+use Drupal\Core\Config\Schema\TypeResolver;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint as SymfonyConstraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+class NoEntitiesExistYetWithHigherCardinalityValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  public function __construct(
+    protected EntityTypeManagerInterface $entityTypeManager,
+  ) {
+  }
+
+  public static function create(ContainerInterface $container): self {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate(mixed $cardinality, SymfonyConstraint $constraint): void {
+    assert($constraint instanceof NoEntitiesExistYetWithHigherCardinality);
+
+    if ($cardinality === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
+      return;
+    }
+
+    /**
+     * We cannot check this constraint if the field storage does not exist.
+     */
+    $fieldStorageConfig = $this->entityTypeManager->getStorage('field_storage_config')
+      ->load($constraint->entityType . '.' . $constraint->fieldName);
+    if ($fieldStorageConfig === NULL) {
+      return;
+    }
+
+    $object = $this->context->getObject();
+    assert($object instanceof TypedDataInterface);
+
+    $entity_type = TypeResolver::resolveExpression($constraint->entityType, $object);
+    $field_name = TypeResolver::resolveExpression($constraint->fieldName, $object);
+
+    $max_delta_alias = 'max_delta';
+    $result = $this->entityTypeManager->getStorage($entity_type)
+      ->getAggregateQuery()
+      ->accessCheck(FALSE)
+      ->aggregate($field_name . '.%delta', 'MAX', NULL, $max_delta_alias)
+      ->execute();
+
+    $max_delta = 0;
+    if (is_array($result) && !empty($result)) {
+      $max_delta = $result[0][$max_delta_alias] ?? 0;
+    }
+
+    if ($max_delta > $cardinality) {
+      $this->context->addViolation($constraint->message, [
+        '@entity_type' => $entity_type,
+        '@field_name' => $field_name,
+        '@max_delta' => $max_delta,
+        '@cardinality' => $cardinality,
+      ]);
+    }
+  }
+
+}
-- 
GitLab


From b14281093ba3eb99f49a284413313913bc9d937b Mon Sep 17 00:00:00 2001
From: anjaliprasannan <anjaliprasannan243@gmail.com>
Date: Sat, 22 Mar 2025 22:11:27 +0530
Subject: [PATCH 02/11] Issue #3513035: Implement
 NoEntitiesExistYetWithHigherCardinality tests

---
 ...itiesExistYetWithHigherCardinalityTest.php | 125 ++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php

diff --git a/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php b/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
new file mode 100644
index 000000000000..8e2c19e6fc49
--- /dev/null
+++ b/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
@@ -0,0 +1,125 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\field\Unit\Plugin\Validation\Constraint;
+
+use Drupal\field\Plugin\Validation\Constraint\NoEntitiesExistYetWithHigherCardinality;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
+
+/**
+ * Tests the NoEntitiesExistYetWithHigherCardinality constraint.
+ *
+ * @group field
+ */
+class NoEntitiesExistYetWithHigherCardinalityTest extends UnitTestCase {
+
+  /**
+   * Tests the constraint's required options.
+   */
+  public function testRequiredOptions(): void {
+    $options = [
+      'entityType' => 'node',
+      'fieldName' => 'field_test',
+    ];
+    $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
+    $requiredOptions = $constraint->getRequiredOptions();
+
+    $this->assertTrue(is_array($requiredOptions));
+    $this->assertEquals(['entityType', 'fieldName'], $requiredOptions);
+  }
+
+  /**
+   * Tests the constraint initialization with valid options.
+   */
+  public function testValidOptions(): void {
+    $options = [
+      'entityType' => 'node',
+      'fieldName' => 'field_test',
+    ];
+    
+    $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
+    
+    $this->assertEquals('node', $constraint->entityType);
+    $this->assertEquals('field_test', $constraint->fieldName);
+    $this->assertEquals(
+      "The field '@field_name' of entity type '@entity_type' has more entries (@max_delta) than the cardinality (@cardinality) allows.",
+      $constraint->message
+    );
+  }
+
+  /**
+   * Tests the constraint initialization with missing required options.
+   */
+  public function testMissingOptions(): void {
+    $this->expectException(\Symfony\Component\Validator\Exception\MissingOptionsException::class);
+    $this->expectExceptionMessage('The options "entityType" must be set for constraint');
+    
+    new NoEntitiesExistYetWithHigherCardinality(['fieldName' => 'field_test']);
+  }
+
+  /**
+   * Tests the constraint's default configuration.
+   */
+  public function testDefaultConfiguration(): void {
+    $options = [
+      'entityType' => 'user',
+      'fieldName' => 'field_example',
+    ];
+    
+    $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
+    
+    $defaultConfig = $constraint->getDefaultOption();
+    $this->assertNull($defaultConfig);
+  }
+
+  /**
+   * Tests the message template with different parameters.
+   *
+   * @dataProvider messageParametersProvider
+   */
+  public function testMessageParameters(string $entityType, string $fieldName, int $maxDelta, int $cardinality, string $expectedMessage): void {
+    $options = [
+      'entityType' => $entityType,
+      'fieldName' => $fieldName,
+    ];
+    
+    $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
+    
+    // Simulate the violation building process.
+    $parameters = [
+      '@field_name' => $fieldName,
+      '@entity_type' => $entityType,
+      '@max_delta' => (string) $maxDelta,
+      '@cardinality' => (string) $cardinality,
+    ];
+    
+    $message = strtr($constraint->message, $parameters);
+    $this->assertEquals($expectedMessage, $message);
+  }
+
+  /**
+   * Data provider for testMessageParameters.
+   */
+  public function messageParametersProvider(): array {
+    return [
+      [
+        'node',
+        'field_body',
+        3,
+        2,
+        "The field 'field_body' of entity type 'node' has more entries (3) than the cardinality (2) allows.",
+      ],
+      [
+        'user',
+        'field_address',
+        5,
+        1,
+        "The field 'field_address' of entity type 'user' has more entries (5) than the cardinality (1) allows.",
+      ],
+    ];
+  }
+
+}
-- 
GitLab


From d6a8fd712ddca926f64ba0ff3e287b9dbd580d84 Mon Sep 17 00:00:00 2001
From: anjaliprasannan <anjaliprasannan243@gmail.com>
Date: Sat, 22 Mar 2025 22:25:34 +0530
Subject: [PATCH 03/11] Issue #3513035: phpcs fix

---
 ...ExistYetWithHigherCardinalityValidator.php | 24 ++++++++++++++++---
 ...itiesExistYetWithHigherCardinalityTest.php | 12 ++++------
 2 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
index d251dab2854d..2905e13a4f92 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -13,6 +13,26 @@
 use Symfony\Component\Validator\Constraint as SymfonyConstraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
+/**
+ * Validates the NoEntitiesExistYetWithHigherCardinality constraint.
+ *
+ * This validator checks whether existing entities of a specified type have more
+ * field values than allowed by the given cardinality limit. It performs an
+ * aggregate query to find the maximum delta (number of field values) for the
+ * specified field across all entities of the given type, and compares it
+ * against the provided cardinality.
+ *
+ * The validation:
+ * - Skips if cardinality is unlimited (-1)
+ * - Skips if the field storage configuration doesn't exist
+ * - Uses EntityTypeManager to query the maximum field delta
+ * - Adds a violation if the maximum delta exceeds the cardinality
+ *
+ * This validator implements ContainerInjectionInterface to access the entity
+ * type manager service from the Drupal service container.
+ *
+ * @see \Drupal\field\Plugin\Validation\Constraint\NoEntitiesExistYetWithHigherCardinality
+ */
 class NoEntitiesExistYetWithHigherCardinalityValidator extends ConstraintValidator implements ContainerInjectionInterface {
 
   public function __construct(
@@ -36,9 +56,7 @@ public function validate(mixed $cardinality, SymfonyConstraint $constraint): voi
       return;
     }
 
-    /**
-     * We cannot check this constraint if the field storage does not exist.
-     */
+    // We cannot check this constraint if the field storage does not exist.
     $fieldStorageConfig = $this->entityTypeManager->getStorage('field_storage_config')
       ->load($constraint->entityType . '.' . $constraint->fieldName);
     if ($fieldStorageConfig === NULL) {
diff --git a/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php b/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
index 8e2c19e6fc49..85dcaa536db8 100644
--- a/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
+++ b/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
@@ -6,8 +6,6 @@
 
 use Drupal\field\Plugin\Validation\Constraint\NoEntitiesExistYetWithHigherCardinality;
 use Drupal\Tests\UnitTestCase;
-use Symfony\Component\Validator\Context\ExecutionContextInterface;
-use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
 
 /**
  * Tests the NoEntitiesExistYetWithHigherCardinality constraint.
@@ -41,7 +39,7 @@ public function testValidOptions(): void {
     ];
     
     $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
-    
+
     $this->assertEquals('node', $constraint->entityType);
     $this->assertEquals('field_test', $constraint->fieldName);
     $this->assertEquals(
@@ -56,7 +54,7 @@ public function testValidOptions(): void {
   public function testMissingOptions(): void {
     $this->expectException(\Symfony\Component\Validator\Exception\MissingOptionsException::class);
     $this->expectExceptionMessage('The options "entityType" must be set for constraint');
-    
+
     new NoEntitiesExistYetWithHigherCardinality(['fieldName' => 'field_test']);
   }
 
@@ -70,7 +68,7 @@ public function testDefaultConfiguration(): void {
     ];
     
     $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
-    
+
     $defaultConfig = $constraint->getDefaultOption();
     $this->assertNull($defaultConfig);
   }
@@ -87,7 +85,7 @@ public function testMessageParameters(string $entityType, string $fieldName, int
     ];
     
     $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
-    
+
     // Simulate the violation building process.
     $parameters = [
       '@field_name' => $fieldName,
@@ -103,7 +101,7 @@ public function testMessageParameters(string $entityType, string $fieldName, int
   /**
    * Data provider for testMessageParameters.
    */
-  public function messageParametersProvider(): array {
+  public static function messageParametersProvider(): array {
     return [
       [
         'node',
-- 
GitLab


From f14dae533db8a64394e96d2638711048736823d0 Mon Sep 17 00:00:00 2001
From: anjaliprasannan <anjaliprasannan243@gmail.com>
Date: Mon, 24 Mar 2025 10:05:01 +0530
Subject: [PATCH 04/11] #3513035 Phpcs fix.

---
 .../NoEntitiesExistYetWithHigherCardinalityTest.php   | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php b/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
index 85dcaa536db8..a612d4c4a726 100644
--- a/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
+++ b/core/modules/field/tests/src/Unit/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityTest.php
@@ -6,6 +6,7 @@
 
 use Drupal\field\Plugin\Validation\Constraint\NoEntitiesExistYetWithHigherCardinality;
 use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Validator\Exception\MissingOptionsException as MissingOptionsExceptionAlias;
 
 /**
  * Tests the NoEntitiesExistYetWithHigherCardinality constraint.
@@ -37,7 +38,7 @@ public function testValidOptions(): void {
       'entityType' => 'node',
       'fieldName' => 'field_test',
     ];
-    
+
     $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
 
     $this->assertEquals('node', $constraint->entityType);
@@ -52,7 +53,7 @@ public function testValidOptions(): void {
    * Tests the constraint initialization with missing required options.
    */
   public function testMissingOptions(): void {
-    $this->expectException(\Symfony\Component\Validator\Exception\MissingOptionsException::class);
+    $this->expectException(MissingOptionsExceptionAlias::class);
     $this->expectExceptionMessage('The options "entityType" must be set for constraint');
 
     new NoEntitiesExistYetWithHigherCardinality(['fieldName' => 'field_test']);
@@ -66,7 +67,7 @@ public function testDefaultConfiguration(): void {
       'entityType' => 'user',
       'fieldName' => 'field_example',
     ];
-    
+
     $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
 
     $defaultConfig = $constraint->getDefaultOption();
@@ -83,7 +84,7 @@ public function testMessageParameters(string $entityType, string $fieldName, int
       'entityType' => $entityType,
       'fieldName' => $fieldName,
     ];
-    
+
     $constraint = new NoEntitiesExistYetWithHigherCardinality($options);
 
     // Simulate the violation building process.
@@ -93,7 +94,7 @@ public function testMessageParameters(string $entityType, string $fieldName, int
       '@max_delta' => (string) $maxDelta,
       '@cardinality' => (string) $cardinality,
     ];
-    
+
     $message = strtr($constraint->message, $parameters);
     $this->assertEquals($expectedMessage, $message);
   }
-- 
GitLab


From 7fcf3bab232bbbd08c0f3572264c7c935e386d40 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Brala?=
 <49259-bbrala@users.noreply.drupalcode.org>
Date: Thu, 27 Mar 2025 14:00:36 +0000
Subject: [PATCH 05/11] Small update from parent issue in the order of the
 validate function.

---
 ...itiesExistYetWithHigherCardinalityValidator.php | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
index 2905e13a4f92..5a3438633560 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -56,19 +56,19 @@ public function validate(mixed $cardinality, SymfonyConstraint $constraint): voi
       return;
     }
 
-    // We cannot check this constraint if the field storage does not exist.
-    $fieldStorageConfig = $this->entityTypeManager->getStorage('field_storage_config')
-      ->load($constraint->entityType . '.' . $constraint->fieldName);
-    if ($fieldStorageConfig === NULL) {
-      return;
-    }
-
     $object = $this->context->getObject();
     assert($object instanceof TypedDataInterface);
 
     $entity_type = TypeResolver::resolveExpression($constraint->entityType, $object);
     $field_name = TypeResolver::resolveExpression($constraint->fieldName, $object);
 
+    // We cannot check this constraint if the field storage does not exist.
+    $fieldStorageConfig = $this->entityTypeManager->getStorage('field_storage_config')
+      ->load($entity_type . '.' . $field_name);
+    if ($fieldStorageConfig === NULL) {
+      return;
+    }
+ 
     $max_delta_alias = 'max_delta';
     $result = $this->entityTypeManager->getStorage($entity_type)
       ->getAggregateQuery()
-- 
GitLab


From 7fda1e2193c191a3f11ce406e2fb60bbc056edd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Brala?=
 <49259-bbrala@users.noreply.drupalcode.org>
Date: Thu, 27 Mar 2025 14:44:29 +0000
Subject: [PATCH 06/11] Small phpcs fix

---
 .../NoEntitiesExistYetWithHigherCardinalityValidator.php        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
index 5a3438633560..60dc8b0b94c2 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -68,7 +68,7 @@ public function validate(mixed $cardinality, SymfonyConstraint $constraint): voi
     if ($fieldStorageConfig === NULL) {
       return;
     }
- 
+
     $max_delta_alias = 'max_delta';
     $result = $this->entityTypeManager->getStorage($entity_type)
       ->getAggregateQuery()
-- 
GitLab


From d70d52dbb693af0c0bd44e4624e08478456d96f9 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Fri, 28 Mar 2025 15:04:17 +0100
Subject: [PATCH 07/11] fix: Deprecation test of installing a field without an
 installed schema fails. This check fixes that.

---
 ...iesExistYetWithHigherCardinalityValidator.php | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
index 60dc8b0b94c2..6ad53235d43f 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -5,6 +5,7 @@
 namespace Drupal\field\Plugin\Validation\Constraint;
 
 use Drupal\Core\Config\Schema\TypeResolver;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
@@ -70,11 +71,20 @@ public function validate(mixed $cardinality, SymfonyConstraint $constraint): voi
     }
 
     $max_delta_alias = 'max_delta';
-    $result = $this->entityTypeManager->getStorage($entity_type)
+    $query = $this->entityTypeManager->getStorage($entity_type)
       ->getAggregateQuery()
       ->accessCheck(FALSE)
-      ->aggregate($field_name . '.%delta', 'MAX', NULL, $max_delta_alias)
-      ->execute();
+      ->aggregate($field_name . '.%delta', 'MAX', NULL, $max_delta_alias);
+
+    // When the schema for the entity does not exist the query will throw an
+    // exception. This should only happen in tests.
+    // @see https://www.drupal.org/node/3475719
+    // @todo Remove in Drupal 12.
+    try {
+      $result = $query->execute();
+    } catch (DatabaseExceptionWrapper $exception) {
+      return;
+    }
 
     $max_delta = 0;
     if (is_array($result) && !empty($result)) {
-- 
GitLab


From 7131064709687ba92ac6dca4191f2d06b36b796f Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Fri, 28 Mar 2025 15:09:44 +0100
Subject: [PATCH 08/11] fix: Don't check
 NoEntitiesExistYetWithHigherCardinalityValidator when custom_storage is
 involved

---
 .../NoEntitiesExistYetWithHigherCardinalityValidator.php    | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
index 6ad53235d43f..f6a15bdccf52 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -70,6 +70,12 @@ public function validate(mixed $cardinality, SymfonyConstraint $constraint): voi
       return;
     }
 
+    if ($fieldStorageConfig->hasCustomStorage()) {
+      // If the field storage has custom storage, we cannot check this
+      // constraint.
+      return;
+    }
+
     $max_delta_alias = 'max_delta';
     $query = $this->entityTypeManager->getStorage($entity_type)
       ->getAggregateQuery()
-- 
GitLab


From 47b4e3da6bc522fec90b0a4041c109e65a1d6435 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Fri, 28 Mar 2025 15:11:25 +0100
Subject: [PATCH 09/11] build: codestyle fixes

---
 .../NoEntitiesExistYetWithHigherCardinalityValidator.php       | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
index f6a15bdccf52..aafd8f68e687 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -88,7 +88,8 @@ public function validate(mixed $cardinality, SymfonyConstraint $constraint): voi
     // @todo Remove in Drupal 12.
     try {
       $result = $query->execute();
-    } catch (DatabaseExceptionWrapper $exception) {
+    }
+    catch (DatabaseExceptionWrapper) {
       return;
     }
 
-- 
GitLab


From c038907b0aa1119bf7af1ca44192dacc47f2d2d5 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Fri, 28 Mar 2025 21:35:04 +0100
Subject: [PATCH 10/11] doc: fix some comments as per feedback. Those were
 copied from another class that was the base of this one

---
 .../Constraint/NoEntitiesExistYetWithHigherCardinality.php   | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php
index a2bb7bd30264..88b976e568ac 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinality.php
@@ -9,7 +9,7 @@
 use Drupal\Core\Validation\Attribute\Constraint;
 
 /**
- * Checks if a plugin exists and optionally implements a particular interface.
+ * Checks if an entity with a higher cardinality than specified exists.
  */
 #[Constraint(
   id: 'NoEntitiesExistYetWithHigherCardinality',
@@ -18,11 +18,12 @@
 class NoEntitiesExistYetWithHigherCardinality extends SymfonyConstraint {
 
   /**
-   * The error message if a plugin does not implement the expected interface.
+   * The error message if an entity with a higher cardinality exists.
    *
    * @var string
    */
   public string $message = "The field '@field_name' of entity type '@entity_type' has more entries (@max_delta) than the cardinality (@cardinality) allows.";
+
   /**
    * The entity type to check.
    *
-- 
GitLab


From 848ad1c9226bafb7f107f5f457ce3b751dc25b41 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Wed, 2 Apr 2025 14:54:04 +0200
Subject: [PATCH 11/11] build: fix missing docblock

---
 .../NoEntitiesExistYetWithHigherCardinalityValidator.php       | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
index aafd8f68e687..e2dd2dbf5b52 100644
--- a/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
+++ b/core/modules/field/src/Plugin/Validation/Constraint/NoEntitiesExistYetWithHigherCardinalityValidator.php
@@ -41,6 +41,9 @@ public function __construct(
   ) {
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public static function create(ContainerInterface $container): self {
     return new static(
       $container->get('entity_type.manager')
-- 
GitLab