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
6 changes: 4 additions & 2 deletions docs/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>

In this page you will find a list of validators by their category.

**Arrays**: [ArrayType][] - [ArrayVal][] - [Contains][] - [ContainsAny][] - [ContainsCount][] - [Each][] - [EndsWith][] - [In][] - [Key][] - [KeyExists][] - [KeyOptional][] - [KeySet][] - [Sorted][] - [StartsWith][] - [Subset][] - [Unique][]
**Arrays**: [ArrayType][] - [ArrayVal][] - [Contains][] - [ContainsAny][] - [ContainsCount][] - [Each][] - [EachKey][] - [EndsWith][] - [In][] - [Key][] - [KeyExists][] - [KeyOptional][] - [KeySet][] - [Sorted][] - [StartsWith][] - [Subset][] - [Unique][]

**Banking**: [CreditCard][] - [Iban][]

Expand Down Expand Up @@ -43,7 +43,7 @@ In this page you will find a list of validators by their category.

**Miscellaneous**: [Blank][] - [Falsy][] - [Named][] - [Templated][] - [Undef][]

**Nesting**: [After][] - [AllOf][] - [AnyOf][] - [Each][] - [Factory][] - [Given][] - [Key][] - [KeySet][] - [NoneOf][] - [Not][] - [NullOr][] - [OneOf][] - [Property][] - [PropertyOptional][] - [ShortCircuit][] - [UndefOr][] - [When][]
**Nesting**: [After][] - [AllOf][] - [AnyOf][] - [Each][] - [EachKey][] - [Factory][] - [Given][] - [Key][] - [KeySet][] - [NoneOf][] - [Not][] - [NullOr][] - [OneOf][] - [Property][] - [PropertyOptional][] - [ShortCircuit][] - [UndefOr][] - [When][]

**Numbers**: [Base][] - [Decimal][] - [Digit][] - [Even][] - [Factor][] - [Finite][] - [FloatType][] - [FloatVal][] - [Infinite][] - [IntType][] - [IntVal][] - [Multiple][] - [Negative][] - [Number][] - [NumericVal][] - [Odd][] - [Positive][] - [Roman][]

Expand Down Expand Up @@ -100,6 +100,7 @@ In this page you will find a list of validators by their category.
- [Directory][] - `v::directory()->assert(__DIR__);`
- [Domain][] - `v::domain()->assert('google.com');`
- [Each][] - `v::each(v::dateTime())->assert($releaseDates);`
- [EachKey][] - `v::eachKey(v::stringType())->assert($releaseDates);`
- [Email][] - `v::email()->assert('alganet@gmail.com');`
- [Emoji][] - `v::emoji()->assert('🍕');`
- [EndsWith][] - `v::endsWith('ipsum')->assert('lorem ipsum');`
Expand Down Expand Up @@ -259,6 +260,7 @@ In this page you will find a list of validators by their category.
[Directory]: validators/Directory.md "Validates if the given path is a directory."
[Domain]: validators/Domain.md "Validates whether the input is a valid domain name or not."
[Each]: validators/Each.md "Validates whether each value in the input is valid according to another validator."
[EachKey]: validators/EachKey.md "Validates whether each key in the input is valid according to another validator."
[Email]: validators/Email.md "Validates an email address."
[Emoji]: validators/Emoji.md "Validates if the input is an emoji or a sequence of emojis."
[EndsWith]: validators/EndsWith.md "This validator is similar to `Contains()`, but validates"
Expand Down
1 change: 1 addition & 0 deletions docs/validators/Each.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ v::length(v::greaterThan(0))->each(v::equals(10))->assert([]);
- [After](After.md)
- [All](All.md)
- [ArrayVal](ArrayVal.md)
- [EachKey](EachKey.md)
- [Falsy](Falsy.md)
- [IterableType](IterableType.md)
- [IterableVal](IterableVal.md)
Expand Down
111 changes: 111 additions & 0 deletions docs/validators/EachKey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<!--
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: (c) Respect Project Contributors
SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
-->

# EachKey

- `EachKey(Validator $validator)`

Validates whether each key in the input is valid according to another validator.

```php
$releaseDates = [
'validation' => '2010-01-01',
'template' => '2011-01-01',
'relational' => '2011-02-05',
];

v::eachKey(v::stringType())->assert($releaseDates);
// Validation passes successfully
```

This validator is the key-type counterpart to [Each](Each.md). While `Each` validates
values, `EachKey` validates keys. This allows composable validation of array key types:

```php
v::eachKey(v::stringType())->assert(['a' => 1, 'b' => 2]);
// Validation passes successfully

v::eachKey(v::stringType())->assert([0 => 'x', 1 => 'y']);
// → - Each key of `["x", "y"]` must be valid
// → - Key `.0` must be a string
// → - Key `.1` must be a string
```

You can combine `EachKey` with [Each](Each.md) via [AllOf](AllOf.md) to validate both
keys and values independently:

```php
v::allOf(v::eachKey(v::stringType()), v::each(v::intType()))->assert(['a' => 1, 'b' => 2]);
// Validation passes successfully
```

## Note

This validator will pass if the input is empty. Use [Length](Length.md) with [GreaterThan](GreaterThan.md) to perform a stricter check:

```php
v::eachKey(v::stringType())->assert([]);
// Validation passes successfully

v::length(v::greaterThan(0))->eachKey(v::stringType())->assert([]);
// → The length of `[]` must be greater than 0
```

## Templates

### `EachKey::TEMPLATE_STANDARD`

| Mode | Template |
| ---------: | :-------------------------------------- |
| `default` | Each key of {{subject}} must be valid |
| `inverted` | Each key of {{subject}} must be invalid |

### `EachKey::TEMPLATE_NESTED`

| Mode | Template |
| ---------: | :------- |
| `default` | Key |
| `inverted` | Key |

### `EachKey::TEMPLATE_SHORT_CIRCUITED`

| Mode | Template |
| ---------: | :---------- |
| `default` | Each key of |
| `inverted` | Each key of |

## Template placeholders

- **TEMPLATE_STANDARD**: Uses `{{subject}}` — the validated input or the custom
validator name (if specified).
- **TEMPLATE_NESTED**: Does not use placeholders. Composes with the inner
validator's template via `asAdjacentOf` to produce messages like
"Key `.0` must be a string".

## Categorization

- Arrays
- Nesting

## Changelog

| Version | Description |
| ------: | :---------- |
| 3.2.0 | Created |

## See Also

- [After](After.md)
- [All](All.md)
- [AllOf](AllOf.md)
- [Each](Each.md)
- [IterableType](IterableType.md)
- [IterableVal](IterableVal.md)
- [Key](Key.md)
- [KeyExists](KeyExists.md)
- [KeyOptional](KeyOptional.md)
- [KeySet](KeySet.md)
- [Length](Length.md)
2 changes: 2 additions & 0 deletions src/Mixins/AllBuilder.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/AllChain.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/Builder.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/Chain.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/KeyBuilder.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/KeyChain.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/NotBuilder.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/NotChain.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/NullOrBuilder.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/NullOrChain.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/PropertyBuilder.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/PropertyChain.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/UndefOrBuilder.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Mixins/UndefOrChain.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions src/Validators/EachKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: (c) Respect Project Contributors
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
*/

declare(strict_types=1);

namespace Respect\Validation\Validators;

use Attribute;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
use Respect\Validation\Path;
use Respect\Validation\Result;
use Respect\Validation\Validators\Core\FilteredArray;
use Respect\Validation\Validators\Core\ShortCircuitable;

use function array_keys;
use function array_reduce;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'Each key of {{subject}} must be valid',
'Each key of {{subject}} must be invalid',
self::TEMPLATE_STANDARD,
)]
#[Template('Key', 'Key', self::TEMPLATE_NESTED)]
#[Template('Each key of', 'Each key of', self::TEMPLATE_SHORT_CIRCUITED)]
final class EachKey extends FilteredArray implements ShortCircuitable
{
use CanEvaluateShortCircuit;

public const string TEMPLATE_NESTED = '__nested__';
public const string TEMPLATE_SHORT_CIRCUITED = '__short_circuited__';

public function evaluateShortCircuit(mixed $input): Result
{
$iterableResult = (new IterableType())->evaluate($input);
if (!$iterableResult->hasPassed) {
return $iterableResult->withIdFrom($this);
}

$result = null;
// phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable -- only keys are validated
foreach ($input as $key => $value) {
$result = $this->evaluateShortCircuitWith($this->validator, $key);
if (!$result->hasPassed) {
return $result->asAdjacentOf(
Result::failed($key, $this, [], self::TEMPLATE_NESTED),
'eachKey',
)->withPath(new Path($key));
}
}

if ($result === null) {
return Result::passed($input, $this)->asIndeterminate();
}

return $result->asAdjacentOf(Result::passed($input, $this, [], self::TEMPLATE_SHORT_CIRCUITED), 'eachKey');
}

/** @param non-empty-array<mixed> $input */
protected function evaluateArray(array $input): Result
{
$children = [];
foreach (array_keys($input) as $key) {
$validatorResult = $this->validator->evaluate($key);
$children[] = $validatorResult->asAdjacentOf(
Result::of($validatorResult->hasPassed, $key, $this, [], self::TEMPLATE_NESTED),
'eachKey',
)->withPath(new Path($key));
}

$hasPassed = array_reduce(
$children,
static fn($carry, $childResult) => $carry && $childResult->hasPassed,
true,
);

return Result::of($hasPassed, $input, $this)->withChildren(...$children);
}
}
Loading
Loading