From ee822bb01d38859b3900301c9f7a5ff101489857 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Fri, 22 Feb 2019 08:45:40 +0000 Subject: [PATCH] Issue #3027745 by claudiu.cristea, idimopoulos, wengerk, alexpott: UniqueFieldConstraint doesn't work for entities with string IDs --- .../Constraint/UniqueFieldValueValidator.php | 13 +- .../unique_field_constraint_test.info.yml | 6 + .../unique_field_constraint_test.module | 18 +++ .../Validation/UniqueFieldConstraintTest.php | 115 ++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml create mode 100644 core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.module create mode 100644 core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldConstraintTest.php 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 3b8fd036f95c..3c34ec9665e2 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php @@ -23,9 +23,16 @@ public function validate($items, Constraint $constraint) { $entity_type_id = $entity->getEntityTypeId(); $id_key = $entity->getEntityType()->getKey('id'); - $value_taken = (bool) \Drupal::entityQuery($entity_type_id) - // The id could be NULL, so we cast it to 0 in that case. - ->condition($id_key, (int) $items->getEntity()->id(), '<>') + $query = \Drupal::entityQuery($entity_type_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 ->condition($field_name, $item->value) ->range(0, 1) ->count() 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 000000000000..a667c6217d0d --- /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 000000000000..c9bb6ca484a8 --- /dev/null +++ b/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.module @@ -0,0 +1,18 @@ +<?php + +/** + * @file + * Contains unique_field_constraint_test.module + */ + +use Drupal\Core\Entity\EntityTypeInterface; + +/** + * Implements hook_entity_base_field_info_alter(). + */ +function unique_field_constraint_test_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) { + if ($entity_type->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 000000000000..3fbdf4e14143 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Validation/UniqueFieldConstraintTest.php @@ -0,0 +1,115 @@ +<?php + +namespace Drupal\KernelTests\Core\Validation; + +use Drupal\Component\Render\FormattableMarkup; +use Drupal\entity_test\Entity\EntityTestStringId; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests the unique field value validation constraint. + * + * @coversDefaultClass \Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator + * + * @group Validation + */ +class UniqueFieldConstraintTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'entity_test', + 'unique_field_constraint_test', + 'user', + ]; + + /** + * Tests cases where the validation passes for entities with string IDs. + * + * @covers ::validate + */ + public function testEntityWithStringId() { + $this->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()], + ]; + } + +} -- GitLab