diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ca5ff1b..93df7e243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.3.1 under development -- no changes in this release. +- Bug #751: Fix incorrect `Nested` rule processing within `Each` (@Enjoyzz) ## 2.3.0 May 07, 2025 diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index e5e497f57..3dd3fa97f 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -30,9 +30,6 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c throw new UnexpectedRuleException(Nested::class, $rule); } - /** @var mixed $value */ - $value = $context->getParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY) ?? $value; - if ($rule->getRules() === null) { if (!is_object($value)) { return (new Result())->addError($rule->getNoRulesWithNoObjectMessage(), [ @@ -47,6 +44,8 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c return $context->validate($dataSet); } + $value = $context->getParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY) ?? $value; + if (is_array($value)) { $data = $value; } elseif (is_object($value)) { @@ -79,7 +78,8 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c [ 'path' => $valuePath, 'property' => $context->getTranslatedProperty(), - 'Property' => $context->getCapitalizedTranslatedProperty(), ], + 'Property' => $context->getCapitalizedTranslatedProperty(), + ], $valuePathList, ); diff --git a/tests/Rule/NestedTest.php b/tests/Rule/NestedTest.php index 58f63259e..2fe0a74bb 100644 --- a/tests/Rule/NestedTest.php +++ b/tests/Rule/NestedTest.php @@ -39,14 +39,16 @@ use Yiisoft\Validator\Tests\Support\Data\EachNestedObjects\Foo; use Yiisoft\Validator\Tests\Support\Data\InheritAttributesObject\InheritAttributesObject; use Yiisoft\Validator\Tests\Support\Data\IteratorWithBooleanKey; +use Yiisoft\Validator\Tests\Support\Data\NestedClassAttribute; use Yiisoft\Validator\Tests\Support\Data\NestedHookProvider\NestedObjectWithPostValidationHook; +use Yiisoft\Validator\Tests\Support\Data\NestedIterableOfObjects; +use Yiisoft\Validator\Tests\Support\Data\NestedWithCallbackAttribute; +use Yiisoft\Validator\Tests\Support\Data\ObjectForIterableCollection; use Yiisoft\Validator\Tests\Support\Data\ObjectWithDifferentPropertyVisibility; use Yiisoft\Validator\Tests\Support\Data\ObjectWithNestedObject; use Yiisoft\Validator\Tests\Support\Helper\OptionsHelper; use Yiisoft\Validator\Tests\Support\Rule\StubRule\StubDumpedRule; use Yiisoft\Validator\Tests\Support\RulesProvider\SimpleRulesProvider; -use Yiisoft\Validator\Tests\Support\Data\NestedClassAttribute; -use Yiisoft\Validator\Tests\Support\Data\NestedWithCallbackAttribute; use Yiisoft\Validator\ValidationContext; use Yiisoft\Validator\Validator; @@ -879,7 +881,7 @@ public function testWithOtherNestedAndEach( $result = (new Validator())->validate($data, $rules); $errorsData = array_map( - static fn (Error $error) => [ + static fn(Error $error) => [ $error->getMessage(), $error->getValuePath(), ], @@ -1424,7 +1426,7 @@ public function testValidationFailedWithDetailedErrors(mixed $data, array $rules $result = (new Validator())->validate($data, $rules); $errorsData = array_map( - static fn (Error $error) => [ + static fn(Error $error) => [ $error->getMessage(), $error->getValuePath(), ], @@ -1455,7 +1457,7 @@ public function testSkipOnError(): void public function testWhen(): void { - $when = static fn (mixed $value): bool => $value !== null; + $when = static fn(mixed $value): bool => $value !== null; $this->testWhenInternal(new Nested(), new Nested(when: $when)); } @@ -1499,4 +1501,68 @@ protected function getDifferentRuleInHandlerItems(): array { return [Nested::class, NestedHandler::class]; } + + public function testWithArrayOfObjects(): void + { + $obj = (new NestedIterableOfObjects())->setCollection([ + new ObjectForIterableCollection('', ''), + ]); + $result = (new Validator())->validate($obj); + $this->assertFalse($result->isValid()); + $this->assertCount(4, $result->getErrorMessages()); + + $obj = (new NestedIterableOfObjects())->setCollection([ + new ObjectForIterableCollection('12345', 'myName'), + ]); + $result = (new Validator())->validate($obj); + $this->assertTrue($result->isValid()); + $this->assertCount(0, $result->getErrorMessages()); + } + + public function testWithNonArrayButIterableOfObjects(): void + { + $classA = new class () { + #[Required] + #[Length(min: 10)] + public $id; + }; + + $classB = new class () { + #[Each(new Nested())] + public iterable $collection; + }; + $collection = new \ArrayIterator([$classA]); + $classB->collection = $collection; + $result = (new Validator())->validate($classB); + + $this->assertFalse($result->isValid()); + $this->assertSame([ + 'collection.0.id' => [ + 'Id cannot be blank.', + 'Id must be a string. null given.', + ], + ], $result->getErrorMessagesIndexedByPath()); + } + + /** + * @see https://github.com/yiisoft/validator/issues/751 + */ + public function testExampleFromIssue751() + { + $classA = new class () { + #[Required] + public $id; + }; + + $classB = new class () { + #[Each([new Nested()])] + public $array_of_a; + }; + + $classB->array_of_a = [$classA]; + $result = (new Validator())->validate($classB); + + $this->assertFalse($result->isValid()); + $this->assertSame(['array_of_a.0.id' => ['Id cannot be blank.']], $result->getErrorMessagesIndexedByPath()); + } } diff --git a/tests/Support/Data/NestedIterableOfObjects.php b/tests/Support/Data/NestedIterableOfObjects.php new file mode 100644 index 000000000..a39c6401c --- /dev/null +++ b/tests/Support/Data/NestedIterableOfObjects.php @@ -0,0 +1,28 @@ + + */ + #[Each(new Nested())] + private iterable $collection = []; + + public function setCollection(iterable $collection): self + { + $this->collection = $collection; + return $this; + } + + public function getCollection(): iterable + { + return $this->collection; + } +} diff --git a/tests/Support/Data/ObjectForIterableCollection.php b/tests/Support/Data/ObjectForIterableCollection.php new file mode 100644 index 000000000..1fca5900f --- /dev/null +++ b/tests/Support/Data/ObjectForIterableCollection.php @@ -0,0 +1,21 @@ +