Skip to content

Commit 2aa5e39

Browse files
committed
Improve KeySet rule
After changes in the key-related rules, the KeySet rule became unusable. Besides, when evaluating an input, it wasn't reporting every single failure because it would not validate the items in the array if they had missing or extra keys. This commit will make several improvements to the rule. It will create some not(keyExists($key)) rules for the extra keys, which makes the error reporting much better. A limit of 10 additional keys will show up when asserting an input with extra keys. I put that limit in place to prevent the creation of too many rules.
1 parent 61e9c0c commit 2aa5e39

File tree

9 files changed

+359
-174
lines changed

9 files changed

+359
-174
lines changed

docs/rules/KeySet.md

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,60 @@
11
# KeySet
22

3-
- `KeySet(Key $rule, Key ...$rules)`
3+
- `KeySet(KeyRelated $rule, KeyRelated ...$rules)`
44

55
Validates a keys in a defined structure.
66

77
```php
8-
$dict = ['foo' => 42];
8+
v::keySet(
9+
v::keyExists('foo'),
10+
v::keyExists('bar')
11+
)->validate(['foo' => 'whatever', 'bar' => 'something']); // true
12+
```
913

14+
It will validate the keys in the array with the rules passed in the constructor.
15+
```php
1016
v::keySet(
1117
v::key('foo', v::intVal())
12-
)->validate($dict); // true
18+
)->validate(['foo' => 42]); // true
19+
20+
v::keySet(
21+
v::key('foo', v::intVal())
22+
)->validate(['foo' => 'string']); // false
1323
```
1424

1525
Extra keys are not allowed:
1626
```php
17-
$dict = ['foo' => 42, 'bar' => 'String'];
18-
1927
v::keySet(
2028
v::key('foo', v::intVal())
21-
)->validate($dict); // false
29+
)->validate(['foo' => 42, 'bar' => 'String']); // false
2230
```
2331

2432
Missing required keys are not allowed:
2533
```php
26-
$dict = ['foo' => 42, 'bar' => 'String'];
27-
2834
v::keySet(
2935
v::key('foo', v::intVal()),
3036
v::key('bar', v::stringType()),
3137
v::key('baz', v::boolType())
32-
)->validate($dict); // false
38+
)->validate(['foo' => 42, 'bar' => 'String']); // false
3339
```
3440

3541
Missing non-required keys are allowed:
3642
```php
37-
$dict = ['foo' => 42, 'bar' => 'String'];
38-
3943
v::keySet(
4044
v::key('foo', v::intVal()),
4145
v::key('bar', v::stringType()),
42-
v::key('baz', v::boolType(), false)
43-
)->validate($dict); // true
46+
v::keyOptional('baz', v::boolType())
47+
)->validate(['foo' => 42, 'bar' => 'String']); // true
48+
```
49+
50+
Alternatively, you can pass a chain of key-related rules to `keySet()`:
51+
```php
52+
v::keySet(
53+
v::create()
54+
->key('foo', v::intVal())
55+
->key('bar', v::stringType())
56+
->keyOptional('baz', v::boolType())
57+
)->validate(['foo' => 42, 'bar' => 'String']); // true
4458
```
4559

4660
It is not possible to negate `keySet()` rules with `not()`.
@@ -55,11 +69,11 @@ The keys' order is not considered in the validation.
5569

5670
## Changelog
5771

58-
Version | Description
59-
--------|-------------
60-
3.0.0 | Require at one rule to be passed
61-
2.3.0 | KeySet is NonNegatable, fixed message with extra keys
62-
1.0.0 | Created
72+
| Version | Description |
73+
|--------:|-------------------------------------------------------|
74+
| 3.0.0 | Requires at least one key-related rule |
75+
| 2.3.0 | KeySet is NonNegatable, fixed message with extra keys |
76+
| 1.0.0 | Created |
6377

6478
***
6579
See also:

library/Helpers/CanExtractRules.php

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,16 @@
99

1010
namespace Respect\Validation\Helpers;
1111

12-
use Respect\Validation\Exceptions\ComponentException;
1312
use Respect\Validation\Rules\Core\Composite;
1413
use Respect\Validation\Rules\Not;
1514
use Respect\Validation\Validatable;
1615
use Respect\Validation\Validator;
1716
use Throwable;
1817

19-
use function array_map;
2018
use function count;
21-
use function current;
22-
use function sprintf;
2319

2420
trait CanExtractRules
2521
{
26-
private function extractSingle(Validatable $rule, string $class): Validatable
27-
{
28-
if ($rule instanceof Validator) {
29-
return $this->extractSingleFromValidator($rule, $class);
30-
}
31-
32-
if (!$rule instanceof $class) {
33-
throw new ComponentException(sprintf(
34-
'Could not extract rule %s from %s',
35-
$class,
36-
$rule::class,
37-
));
38-
}
39-
40-
return $rule;
41-
}
42-
4322
private function extractSiblingSuitableRule(Validatable $rule, Throwable $throwable): Validatable
4423
{
4524
$this->assertSingleRule($rule, $throwable);
@@ -73,26 +52,4 @@ private function assertSingleRule(Validatable $rule, Throwable $throwable): void
7352
throw $throwable;
7453
}
7554
}
76-
77-
/**
78-
* @param array<Validatable> $rules
79-
*
80-
* @return array<Validatable>
81-
*/
82-
private function extractMany(array $rules, string $class): array
83-
{
84-
return array_map(fn (Validatable $rule) => $this->extractSingle($rule, $class), $rules);
85-
}
86-
87-
private function extractSingleFromValidator(Validator $rule, string $class): Validatable
88-
{
89-
$rules = $rule->getRules();
90-
if (count($rules) !== 1) {
91-
throw new ComponentException(sprintf(
92-
'Validator must contain exactly one rule'
93-
));
94-
}
95-
96-
return $this->extractSingle(current($rules), $class);
97-
}
9855
}

library/Rules/Core/KeyRelated.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Respect\Validation\Rules\Core;
11+
12+
use Respect\Validation\Validatable;
13+
14+
interface KeyRelated extends Validatable
15+
{
16+
public function getKey(): int|string;
17+
}

library/Rules/Key.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111

1212
use Respect\Validation\Helpers\CanBindEvaluateRule;
1313
use Respect\Validation\Result;
14+
use Respect\Validation\Rules\Core\KeyRelated;
1415
use Respect\Validation\Rules\Core\Wrapper;
1516
use Respect\Validation\Validatable;
1617

17-
final class Key extends Wrapper
18+
final class Key extends Wrapper implements KeyRelated
1819
{
1920
use CanBindEvaluateRule;
2021

library/Rules/KeyExists.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use ArrayAccess;
1313
use Respect\Validation\Message\Template;
1414
use Respect\Validation\Result;
15+
use Respect\Validation\Rules\Core\KeyRelated;
1516
use Respect\Validation\Rules\Core\Standard;
1617

1718
use function array_key_exists;
@@ -21,13 +22,18 @@
2122
'{{name}} must be present',
2223
'{{name}} must not be present',
2324
)]
24-
final class KeyExists extends Standard
25+
final class KeyExists extends Standard implements KeyRelated
2526
{
2627
public function __construct(
2728
private readonly int|string $key
2829
) {
2930
}
3031

32+
public function getKey(): int|string
33+
{
34+
return $this->key;
35+
}
36+
3137
public function evaluate(mixed $input): Result
3238
{
3339
return new Result($this->hasKey($input), $input, $this, name: (string) $this->key, id: (string) $this->key);

library/Rules/KeyOptional.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111

1212
use Respect\Validation\Helpers\CanBindEvaluateRule;
1313
use Respect\Validation\Result;
14+
use Respect\Validation\Rules\Core\KeyRelated;
1415
use Respect\Validation\Rules\Core\Wrapper;
1516
use Respect\Validation\Validatable;
1617

17-
final class KeyOptional extends Wrapper
18+
final class KeyOptional extends Wrapper implements KeyRelated
1819
{
1920
use CanBindEvaluateRule;
2021

@@ -26,6 +27,11 @@ public function __construct(
2627
parent::__construct($rule);
2728
}
2829

30+
public function getKey(): int|string
31+
{
32+
return $this->key;
33+
}
34+
2935
public function evaluate(mixed $input): Result
3036
{
3137
$keyExistsResult = $this->bindEvaluate(new KeyExists($this->key), $this, $input);

0 commit comments

Comments
 (0)