From 4dd19e4a8cfeefad2a65f3f827c8fbf60f229a62 Mon Sep 17 00:00:00 2001 From: enjoys Date: Sun, 1 Jun 2025 02:09:17 +0300 Subject: [PATCH 01/12] fix WhenMissing doesn't work in Nested rules. Issue https://github.com/yiisoft/validator/issues/750 --- src/Rule/NestedHandler.php | 8 ++++-- tests/Rule/NestedTest.php | 57 +++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index 3dd3fa97f..ba0d3677e 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -7,6 +7,7 @@ use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Strings\StringHelper; use Yiisoft\Validator\DataSet\ObjectDataSet; +use Yiisoft\Validator\Exception\NestedRuleException; use Yiisoft\Validator\Exception\UnexpectedRuleException; use Yiisoft\Validator\PostValidationHookInterface; use Yiisoft\Validator\Result; @@ -89,11 +90,14 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $validatedValue = ArrayHelper::getValueByPath($data, $valuePath); if (is_int($valuePath)) { - $itemResult = $context->validate($validatedValue, $rules); + $itemResult = $context->validate($validatedValue === null ? $data : $validatedValue, $rules); } else { $valuePathList = StringHelper::parsePath($valuePath); $property = end($valuePathList); - $itemResult = $context->validate([$property => $validatedValue], [$property => $rules]); + $itemResult = $context->validate( + $validatedValue === null ? $data : [$property => $validatedValue], + [$property => $rules] + ); } if ($itemResult->isValid()) { diff --git a/tests/Rule/NestedTest.php b/tests/Rule/NestedTest.php index 2fe0a74bb..e6cd7c7cb 100644 --- a/tests/Rule/NestedTest.php +++ b/tests/Rule/NestedTest.php @@ -11,6 +11,7 @@ use stdClass; use Yiisoft\Validator\DataSet\ObjectDataSet; use Yiisoft\Validator\DataSetInterface; +use Yiisoft\Validator\EmptyCondition\WhenMissing; use Yiisoft\Validator\Error; use Yiisoft\Validator\Helper\RulesDumper; use Yiisoft\Validator\PostValidationHookInterface; @@ -29,6 +30,7 @@ use Yiisoft\Validator\Rule\Regex; use Yiisoft\Validator\Rule\Required; use Yiisoft\Validator\Rule\StringValue; +use Yiisoft\Validator\Rule\Type\BooleanType; use Yiisoft\Validator\RuleInterface; use Yiisoft\Validator\RulesProviderInterface; use Yiisoft\Validator\Tests\Rule\Base\DifferentRuleInHandlerTestTrait; @@ -1259,7 +1261,7 @@ public function hasProperty(string $property): bool ReflectionProperty::IS_PUBLIC, ), new Nested(['value' => new Required()]), - ['value' => ['Value cannot be blank.']], + ['value' => ['Value cannot be blank!.']], ], 'nested context' => [ [ @@ -1565,4 +1567,57 @@ public function testExampleFromIssue751() $this->assertFalse($result->isValid()); $this->assertSame(['array_of_a.0.id' => ['Id cannot be blank.']], $result->getErrorMessagesIndexedByPath()); } + + public function testWhenMissing(): void + { + $rules = [ + 'regulations' => new Nested( + rules: [ + 'is_one_time' => new BooleanType( + skipOnEmpty: new WhenMissing() + ) + ], + skipOnEmpty: new WhenMissing() + ), + ]; + + $data = [ + "service" => "service", + "amount" => 200 + ]; + + $result = (new Validator())->validate($data, $rules); + $this->assertTrue($result->isValid()); + + $data = [ + "service" => "service", + "amount" => 200, + "regulations" => [], + ]; + + $result = (new Validator())->validate($data, $rules); + $this->assertTrue($result->isValid()); + + $data = [ + "service" => "service", + "amount" => 200, + "regulations" => [ + 'is_one_time' => null + ], + ]; + + $result = (new Validator())->validate($data, $rules); + $this->assertFalse($result->isValid()); + + $data = [ + "service" => "service", + "amount" => 200, + "regulations" => [ + 'is_one_time' => true + ], + ]; + + $result = (new Validator())->validate($data, $rules); + $this->assertTrue($result->isValid()); + } } From 3b7932319591734e18b31bb2949d3cb806ddb653 Mon Sep 17 00:00:00 2001 From: enjoys Date: Sun, 1 Jun 2025 15:09:36 +0300 Subject: [PATCH 02/12] `Value cannot be blank.` replaced with `Value not passed.` --- tests/Rule/NestedTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Rule/NestedTest.php b/tests/Rule/NestedTest.php index e6cd7c7cb..2854870e6 100644 --- a/tests/Rule/NestedTest.php +++ b/tests/Rule/NestedTest.php @@ -1261,7 +1261,7 @@ public function hasProperty(string $property): bool ReflectionProperty::IS_PUBLIC, ), new Nested(['value' => new Required()]), - ['value' => ['Value cannot be blank!.']], + ['value' => ['Value not passed.']], ], 'nested context' => [ [ From bd3163b68d766172f33338f400e0c0ec1241c3e3 Mon Sep 17 00:00:00 2001 From: enjoys Date: Sun, 1 Jun 2025 15:44:41 +0300 Subject: [PATCH 03/12] cc --- src/Rule/NestedHandler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index ba0d3677e..6af2766c9 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -7,7 +7,6 @@ use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Strings\StringHelper; use Yiisoft\Validator\DataSet\ObjectDataSet; -use Yiisoft\Validator\Exception\NestedRuleException; use Yiisoft\Validator\Exception\UnexpectedRuleException; use Yiisoft\Validator\PostValidationHookInterface; use Yiisoft\Validator\Result; From 0600405357c2dbb7dd25e55a74020f3f3c33cae2 Mon Sep 17 00:00:00 2001 From: enjoys Date: Sun, 1 Jun 2025 15:45:35 +0300 Subject: [PATCH 04/12] add test testWhenMissingWithAllowNull() --- tests/Rule/NestedTest.php | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/Rule/NestedTest.php b/tests/Rule/NestedTest.php index 2854870e6..a61fad4d5 100644 --- a/tests/Rule/NestedTest.php +++ b/tests/Rule/NestedTest.php @@ -1568,6 +1568,9 @@ public function testExampleFromIssue751() $this->assertSame(['array_of_a.0.id' => ['Id cannot be blank.']], $result->getErrorMessagesIndexedByPath()); } + /** + * @see https://github.com/yiisoft/validator/issues/750 + */ public function testWhenMissing(): void { $rules = [ @@ -1620,4 +1623,59 @@ public function testWhenMissing(): void $result = (new Validator())->validate($data, $rules); $this->assertTrue($result->isValid()); } + + public function testWhenMissingWithAllowNull(): void + { + $rules = [ + 'regulations' => new Nested( + rules: [ + 'is_one_time' => new Callback( + callback: static function (mixed $value): Result { + $result = new Result(); + if (!in_array(get_debug_type($value), ['bool', 'null'], true)) { + $result->addError('Value must be a boolean or null.'); + } + return $result; + }, + skipOnEmpty: new WhenMissing() + ) + ], + skipOnEmpty: new WhenMissing() + ), + ]; + + $data = [ + "service" => "service", + "amount" => 200, + "regulations" => [ + 'is_one_time' => null + ], + ]; + + $result = (new Validator())->validate($data, $rules); + $this->assertTrue($result->isValid()); + + + $data = [ + "service" => "service", + "amount" => 200, + "regulations" => [ + 'is_one_time' => true + ], + ]; + + $result = (new Validator())->validate($data, $rules); + $this->assertTrue($result->isValid()); + + $data = [ + "service" => "service", + "amount" => 200, + "regulations" => [ + 'is_one_time' => "" + ], + ]; + + $result = (new Validator())->validate($data, $rules); + $this->assertFalse($result->isValid()); + } } From 8f3c7b89af4b04ad3b8405aa2d48e9246af8ca0b Mon Sep 17 00:00:00 2001 From: enjoys Date: Sun, 1 Jun 2025 15:51:08 +0300 Subject: [PATCH 05/12] rename tests --- tests/Rule/NestedTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Rule/NestedTest.php b/tests/Rule/NestedTest.php index a61fad4d5..2991040a4 100644 --- a/tests/Rule/NestedTest.php +++ b/tests/Rule/NestedTest.php @@ -1571,7 +1571,7 @@ public function testExampleFromIssue751() /** * @see https://github.com/yiisoft/validator/issues/750 */ - public function testWhenMissing(): void + public function testNestedRulesWithWhenMissing(): void { $rules = [ 'regulations' => new Nested( @@ -1624,7 +1624,7 @@ public function testWhenMissing(): void $this->assertTrue($result->isValid()); } - public function testWhenMissingWithAllowNull(): void + public function testNestedRulesWithWhenMissingAndNullableField(): void { $rules = [ 'regulations' => new Nested( From 758046924f39fb90667cb28ca5a3ebadd1249137 Mon Sep 17 00:00:00 2001 From: Enjoyzz <1448659+Enjoyzz@users.noreply.github.com> Date: Sun, 1 Jun 2025 13:03:45 +0000 Subject: [PATCH 06/12] Apply Rector changes (CI) --- src/Rule/NestedHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index 6af2766c9..54facaab4 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -89,7 +89,7 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $validatedValue = ArrayHelper::getValueByPath($data, $valuePath); if (is_int($valuePath)) { - $itemResult = $context->validate($validatedValue === null ? $data : $validatedValue, $rules); + $itemResult = $context->validate($validatedValue ?? $data, $rules); } else { $valuePathList = StringHelper::parsePath($valuePath); $property = end($valuePathList); From 042478758e3b9595a1ae898a11d680d767c164d1 Mon Sep 17 00:00:00 2001 From: enjoys Date: Thu, 5 Jun 2025 15:11:36 +0300 Subject: [PATCH 07/12] canceling a change as unnecessary --- src/Rule/NestedHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index 54facaab4..a0008dfe6 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -89,7 +89,7 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $validatedValue = ArrayHelper::getValueByPath($data, $valuePath); if (is_int($valuePath)) { - $itemResult = $context->validate($validatedValue ?? $data, $rules); + $itemResult = $context->validate($validatedValue, $rules); } else { $valuePathList = StringHelper::parsePath($valuePath); $property = end($valuePathList); From 6a198f487320c823630f96ec39c567c168150b96 Mon Sep 17 00:00:00 2001 From: enjoys Date: Fri, 20 Jun 2025 18:41:42 +0300 Subject: [PATCH 08/12] Improve array path validation with MissingValue handling Replaces null checks with MissingValue instance detection for more reliable validation of optional array paths. --- src/Helper/MissingValue.php | 13 +++++++++ src/Rule/NestedHandler.php | 5 ++-- tests/Rule/NestedTest.php | 54 ++++++++++++++++++------------------- 3 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 src/Helper/MissingValue.php diff --git a/src/Helper/MissingValue.php b/src/Helper/MissingValue.php new file mode 100644 index 000000000..a228857b2 --- /dev/null +++ b/src/Helper/MissingValue.php @@ -0,0 +1,13 @@ +validate($validatedValue, $rules); @@ -94,7 +95,7 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $valuePathList = StringHelper::parsePath($valuePath); $property = end($valuePathList); $itemResult = $context->validate( - $validatedValue === null ? $data : [$property => $validatedValue], + $validatedValue instanceof MissingValue ? [] : [$property => $validatedValue], [$property => $rules] ); } diff --git a/tests/Rule/NestedTest.php b/tests/Rule/NestedTest.php index c26c20a8f..241024962 100644 --- a/tests/Rule/NestedTest.php +++ b/tests/Rule/NestedTest.php @@ -1589,34 +1589,34 @@ public function testNestedRulesWithWhenMissing(): void rules: [ 'is_one_time' => new BooleanType( skipOnEmpty: new WhenMissing() - ) + ), ], skipOnEmpty: new WhenMissing() ), ]; $data = [ - "service" => "service", - "amount" => 200 + 'service' => 'service', + 'amount' => 200, ]; $result = (new Validator())->validate($data, $rules); $this->assertTrue($result->isValid()); $data = [ - "service" => "service", - "amount" => 200, - "regulations" => [], + 'service' => 'service', + 'amount' => 200, + 'regulations' => [], ]; $result = (new Validator())->validate($data, $rules); $this->assertTrue($result->isValid()); $data = [ - "service" => "service", - "amount" => 200, - "regulations" => [ - 'is_one_time' => null + 'service' => 'service', + 'amount' => 200, + 'regulations' => [ + 'is_one_time' => null, ], ]; @@ -1624,10 +1624,10 @@ public function testNestedRulesWithWhenMissing(): void $this->assertFalse($result->isValid()); $data = [ - "service" => "service", - "amount" => 200, - "regulations" => [ - 'is_one_time' => true + 'service' => 'service', + 'amount' => 200, + 'regulations' => [ + 'is_one_time' => true, ], ]; @@ -1649,17 +1649,17 @@ public function testNestedRulesWithWhenMissingAndNullableField(): void return $result; }, skipOnEmpty: new WhenMissing() - ) + ), ], skipOnEmpty: new WhenMissing() ), ]; $data = [ - "service" => "service", - "amount" => 200, - "regulations" => [ - 'is_one_time' => null + 'service' => 'service', + 'amount' => 200, + 'regulations' => [ + 'is_one_time' => null, ], ]; @@ -1668,10 +1668,10 @@ public function testNestedRulesWithWhenMissingAndNullableField(): void $data = [ - "service" => "service", - "amount" => 200, - "regulations" => [ - 'is_one_time' => true + 'service' => 'service', + 'amount' => 200, + 'regulations' => [ + 'is_one_time' => true, ], ]; @@ -1679,10 +1679,10 @@ public function testNestedRulesWithWhenMissingAndNullableField(): void $this->assertTrue($result->isValid()); $data = [ - "service" => "service", - "amount" => 200, - "regulations" => [ - 'is_one_time' => "" + 'service' => 'service', + 'amount' => 200, + 'regulations' => [ + 'is_one_time' => '', ], ]; From d1725c63df433dbcc10d2064f109f617746f09d9 Mon Sep 17 00:00:00 2001 From: enjoys Date: Mon, 23 Jun 2025 10:21:45 +0300 Subject: [PATCH 09/12] Abandoning 'MissingValue' in favor of 'ArrayHelper::pathExists()' --- src/Helper/MissingValue.php | 13 ------------- src/Rule/NestedHandler.php | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 src/Helper/MissingValue.php diff --git a/src/Helper/MissingValue.php b/src/Helper/MissingValue.php deleted file mode 100644 index a228857b2..000000000 --- a/src/Helper/MissingValue.php +++ /dev/null @@ -1,13 +0,0 @@ -validate($validatedValue, $rules); @@ -95,7 +95,7 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $valuePathList = StringHelper::parsePath($valuePath); $property = end($valuePathList); $itemResult = $context->validate( - $validatedValue instanceof MissingValue ? [] : [$property => $validatedValue], + ArrayHelper::pathExists($data, $valuePath) ? [$property => $validatedValue] : [], [$property => $rules] ); } From 1840ff438df0075bc3210c1c5b6b318930bb5166 Mon Sep 17 00:00:00 2001 From: enjoys Date: Mon, 23 Jun 2025 10:24:20 +0300 Subject: [PATCH 10/12] Remove the non-existent `use` --- src/Rule/NestedHandler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index ebcbf7555..4df6d323d 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -8,7 +8,6 @@ use Yiisoft\Strings\StringHelper; use Yiisoft\Validator\DataSet\ObjectDataSet; use Yiisoft\Validator\Exception\UnexpectedRuleException; -use Yiisoft\Validator\Helper\MissingValue; use Yiisoft\Validator\PostValidationHookInterface; use Yiisoft\Validator\Result; use Yiisoft\Validator\RuleHandlerInterface; From 19ac593be720f5dd6f2cf4c60c4a9fd1cb8f2574 Mon Sep 17 00:00:00 2001 From: enjoys Date: Mon, 23 Jun 2025 15:48:45 +0300 Subject: [PATCH 11/12] Use `ArrayHelper::keyExists` instead of `ArrayHelper::pathExists` --- src/Rule/NestedHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index 4df6d323d..0d7bd406a 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -94,7 +94,7 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c $valuePathList = StringHelper::parsePath($valuePath); $property = end($valuePathList); $itemResult = $context->validate( - ArrayHelper::pathExists($data, $valuePath) ? [$property => $validatedValue] : [], + ArrayHelper::keyExists($data, $valuePathList) ? [$property => $validatedValue] : [], [$property => $rules] ); } From 57b8c9f97254e35d2d1f78e7dbac2a3cf2be617f Mon Sep 17 00:00:00 2001 From: enjoys Date: Mon, 23 Jun 2025 15:52:12 +0300 Subject: [PATCH 12/12] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f253d691..1543ecb46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2.3.1 under development +- Bug #750: Fix `WhenMissing` not working in nested rules (@Enjoyzz) - Enh #770: Allow use callable rules into `Nested` (@Enjoyzz) - Bug #751: Fix incorrect `Nested` rule processing within `Each` (@Enjoyzz)