Create "Lazy" rule

This rule resembles a proper replacement for the old "KeyValue" rule
rather than the "LazyConsecutive" rule[1]. I will soon delete the
"LazyConsecutive" rule and replace it with something else.

[1]: 41245f663f

Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
Henrique Moody 2024-03-06 20:43:55 +01:00
parent df16ddaf48
commit 78715fb844
No known key found for this signature in database
GPG key ID: 221E9281655813A6
9 changed files with 261 additions and 0 deletions

View file

@ -39,6 +39,7 @@
- [Call](rules/Call.md)
- [CallableType](rules/CallableType.md)
- [Callback](rules/Callback.md)
- [Lazy](rules/Lazy.md)
- [LazyConsecutive](rules/LazyConsecutive.md)
## Comparisons
@ -163,6 +164,7 @@
- [Each](rules/Each.md)
- [Key](rules/Key.md)
- [KeySet](rules/KeySet.md)
- [Lazy](rules/Lazy.md)
- [LazyConsecutive](rules/LazyConsecutive.md)
- [NoneOf](rules/NoneOf.md)
- [Not](rules/Not.md)
@ -359,6 +361,7 @@
- [KeyOptional](rules/KeyOptional.md)
- [KeySet](rules/KeySet.md)
- [LanguageCode](rules/LanguageCode.md)
- [Lazy](rules/Lazy.md)
- [LazyConsecutive](rules/LazyConsecutive.md)
- [LeapDate](rules/LeapDate.md)
- [LeapYear](rules/LeapYear.md)

View file

@ -58,5 +58,6 @@ See also:
- [Callback](Callback.md)
- [Each](Each.md)
- [Lazy](Lazy.md)
- [LazyConsecutive](LazyConsecutive.md)
- [Sorted](Sorted.md)

View file

@ -30,6 +30,7 @@ See also:
- [Callback](Callback.md)
- [FloatType](FloatType.md)
- [IntType](IntType.md)
- [Lazy](Lazy.md)
- [NullType](NullType.md)
- [Number](Number.md)
- [ObjectType](ObjectType.md)

41
docs/rules/Lazy.md Normal file
View file

@ -0,0 +1,41 @@
# Lazy
- `Lazy(callable(mixed $input): Validatable $ruleCreator)`
Validates the input using a rule that is created from a callback.
This rule is particularly useful when creating rules that rely on the input. A good example is validating whether a
`confirmation` field matches the `password` field when processing data from a form.
```php
v::key('confirmation', v::equals($_POST['password'] ?? null))->validate($_POST);
```
The issue with the code is that its hard to reuse because youre relying upon the input itself (`$_POST`). That means
you can create a chain of rules and use it everywhere.
The `lazy()` rule makes this job much simpler and more elegantly:
```php
v::lazy(static fn($input) => v::key('confirmation', v::equals($input['password'] ?? null)))->validate($_POST);
```
The code above is similar to the first example, but the biggest difference is that the creation of the rule doesn't rely
on the input itself (`$_POST`), but it will use any input thats given to the rule
## Categorization
- Callables
- Nesting
## Changelog
| Version | Description |
|--------:|-------------------------|
| 3.0.0 | Created from `KeyValue` |
***
See also:
- [Call](Call.md)
- [CallableType](CallableType.md)

View file

@ -172,6 +172,9 @@ interface ChainedValidator extends Validatable
public function keySet(Validatable $rule, Validatable ...$rules): ChainedValidator;
/** @param callable(mixed): Validatable $ruleCreator */
public function lazy(callable $ruleCreator): ChainedValidator;
public function lazyConsecutive(callable $ruleCreator, callable ...$ruleCreators): ChainedValidator;
/** @param "alpha-2"|"alpha-3" $set */

View file

@ -174,6 +174,9 @@ interface StaticValidator
public static function keySet(Validatable $rule, Validatable ...$rules): ChainedValidator;
/** @param callable(mixed): Validatable $ruleCreator */
public static function lazy(callable $ruleCreator): ChainedValidator;
public static function lazyConsecutive(callable $ruleCreator, callable ...$ruleCreators): ChainedValidator;
/** @param "alpha-2"|"alpha-3" $set */

44
library/Rules/Lazy.php Normal file
View file

@ -0,0 +1,44 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Helpers\CanBindEvaluateRule;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
use Respect\Validation\Validatable;
use function call_user_func;
final class Lazy extends Standard
{
use CanBindEvaluateRule;
/** @var callable(mixed): Validatable */
private $ruleCreator;
/**
* @param callable(mixed): Validatable $ruleCreator
*/
public function __construct(callable $ruleCreator)
{
$this->ruleCreator = $ruleCreator;
}
public function evaluate(mixed $input): Result
{
$rule = call_user_func($this->ruleCreator, $input);
if (!$rule instanceof Validatable) {
throw new ComponentException('Lazy failed because it could not create the rule');
}
return $this->bindEvaluate($rule, $this, $input);
}
}

View file

@ -0,0 +1,104 @@
--FILE--
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use Respect\Validation\Validator as v;
run([
'Default' => [v::lazy(static fn() => v::intType()), true],
'Negative' => [v::not(v::lazy(static fn() => v::intType())), 2],
'With created name, default' => [
v::lazy(static fn() => v::intType()->setName('Created'))->setName('Wrapper'),
true,
],
'With wrapper name, default' => [
v::lazy(static fn() => v::intType())->setName('Wrapper'),
true,
],
'With created name, negative' => [
v::not(v::lazy(static fn() => v::intType()->setName('Created'))->setName('Wrapped'))->setName('Not'),
2,
],
'With wrapper name, negative' => [
v::not(v::lazy(static fn() => v::intType())->setName('Wrapped'))->setName('Not'),
2,
],
'With not name, negative' => [
v::not(v::lazy(static fn() => v::intType()))->setName('Not'),
2,
],
'With template, default' => [
v::lazy(static fn() => v::intType()),
true,
'Lazy lizards lounging like lords in the local lagoon',
],
]);
?>
--EXPECT--
Default
⎺⎺⎺⎺⎺⎺⎺
`true` must be of type integer
- `true` must be of type integer
[
'intType' => '`true` must be of type integer',
]
Negative
⎺⎺⎺⎺⎺⎺⎺⎺
2 must not be of type integer
- 2 must not be of type integer
[
'intType' => '2 must not be of type integer',
]
With created name, default
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Created must be of type integer
- Created must be of type integer
[
'Created' => 'Created must be of type integer',
]
With wrapper name, default
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Wrapper must be of type integer
- Wrapper must be of type integer
[
'Wrapper' => 'Wrapper must be of type integer',
]
With created name, negative
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Created must not be of type integer
- Created must not be of type integer
[
'Created' => 'Created must not be of type integer',
]
With wrapper name, negative
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Wrapped must not be of type integer
- Wrapped must not be of type integer
[
'Wrapped' => 'Wrapped must not be of type integer',
]
With not name, negative
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Not must not be of type integer
- Not must not be of type integer
[
'Not' => 'Not must not be of type integer',
]
With template, default
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Lazy lizards lounging like lords in the local lagoon
- Lazy lizards lounging like lords in the local lagoon
[
'intType' => 'Lazy lizards lounging like lords in the local lagoon',
]

View file

@ -0,0 +1,61 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Test\Rules\Stub;
use Respect\Validation\Test\TestCase;
#[Group('rule')]
#[CoversClass(Lazy::class)]
final class LazyTest extends TestCase
{
#[Test]
public function itShouldThrowAnExceptionWhenRuleCreatorDoesNotReturnValidatable(): void
{
// @phpstan-ignore-next-line
$rule = new Lazy(static fn () => null);
$this->expectException(ComponentException::class);
$this->expectExceptionMessage('Lazy failed because it could not create the rule');
$rule->evaluate('something');
}
#[Test]
#[DataProvider('providerForAnyValues')]
public function itShouldInvalidInputWhenCreatedRuleFails(mixed $input): void
{
self::assertInvalidInput(new Lazy(static fn ($creatorInput) => Stub::fail(1)), $input);
}
#[Test]
#[DataProvider('providerForAnyValues')]
public function itShouldValidInputWhenCreatedRulePasses(mixed $input): void
{
self::assertValidInput(new Lazy(static fn ($creatorInput) => Stub::pass(1)), $input);
}
#[Test]
#[DataProvider('providerForAnyValues')]
public function itShouldReturnTheResultFromTheCreatedRule(mixed $input): void
{
$expected = Stub::fail(1)->evaluate($input);
$rule = new Lazy(static fn ($creatorInput) => Stub::fail(1));
$actual = $rule->evaluate($input);
self::assertEquals($expected, $actual);
}
}