diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php index c6849264b4..48d2f40526 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php @@ -24,8 +24,12 @@ public function validate($items, Constraint $constraint) { $id_key = $entity->getEntityType()->getKey('id'); $query = \Drupal::entityQuery($entity_type_id); - if (!empty($entity->id())) { - $query->condition($id_key, $items->getEntity()->id(), '<>'); + + $entity_id = $entity->id(); + // Using isset() instead of !empty() as 0 and '0' are valid ID values for + // entity types using string IDs. + if (isset($entity_id)) { + $query->condition($id_key, $entity_id, '<>'); } $value_taken = (bool) $query @@ -38,7 +42,7 @@ public function validate($items, Constraint $constraint) { $this->context->addViolation($constraint->message, [ '%value' => $item->value, '@entity_type' => $entity->getEntityType()->getLowercaseLabel(), - '@field_name' => mb_strtolower($items->getFieldDefinition()->getLabel()), + '@field_name' => $items->getFieldDefinition()->getLabel(), ]); } } diff --git a/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml b/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml new file mode 100644 index 0000000000..a667c6217d --- /dev/null +++ b/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml @@ -0,0 +1,6 @@ +name: 'UniqueField Constraint Test' +type: module +description: 'Support module for UniqueField Constraint testing.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.module b/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.module new file mode 100644 index 0000000000..c9bb6ca484 --- /dev/null +++ b/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.module @@ -0,0 +1,18 @@ +id() === 'entity_test_string_id') { + /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */ + $fields['name']->addConstraint('UniqueField'); + } +} diff --git a/core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldConstraintTest.php b/core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldConstraintTest.php new file mode 100644 index 0000000000..bd4333b001 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldConstraintTest.php @@ -0,0 +1,115 @@ +installEntitySchema('entity_test_string_id'); + + EntityTestStringId::create([ + 'id' => 'foo', + 'name' => $this->randomString(), + ])->save(); + + // Reload the entity. + $entity = EntityTestStringId::load('foo'); + + // Check that an existing entity validates when the value is preserved. + $violations = $entity->name->validate(); + $this->assertCount(0, $violations); + + // Create a new entity with a different ID and a different field value. + EntityTestStringId::create([ + 'id' => 'bar', + 'name' => $this->randomString(), + ]); + + // Check that a new entity with a different field value validates. + $violations = $entity->name->validate(); + $this->assertCount(0, $violations); + } + + /** + * Tests cases when validation raises violations for entities with string IDs. + * + * @param string|int|null $id + * The entity ID. + * + * @covers ::validate + * + * @dataProvider providerTestEntityWithStringIdWithViolation + */ + public function testEntityWithStringIdWithViolation($id) { + $this->installEntitySchema('entity_test_string_id'); + + $value = $this->randomString(); + + EntityTestStringId::create([ + 'id' => 'first_entity', + 'name' => $value, + ])->save(); + + $entity = EntityTestStringId::create([ + 'id' => $id, + 'name' => $value, + ]); + /** @var \Symfony\Component\Validator\ConstraintViolationList $violations */ + $violations = $entity->get('name')->validate(); + + $message = new FormattableMarkup('A @entity_type with @field_name %value already exists.', [ + '%value' => $value, + '@entity_type' => $entity->getEntityType()->getLowercaseLabel(), + '@field_name' => 'Name', + ]); + + // Check that the validation has created the appropriate violation. + $this->assertCount(1, $violations); + $this->assertEquals($message, $violations[0]->getMessage()); + } + + /** + * Data provider for ::testEntityWithStringIdWithViolation(). + * + * @return array + * An array of test cases. + * + * @see self::testEntityWithStringIdWithViolation() + */ + public function providerTestEntityWithStringIdWithViolation() { + return [ + 'without an id' => [NULL], + 'zero as integer' => [0], + 'zero as string' => ["0"], + 'non-zero as integer' => [mt_rand(1, 127)], + 'non-zero as string' => [(string) mt_rand(1, 127)], + 'alphanumeric' => [$this->randomMachineName()], + ]; + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldValueValidationTest.php b/core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldValueValidationTest.php deleted file mode 100644 index 93ad41bf40..0000000000 --- a/core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldValueValidationTest.php +++ /dev/null @@ -1,125 +0,0 @@ -installEntitySchema('entity_test_string_id'); - - $this->entityType = 'entity_test'; - $this->fieldName = $field_storage = mb_strtolower($this->randomMachineName() . '_field_name'); - $storage = FieldStorageConfig::create([ - 'field_name' => $this->fieldName, - 'entity_type' => 'entity_test_string_id', - 'type' => 'string', - 'constraints' => [ - 'UniqueField' => [], - ], - ]); - $storage->save(); - - $field_definition = [ - 'field_storage' => $storage, - 'bundle' => 'entity_test_string_id', - 'field_name' => $this->fieldName, - 'label' => $this->randomMachineName() . '_label', - 'description' => $this->randomMachineName() . '_description', - ]; - - FieldConfig::create($field_definition)->save(); - $this->value = $this->randomString(); - - EntityTestStringId::create([ - 'id' => $this->randomMachineName(), - $this->fieldName => $this->value, - ])->save(); - } - - /** - * Tests that the field passes validation if the value is not unique. - */ - public function testNonUniqueFieldValue() { - $entity = EntityTestStringId::create([ - 'id' => $this->randomMachineName(), - $this->fieldName => $this->randomString(), - ]); - $entity->{$this->fieldName}->getFieldDefinition()->addConstraint('UniqueField'); - $violations = $entity->{$this->fieldName}->validate(); - - $this->assertEquals(0, count($violations)); - } - - /** - * Tests that the number of values is validated against the field cardinality. - * - * @dataProvider providerTestUniqueFieldValueConstraint - */ - public function testUniqueFieldValueConstraint($id) { - $entity = EntityTestStringId::create([ - 'id' => $id, - $this->fieldName => $this->value, - ]); - $entity->{$this->fieldName}->getFieldDefinition()->addConstraint('UniqueField'); - $violations = $entity->{$this->fieldName}->validate(); - - $message = $this->t('A @entity_type with @field_name %value already exists.', [ - '%value' => $this->value, - '@entity_type' => $entity->getEntityType()->getLowercaseLabel(), - '@field_name' => $entity->{$this->fieldName}->getFieldDefinition()->label(), - ]); - - $this->assertEquals(1, count($violations)); - $this->assertEquals($message, $violations[0]->getMessage()); - } - - /** - * Data provider for providerTestUniqueFieldValueConstraint. - * - * @return array - * An array of test cases. - */ - public function providerTestUniqueFieldValueConstraint() { - return [ - 'numeric id entity without an id' => [NULL], - 'numeric id entity with an id of 0' => [0], - 'numeric id entity with a random id' => [mt_rand(1, 127)], - 'string id entity with empty id' => [NULL], - 'string id entity with a random id' => [$this->randomMachineName()], - ]; - } - -}