Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 19ddb26

Browse files
authored
Merge pull request #75 from programmatordev/YAPV-31-create-atleastoneof-rule
Create AtLeastOneOf rule
2 parents 3d3e3e8 + a4a0325 commit 19ddb26

10 files changed

+293
-74
lines changed

docs/03-rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,5 @@
5656

5757
## Other Rules
5858

59+
- [AtLeastOneOf](03-rules_at-least-one-of.md)
5960
- [Optional](03-rules_optional.md)

docs/03-rules_at-least-one-of.md

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# AtLeastOneOf
2+
3+
Checks that the value satisfies at least one of the given constraints.
4+
5+
```php
6+
/** Validator[] $constraints */
7+
Choice(
8+
array $constraints,
9+
?string $message = null
10+
);
11+
```
12+
13+
## Basic Usage
14+
15+
```php
16+
Validator::atLeastOneOf([
17+
Validator::isFalse(),
18+
Validator::type('int')->greaterThanOrEqual(18)
19+
])->validate(false); // true
20+
21+
Validator::atLeastOneOf([
22+
Validator::isFalse(),
23+
Validator::type('int')->greaterThanOrEqual(18)
24+
])->validate(20); // true
25+
26+
Validator::atLeastOneOf([
27+
Validator::isFalse(),
28+
Validator::type('int')->greaterThanOrEqual(18)
29+
])->validate(true); // false
30+
31+
Validator::atLeastOneOf([
32+
Validator::isFalse(),
33+
Validator::type('int')->greaterThanOrEqual(18)
34+
])->validate(16); // false
35+
```
36+
37+
> [!NOTE]
38+
> An `UnexpectedValueException` will be thrown when a value in the `constraints` array is not an instance of `Validator`.
39+
40+
## Options
41+
42+
### `constraints`
43+
44+
type: `array<int, Validator>` `required`
45+
46+
Collection of constraints to be validated against the input value.
47+
If at least one given constraint is valid, the validation is considered successful.
48+
49+
### `message`
50+
51+
type: `?string` default: `The {{ name }} value should satisfy at least one of the following constraints: {{ messages }}`
52+
53+
Message that will be shown if all given constraints are not valid.
54+
55+
The following parameters are available:
56+
57+
| Parameter | Description |
58+
|------------------|-------------------------------------------------|
59+
| `{{ value }}` | The current invalid value |
60+
| `{{ name }}` | Name of the invalid value |
61+
| `{{ messages }}` | List of error messages based on the constraints |
62+
63+
## Changelog
64+
65+
- `1.3.0` Created

src/ChainedValidatorInterface.php

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77

88
interface ChainedValidatorInterface
99
{
10+
public function atLeastOneOf(
11+
array $constraints,
12+
?string $message = null
13+
): ChainedValidatorInterface&Validator;
14+
1015
public function blank(
1116
?callable $normalizer = null,
1217
?string $message = null
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Exception;
4+
5+
class AtLeastOneOfException extends ValidationException {}

src/Exception/Util/MessageTrait.php

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Exception\Util;
4+
5+
trait MessageTrait
6+
{
7+
private function formatMessage(string $message, array $parameters = []): string
8+
{
9+
// if a name was not given, remove it from the message template but keep it intuitive
10+
if (empty($parameters['name'])) {
11+
$message = \str_replace(' {{ name }} ', ' ', $message);
12+
unset($parameters['name']);
13+
}
14+
15+
foreach ($parameters as $parameter => $value) {
16+
// format values (with some exceptions to avoid adding unnecessary quotation marks)
17+
$message = \str_replace(
18+
\sprintf('{{ %s }}', $parameter),
19+
(\in_array($parameter, ['name', 'message', 'messages'])) ? $value : $this->formatValue($value),
20+
$message
21+
);
22+
}
23+
24+
return $message;
25+
}
26+
27+
private function formatValue(mixed $value): string
28+
{
29+
if ($value instanceof \DateTimeInterface) {
30+
return $value->format('Y-m-d H:i:s');
31+
}
32+
33+
if (\is_object($value)) {
34+
if ($value instanceof \Stringable) {
35+
return $value->__toString();
36+
}
37+
38+
return 'object';
39+
}
40+
41+
if (\is_array($value)) {
42+
return $this->formatValues($value);
43+
}
44+
45+
if (\is_string($value)) {
46+
// replace line breaks and tabs with single space
47+
$value = \str_replace(["\n", "\r", "\t", "\v", "\x00"], ' ', $value);
48+
49+
return \sprintf('"%s"', $value);
50+
}
51+
52+
if (\is_resource($value)) {
53+
return 'resource';
54+
}
55+
56+
if ($value === null) {
57+
return 'null';
58+
}
59+
60+
if ($value === false) {
61+
return 'false';
62+
}
63+
64+
if ($value === true) {
65+
return 'true';
66+
}
67+
68+
return (string) $value;
69+
}
70+
71+
private function formatValues(array $values): string
72+
{
73+
foreach ($values as $key => $value) {
74+
$values[$key] = $this->formatValue($value);
75+
}
76+
77+
return \sprintf('[%s]', \implode(', ', $values));
78+
}
79+
}

src/Exception/ValidationException.php

+4-73
Original file line numberDiff line numberDiff line change
@@ -2,85 +2,16 @@
22

33
namespace ProgrammatorDev\Validator\Exception;
44

5+
use ProgrammatorDev\Validator\Exception\Util\MessageTrait;
6+
57
class ValidationException extends \Exception
68
{
9+
use MessageTrait;
10+
711
public function __construct(string $message, array $parameters = [])
812
{
913
$message = $this->formatMessage($message, $parameters);
1014

1115
parent::__construct($message);
1216
}
13-
14-
private function formatMessage(string $message, array $parameters = []): string
15-
{
16-
// If a name was not given, remove it from the message template but keep it intuitive
17-
if (empty($parameters['name'])) {
18-
$message = \str_replace(' {{ name }} ', ' ', $message);
19-
unset($parameters['name']);
20-
}
21-
22-
foreach ($parameters as $parameter => $value) {
23-
// Format values (with some exceptions [name, message] to avoid adding unnecessary quotation marks)
24-
$message = \str_replace(
25-
\sprintf('{{ %s }}', $parameter),
26-
(\in_array($parameter, ['name', 'message'])) ? $value : $this->formatValue($value),
27-
$message
28-
);
29-
}
30-
31-
return $message;
32-
}
33-
34-
private function formatValue(mixed $value): string
35-
{
36-
if ($value instanceof \DateTimeInterface) {
37-
return $value->format('Y-m-d H:i:s');
38-
}
39-
40-
if (\is_object($value)) {
41-
if ($value instanceof \Stringable) {
42-
return $value->__toString();
43-
}
44-
45-
return 'object';
46-
}
47-
48-
if (\is_array($value)) {
49-
return $this->formatValues($value);
50-
}
51-
52-
if (\is_string($value)) {
53-
// Replace line breaks and tabs with single space
54-
$value = \str_replace(["\n", "\r", "\t", "\v", "\x00"], ' ', $value);
55-
56-
return \sprintf('"%s"', $value);
57-
}
58-
59-
if (\is_resource($value)) {
60-
return 'resource';
61-
}
62-
63-
if ($value === null) {
64-
return 'null';
65-
}
66-
67-
if ($value === false) {
68-
return 'false';
69-
}
70-
71-
if ($value === true) {
72-
return 'true';
73-
}
74-
75-
return (string) $value;
76-
}
77-
78-
private function formatValues(array $values): string
79-
{
80-
foreach ($values as $key => $value) {
81-
$values[$key] = $this->formatValue($value);
82-
}
83-
84-
return \sprintf('[%s]', \implode(', ', $values));
85-
}
8617
}

src/Rule/AtLeastOneOf.php

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Rule;
4+
5+
use ProgrammatorDev\Validator\Exception\AtLeastOneOfException;
6+
use ProgrammatorDev\Validator\Exception\UnexpectedValueException;
7+
use ProgrammatorDev\Validator\Exception\ValidationException;
8+
use ProgrammatorDev\Validator\Validator;
9+
10+
class AtLeastOneOf extends AbstractRule implements RuleInterface
11+
{
12+
private string $message = 'The {{ name }} value should satisfy at least one of the following constraints: {{ messages }}';
13+
14+
/** @param Validator[] $constraints */
15+
public function __construct(
16+
private readonly array $constraints,
17+
?string $message = null
18+
)
19+
{
20+
$this->message = $message ?? $this->message;
21+
}
22+
23+
public function assert(mixed $value, ?string $name = null): void
24+
{
25+
try {
26+
Validator::eachValue(
27+
validator: Validator::type(Validator::class)
28+
)->assert($this->constraints);
29+
}
30+
catch (ValidationException $exception) {
31+
throw new UnexpectedValueException($exception->getMessage());
32+
}
33+
34+
$messages = [];
35+
36+
foreach ($this->constraints as $key => $constraint) {
37+
try {
38+
$constraint->assert($value, $name);
39+
return;
40+
}
41+
catch (ValidationException|UnexpectedValueException $exception) {
42+
$messages[] = \sprintf('[%d] %s', ($key + 1), $exception->getMessage());
43+
}
44+
}
45+
46+
throw new AtLeastOneOfException(
47+
message: $this->message,
48+
parameters: [
49+
'value' => $value,
50+
'name' => $name,
51+
'messages' => \implode(' ', $messages)
52+
]
53+
);
54+
}
55+
}

src/Rule/Length.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function assert(mixed $value, ?string $name = null): void
8080
$value = ($this->normalizer)($value);
8181
}
8282

83-
if (!mb_check_encoding($value, $this->charset)) {
83+
if (!\mb_check_encoding($value, $this->charset)) {
8484
throw new LengthException(
8585
message: $this->charsetMessage,
8686
parameters: [

src/StaticValidatorInterface.php

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
interface StaticValidatorInterface
88
{
9+
public static function atLeastOneOf(
10+
array $constraints,
11+
?string $message = null
12+
): ChainedValidatorInterface&Validator;
13+
914
public static function blank(
1015
?callable $normalizer = null,
1116
?string $message = null

0 commit comments

Comments
 (0)