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
21 changes: 21 additions & 0 deletions docs/rules/Length.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,32 @@ v::length(v::equals(0))->isValid(new SplPriorityQueue()); // true

### `Length::TEMPLATE_STANDARD`

Used when it's possible to get the length of the input.

| Mode | Template |
|------------|---------------|
| `default` | The length of |
| `inverted` | The length of |

This template serve as message prefixes.:

```php
v::length(v::equals(3))->assert('tulip');
// Message: The length of "tulip" must be equal to 3

v::not(v::length(v::equals(4)))->assert('rose');
// Message: The length of "rose" must not be equal to 4
```

### `Length::TEMPLATE_WRONG_TYPE`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is a new template, I had to add it to the documentation.


Used when it's impossible to get the length of the input.

| Mode | Template |
|------------|----------------------------------------------------|
| `default` | {{name}} must be a countable value or a string |
| `inverted` | {{name}} must not be a countable value or a string |

## Template placeholders

| Placeholder | Description |
Expand Down
51 changes: 39 additions & 12 deletions library/Rules/Length.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,66 @@
use Countable as PhpCountable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Binder;
use Respect\Validation\Rules\Core\Wrapper;

use function array_map;
use function count;
use function is_array;
use function is_string;
use function mb_strlen;
use function ucfirst;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template('The length of', 'The length of')]
#[Template(
'The length of',
'The length of',
self::TEMPLATE_STANDARD
)]
#[Template(
'{{name}} must be a countable value or a string',
'{{name}} must not be a countable value or a string',
self::TEMPLATE_WRONG_TYPE
)]
final class Length extends Wrapper
{
public const TEMPLATE_WRONG_TYPE = '__wrong_type__';

public function evaluate(mixed $input): Result
{
$typeResult = (new Binder($this, new OneOf(new StringType(), new Countable())))->evaluate($input);
if (!$typeResult->isValid) {
$result = $this->rule->evaluate($input);

return Result::failed($input, $this)->withSubsequent($result)->withId('length' . ucfirst($result->id));
$length = $this->extractLength($input);
if ($length === null) {
return Result::failed($input, $this, [], self::TEMPLATE_WRONG_TYPE)
->withId('length' . ucfirst($this->rule->evaluate($input)->id));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a way to get the id of the result

}

$result = $this->rule->evaluate($this->extractLength($input))->withInput($input)->withPrefixedId('length');
return $this->enrichResult($input, $this->rule->evaluate($length));
}

private function enrichResult(mixed $input, Result $result): Result
{
if (!$result->allowsSubsequent()) {
return $result
->withInput($input)
->withChildren(
...array_map(fn(Result $child) => $this->enrichResult($input, $child), $result->children)
);
}

return (new Result($result->isValid, $input, $this, id: $result->id))->withSubsequent($result);
return (new Result($result->isValid, $input, $this, id: $result->id))
->withPrefixedId('length')
->withSubsequent($result->withInput($input));
}

/** @param array<mixed>|PhpCountable|string $input */
private function extractLength(array|PhpCountable|string $input): int
private function extractLength(mixed $input): ?int
{
if (is_string($input)) {
return (int) mb_strlen($input);
}

return count($input);
if ($input instanceof PhpCountable || is_array($input)) {
return count($input);
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
<<<'FULL_MESSAGE'
- All of the required rules must pass for 0
- 0 must be a string
- The length of 0 must be between 2 and 15
- 0 must be a countable value or a string
FULL_MESSAGE,
));
15 changes: 15 additions & 0 deletions tests/feature/Rules/LengthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,18 @@
'- The length of Cactus must be equal to 3',
['lengthEquals' => 'The length of Cactus must be equal to 3']
));

test('Chained wrapped rule', expectAll(
fn() => v::length(v::between(5, 7)->odd())->assert([]),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much the essence of the changes.

If you see the other tests, length() receives only 1 rule, this test receives two rules, and the two of them fail. That means that we can't simply add the prefix ("The length of") to the result of that rule because the result has two children.

When Length receives a rule that produces a result with children, we need to add the prefix ("The length of") to all the children of that result.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmjohnsson23 I wrote some comments to this merge request. If you have any questions, you can just write here, I'll be happy to answer them!

'The length of `[]` must be between 5 and 7',
<<<'FULL_MESSAGE'
- All of the required rules must pass for `[]`
- The length of `[]` must be between 5 and 7
- The length of `[]` must be an odd number
FULL_MESSAGE,
[
'__root__' => 'All of the required rules must pass for `[]`',
'lengthBetween' => 'The length of `[]` must be between 5 and 7',
'lengthOdd' => 'The length of `[]` must be an odd number',
]
));
12 changes: 6 additions & 6 deletions tests/feature/TranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ function (): void {
ValidatorDefaults::setTranslator(new ArrayTranslator([
'All of the required rules must pass for {{name}}' => 'Todas as regras requeridas devem passar para {{name}}',
'The length of' => 'O comprimento de',
'{{name}} must be of type string' => '{{name}} deve ser do tipo string',
'{{name}} must be a string' => '{{name}} deve ser uma string',
'{{name}} must be between {{minValue}} and {{maxValue}}' => '{{name}} deve possuir de {{minValue}} a {{maxValue}} caracteres',
'{{name}} must be a valid telephone number for country {{countryName|trans}}'
=> '{{name}} deve ser um número de telefone válido para o país {{countryName|trans}}',
'United States' => 'Estados Unidos',
]));

Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert(0);
Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert([]);
},
<<<'FULL_MESSAGE'
- Todas as regras requeridas devem passar para 0
- 0 must be a string
- O comprimento de 0 deve possuir de 2 a 15 caracteres
- 0 deve ser um número de telefone válido para o país Estados Unidos
- Todas as regras requeridas devem passar para `[]`
- `[]` deve ser uma string
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this just because it was wrong, but it doesn't have anything to do with the changes.

- O comprimento de `[]` deve possuir de 2 a 15 caracteres
- `[]` deve ser um número de telefone válido para o país Estados Unidos
FULL_MESSAGE,
));

Expand Down
Loading