diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d91eca8..e180762a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.6.1 under development -- no changes in this release. +- Bug #816: 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..31f1ecf0 100644 --- a/src/Rule/UniqueIterableHandler.php +++ b/src/Rule/UniqueIterableHandler.php @@ -57,20 +57,20 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $previousItem = $item; - if (!empty($stack) && count($stack) !== count(array_unique($stack, flags: SORT_REGULAR))) { + if ($item instanceof DateTimeInterface) { + $stack[] = $item->getTimestamp(); + } elseif ($item instanceof Stringable) { + $stack[] = (string) $item; + } else { + $stack[] = $item; + } + + if (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(); - } else { - $stack[] = $value; - } } return new Result(); diff --git a/tests/Rule/UniqueIterableTest.php b/tests/Rule/UniqueIterableTest.php index d2f835c0..e35aa33f 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,33 @@ public function getRules(): array null, ['data' => ['"Данные" - в списке есть недопустимое значение.']], ], + 'two equal strings' => [['a', 'a'], new UniqueIterable(), ['' => [$message]]], + '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]]],