Skip to content

Commit 7187523

Browse files
committed
Issue #3027745 by claudiu.cristea, idimopoulos, wengerk, alexpott: UniqueFieldConstraint doesn't work for entities with string IDs
(cherry picked from commit ee822bb)
1 parent b1be81b commit 7187523

File tree

4 files changed

+149
-3
lines changed

4 files changed

+149
-3
lines changed

lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,16 @@ public function validate($items, Constraint $constraint) {
2323
$entity_type_id = $entity->getEntityTypeId();
2424
$id_key = $entity->getEntityType()->getKey('id');
2525

26-
$value_taken = (bool) \Drupal::entityQuery($entity_type_id)
27-
// The id could be NULL, so we cast it to 0 in that case.
28-
->condition($id_key, (int) $items->getEntity()->id(), '<>')
26+
$query = \Drupal::entityQuery($entity_type_id);
27+
28+
$entity_id = $entity->id();
29+
// Using isset() instead of !empty() as 0 and '0' are valid ID values for
30+
// entity types using string IDs.
31+
if (isset($entity_id)) {
32+
$query->condition($id_key, $entity_id, '<>');
33+
}
34+
35+
$value_taken = (bool) $query
2936
->condition($field_name, $item->value)
3037
->range(0, 1)
3138
->count()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: 'UniqueField Constraint Test'
2+
type: module
3+
description: 'Support module for UniqueField Constraint testing.'
4+
package: Testing
5+
version: VERSION
6+
core: 8.x
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Contains unique_field_constraint_test.module
6+
*/
7+
8+
use Drupal\Core\Entity\EntityTypeInterface;
9+
10+
/**
11+
* Implements hook_entity_base_field_info_alter().
12+
*/
13+
function unique_field_constraint_test_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
14+
if ($entity_type->id() === 'entity_test_string_id') {
15+
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
16+
$fields['name']->addConstraint('UniqueField');
17+
}
18+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace Drupal\KernelTests\Core\Validation;
4+
5+
use Drupal\Component\Render\FormattableMarkup;
6+
use Drupal\entity_test\Entity\EntityTestStringId;
7+
use Drupal\KernelTests\KernelTestBase;
8+
9+
/**
10+
* Tests the unique field value validation constraint.
11+
*
12+
* @coversDefaultClass \Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator
13+
*
14+
* @group Validation
15+
*/
16+
class UniqueFieldConstraintTest extends KernelTestBase {
17+
18+
/**
19+
* {@inheritdoc}
20+
*/
21+
protected static $modules = [
22+
'entity_test',
23+
'unique_field_constraint_test',
24+
'user',
25+
];
26+
27+
/**
28+
* Tests cases where the validation passes for entities with string IDs.
29+
*
30+
* @covers ::validate
31+
*/
32+
public function testEntityWithStringId() {
33+
$this->installEntitySchema('entity_test_string_id');
34+
35+
EntityTestStringId::create([
36+
'id' => 'foo',
37+
'name' => $this->randomString(),
38+
])->save();
39+
40+
// Reload the entity.
41+
$entity = EntityTestStringId::load('foo');
42+
43+
// Check that an existing entity validates when the value is preserved.
44+
$violations = $entity->name->validate();
45+
$this->assertCount(0, $violations);
46+
47+
// Create a new entity with a different ID and a different field value.
48+
EntityTestStringId::create([
49+
'id' => 'bar',
50+
'name' => $this->randomString(),
51+
]);
52+
53+
// Check that a new entity with a different field value validates.
54+
$violations = $entity->name->validate();
55+
$this->assertCount(0, $violations);
56+
}
57+
58+
/**
59+
* Tests cases when validation raises violations for entities with string IDs.
60+
*
61+
* @param string|int|null $id
62+
* The entity ID.
63+
*
64+
* @covers ::validate
65+
*
66+
* @dataProvider providerTestEntityWithStringIdWithViolation
67+
*/
68+
public function testEntityWithStringIdWithViolation($id) {
69+
$this->installEntitySchema('entity_test_string_id');
70+
71+
$value = $this->randomString();
72+
73+
EntityTestStringId::create([
74+
'id' => 'first_entity',
75+
'name' => $value,
76+
])->save();
77+
78+
$entity = EntityTestStringId::create([
79+
'id' => $id,
80+
'name' => $value,
81+
]);
82+
/** @var \Symfony\Component\Validator\ConstraintViolationList $violations */
83+
$violations = $entity->get('name')->validate();
84+
85+
$message = new FormattableMarkup('A @entity_type with @field_name %value already exists.', [
86+
'%value' => $value,
87+
'@entity_type' => $entity->getEntityType()->getLowercaseLabel(),
88+
'@field_name' => 'name',
89+
]);
90+
91+
// Check that the validation has created the appropriate violation.
92+
$this->assertCount(1, $violations);
93+
$this->assertEquals($message, $violations[0]->getMessage());
94+
}
95+
96+
/**
97+
* Data provider for ::testEntityWithStringIdWithViolation().
98+
*
99+
* @return array
100+
* An array of test cases.
101+
*
102+
* @see self::testEntityWithStringIdWithViolation()
103+
*/
104+
public function providerTestEntityWithStringIdWithViolation() {
105+
return [
106+
'without an id' => [NULL],
107+
'zero as integer' => [0],
108+
'zero as string' => ["0"],
109+
'non-zero as integer' => [mt_rand(1, 127)],
110+
'non-zero as string' => [(string) mt_rand(1, 127)],
111+
'alphanumeric' => [$this->randomMachineName()],
112+
];
113+
}
114+
115+
}

0 commit comments

Comments
 (0)