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) diff --git a/src/Rule/NestedHandler.php b/src/Rule/NestedHandler.php index 3dd3fa97f..0d7bd406a 100644 --- a/src/Rule/NestedHandler.php +++ b/src/Rule/NestedHandler.php @@ -93,7 +93,10 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c } else { $valuePathList = StringHelper::parsePath($valuePath); $property = end($valuePathList); - $itemResult = $context->validate([$property => $validatedValue], [$property => $rules]); + $itemResult = $context->validate( + ArrayHelper::keyExists($data, $valuePathList) ? [$property => $validatedValue] : [], + [$property => $rules] + ); } if ($itemResult->isValid()) { diff --git a/tests/Rule/NestedTest.php b/tests/Rule/NestedTest.php index 25eeddd34..241024962 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 not passed.']], ], 'nested context' => [ [ @@ -1576,4 +1578,115 @@ public function testExampleFromIssue751() $this->assertFalse($result->isValid()); $this->assertSame(['array_of_a.0.id' => ['Id cannot be blank.']], $result->getErrorMessagesIndexedByPath()); } + + /** + * @see https://github.com/yiisoft/validator/issues/750 + */ + public function testNestedRulesWithWhenMissing(): 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()); + } + + public function testNestedRulesWithWhenMissingAndNullableField(): 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()); + } }