Skip to content
Merged
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.3.1 under development

- Enh #770: Allow use callable rules into `Nested` (@Enjoyzz)
- Bug #751: Fix incorrect `Nested` rule processing within `Each` (@Enjoyzz)

## 2.3.0 May 07, 2025
Expand Down
9 changes: 5 additions & 4 deletions src/Rule/Nested.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
* validated value is empty / not passed. See {@see SkipOnEmptyInterface}.
* @param bool $skipOnError Whether to skip this `Nested` rule with all defined {@see $rules} if any of the previous
* rules gave an error. See {@see SkipOnErrorInterface}.
* @param Closure|null $when A callable to define a condition for applying this `Nested` rule with all defined
* @param Closure|null $when A callable to define a condition for applying this `Nested` rule with all defined
* {@see $rules}. See {@see WhenInterface}.
*
* @psalm-param SkipOnEmptyValue $skipOnEmpty
Expand Down Expand Up @@ -453,13 +453,14 @@
$rules = iterator_to_array($rules);
}

/** @var mixed $rule */
foreach ($rules as &$rule) {
if (is_iterable($rule)) {
if (is_callable($rule)) {
$rule = new Callback($rule);
} elseif (is_iterable($rule)) {

Check warning on line 459 in src/Rule/Nested.php

View check run for this annotation

Codecov / codecov/patch

src/Rule/Nested.php#L457-L459

Added lines #L457 - L459 were not covered by tests
self::ensureArrayHasRules($rule);
} elseif (!$rule instanceof RuleInterface) {
$message = sprintf(
'Every rule must be an instance of %s, %s given.',
'Every rule must be an instance of %s or a callable, %s given.',

Check warning on line 463 in src/Rule/Nested.php

View check run for this annotation

Codecov / codecov/patch

src/Rule/Nested.php#L463

Added line #L463 was not covered by tests
RuleInterface::class,
get_debug_type($rule)
);
Expand Down
15 changes: 13 additions & 2 deletions tests/Rule/NestedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public function testGetOptionsWithNotRule(): void
$this->expectException(InvalidArgumentException::class);

$ruleInterfaceName = RuleInterface::class;
$message = "Every rule must be an instance of $ruleInterfaceName, class@anonymous given.";
$message = "Every rule must be an instance of $ruleInterfaceName or a callable, class@anonymous given.";
$this->expectExceptionMessage($message);

$rule = new Nested([
Expand Down Expand Up @@ -1440,7 +1440,7 @@ public function testValidationFailedWithDetailedErrors(mixed $data, array $rules
public function testInitWithNotARule(): void
{
$this->expectException(InvalidArgumentException::class);
$message = 'Every rule must be an instance of Yiisoft\Validator\RuleInterface, string given.';
$message = 'Every rule must be an instance of Yiisoft\Validator\RuleInterface or a callable, string given.';
$this->expectExceptionMessage($message);
new Nested([
'data' => new Nested([
Expand Down Expand Up @@ -1502,6 +1502,17 @@ protected function getDifferentRuleInHandlerItems(): array
return [Nested::class, NestedHandler::class];
}

public function testUseCallableRules()
{
$rules = new Nested([
'tag' => static fn() => (new Result())->addError('Too short.'),
]);
$data = ['tag' => 'value'];

$result = (new Validator())->validate($data, $rules);
$this->assertSame(['tag' => ['Too short.']], $result->getErrorMessagesIndexedByPath());
}

public function testWithArrayOfObjects(): void
{
$obj = (new NestedIterableOfObjects())->setCollection([
Expand Down
Loading