Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 2.5.2 under development

- Bug #752: Allow access to sibling properties in nested validation context (@WarLikeLaux)
- Enh #787: Explicitly import classes, functions, and constants in "use" section (@mspirkov)
- Bug #793: Fix translations, broken link in contributing guide, incorrect imports and grammar in documentation (@evilkarter)
- Chg #795: Update Polish translations (@rbrzezinski)
Expand Down
22 changes: 19 additions & 3 deletions src/Rule/NestedHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use function is_array;
use function is_int;
use function is_object;
use function array_key_exists;
use function count;

/**
* A handler for {@see Nested} rule. Validates nested structures.
Expand Down Expand Up @@ -86,15 +88,29 @@ public function validate(mixed $value, RuleInterface $rule, ValidationContext $c
continue;
}

$validatedValue = ArrayHelper::getValueByPath($data, $valuePath);

if (is_int($valuePath)) {
$validatedValue = ArrayHelper::getValueByPath($data, $valuePath);
$itemResult = $context->validate($validatedValue, $rules);
} else {
$valuePathList = StringHelper::parsePath($valuePath);
$property = end($valuePathList);

$scopeData = $data;
for ($i = 0, $limit = count($valuePathList) - 1; $i < $limit; $i++) {
$key = $valuePathList[$i];
if (!is_array($scopeData) || !array_key_exists($key, $scopeData)) {
$scopeData = [];
Comment thread
WarLikeLaux marked this conversation as resolved.
break;
}
$scopeData = $scopeData[$key];
}

if (!is_array($scopeData)) {
$scopeData = [];
}

$itemResult = $context->validate(
ArrayHelper::keyExists($data, $valuePathList) ? [$property => $validatedValue] : [],
$scopeData,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
[$property => $rules],
);
}
Expand Down
95 changes: 95 additions & 0 deletions tests/Rule/Nested/NestedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,54 @@ public function getRules(): iterable
['' => 17],
new Nested(['' => new Integer(min: 15)]),
],
'sibling access skips rule when sibling is false' => [
[
'push' => [
'isEnabled' => false,
'content' => null,
],
],
new Nested([
'push' => new Nested([
'isEnabled' => [new BooleanValue()],
'content' => [
new Required(
when: static function (mixed $value, ValidationContext $context): bool {
return (bool) $context->getDataSet()->getPropertyValue('isEnabled');
},
),
],
]),
]),
],
'sibling access with each and nested' => [
[
'tasks' => [
[
'push' => [
'isEnabled' => false,
'content' => null,
],
],
],
],
new Nested([
'tasks' => new Each(
new Nested([
'push' => new Nested([
'isEnabled' => [new BooleanValue()],
'content' => [
new Required(
when: static function (mixed $value, ValidationContext $context): bool {
return (bool) $context->getDataSet()->getPropertyValue('isEnabled');
},
),
],
]),
]),
),
]),
],
];
}

Expand Down Expand Up @@ -1284,6 +1332,29 @@ public function hasProperty(string $property): bool
'properties.abc' => ['Abc cannot be blank.'],
],
],
'sibling access in when callback' => [
[
'push' => [
'isEnabled' => true,
'content' => null,
],
],
new Nested([
'push' => new Nested([
'isEnabled' => [new BooleanValue()],
'content' => [
new Required(
when: static function (mixed $value, ValidationContext $context): bool {
return (bool) $context->getDataSet()->getPropertyValue('isEnabled');
},
),
],
]),
]),
[
'push.content' => ['Content cannot be blank.'],
],
],
'deep level of nesting with plain keys' => [
[
'level1' => [
Expand Down Expand Up @@ -1313,6 +1384,30 @@ public function hasProperty(string $property): bool
'level1.level2.level3.name' => ['Name must contain at least 5 characters.'],
],
],
'dotted path with missing intermediate key' => [
[
'level1' => [
'x' => 1,
],
],
new Nested([
'level1.level2.key' => [new Required()],
]),
[
'level1.level2.key' => ['Key not passed.'],
],
],
'dotted path with scalar intermediate value' => [
[
'level1' => 'scalar',
],
new Nested([
'level1.level2' => [new Required()],
]),
[
'level1.level2' => ['Level2 not passed.'],
],
],
'error messages with properties in nested structure' => [
[
'user' => [
Expand Down
Loading