diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d91eca8..f59a4807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.6.1 under development -- no changes in this release. +- Bug #817: Fix `UniqueIterable` comparing the whole iterable instead of its items (@CorvusSharp) ## 2.6.0 June 02, 2026 diff --git a/src/Rule/UniqueIterableHandler.php b/src/Rule/UniqueIterableHandler.php index a06cb7a3..7f20739c 100644 --- a/src/Rule/UniqueIterableHandler.php +++ b/src/Rule/UniqueIterableHandler.php @@ -57,22 +57,22 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $previousItem = $item; - if (!empty($stack) && count($stack) !== count(array_unique($stack, flags: SORT_REGULAR))) { - return (new Result())->addError($rule->getMessage(), [ - 'property' => $context->getTranslatedProperty(), - 'Property' => $context->getCapitalizedTranslatedProperty(), - ]); - } - - if ($value instanceof Stringable) { - $stack[] = (string) $value; - } elseif ($value instanceof DateTimeInterface) { - $stack[] = $value->getTimestamp(); + if ($item instanceof Stringable) { + $stack[] = (string) $item; + } elseif ($item instanceof DateTimeInterface) { + $stack[] = $item->getTimestamp(); } else { - $stack[] = $value; + $stack[] = $item; } } + if (count($stack) !== count(array_unique($stack, flags: SORT_REGULAR))) { + return (new Result())->addError($rule->getMessage(), [ + 'property' => $context->getTranslatedProperty(), + 'Property' => $context->getCapitalizedTranslatedProperty(), + ]); + } + return new Result(); } diff --git a/tests/Rule/UniqueIterableTest.php b/tests/Rule/UniqueIterableTest.php index d2f835c0..ac6031d7 100644 --- a/tests/Rule/UniqueIterableTest.php +++ b/tests/Rule/UniqueIterableTest.php @@ -120,6 +120,40 @@ public function __toString() [new DateTime('2024-04-10 14:05:01'), new DateTime('2024-04-10 14:05:02')], new UniqueIterable(), ], + 'more than two unique strings' => [['a', 'b', 'c'], new UniqueIterable()], + 'more than two unique integers' => [[1, 2, 3, 4], new UniqueIterable()], + 'more than two unique floats' => [[1.5, 2.5, 3.5], new UniqueIterable()], + 'more than two unique stringable values' => [ + [ + new class implements Stringable { + public function __toString() + { + return 'a'; + } + }, + new class implements Stringable { + public function __toString() + { + return 'b'; + } + }, + new class implements Stringable { + public function __toString() + { + return 'c'; + } + }, + ], + new UniqueIterable(), + ], + 'more than two unique datetime values' => [ + [ + new DateTime('2024-04-10 14:05:01'), + new DateTime('2024-04-10 14:05:02'), + new DateTime('2024-04-10 14:05:03'), + ], + new UniqueIterable(), + ], 'using as attribute' => [ new class { #[UniqueIterable] @@ -213,6 +247,43 @@ public function getRules(): array null, ['data' => ['"Данные" - в списке есть недопустимое значение.']], ], + 'two equal strings' => [['a', 'a'], new UniqueIterable(), ['' => [$message]]], + 'duplicate loses to a later disallowed item' => [ + [1, 1, []], + new UniqueIterable(), + ['' => [$incorrectItemValueMessage]], + ], + 'duplicate loses to a later type mismatch' => [ + ['data' => [1, 1, 'a']], + ['data' => new UniqueIterable()], + ['data' => ['All iterable items of data must have the same type.']], + ], + 'two equal integers' => [[1, 1], new UniqueIterable(), ['' => [$message]]], + 'two equal floats' => [[1.5, 1.5], new UniqueIterable(), ['' => [$message]]], + 'two equal boolean values' => [[true, true], new UniqueIterable(), ['' => [$message]]], + 'two equal stringable values' => [ + [ + new class implements Stringable { + public function __toString() + { + return 'a'; + } + }, + new class implements Stringable { + public function __toString() + { + return 'a'; + } + }, + ], + new UniqueIterable(), + ['' => [$message]], + ], + 'two equal datetime values' => [ + [new DateTime('2024-04-10 14:05:01'), new DateTime('2024-04-10 14:05:01')], + new UniqueIterable(), + ['' => [$message]], + ], 'strings' => [['a', 'b', 'a', 'c'], new UniqueIterable(), ['' => [$message]]], 'integers' => [[1, 2, 1, 3], new UniqueIterable(), ['' => [$message]]], 'floats' => [[1.5, 2.5, 1.5, 3.5], new UniqueIterable(), ['' => [$message]]],