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

Commit 5442cc1

Browse files
authored
Merge pull request #56 from programmatordev/YAPV-22-create-regex-rule
Create Regex rule
2 parents ff5ef71 + 60d32a8 commit 5442cc1

File tree

8 files changed

+211
-1
lines changed

8 files changed

+211
-1
lines changed

docs/03-rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [Email](03-rules_email.md)
1919
- [Length](03-rules_length.md)
2020
- [PasswordStrength](03-rules_password-strength.md)
21+
- [Regex](03-rules_regex.md)
2122
- [URL](03-rules_url.md)
2223

2324
## Comparison Rules

docs/03-rules_password-strength.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The strength is calculated by measuring the entropy of the password (in bits) ba
66
```php
77
PasswordStrength(
88
string $minStrength = 'medium',
9-
?string $minMessage = null
9+
?string $message = null
1010
);
1111
```
1212

docs/03-rules_regex.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Regex
2+
3+
Validates that a given regular expression pattern is valid.
4+
5+
```php
6+
Regex(
7+
string $pattern,
8+
bool $match = true,
9+
?callable $normalizer = null,
10+
?string $message = null
11+
);
12+
```
13+
14+
## Basic Usage
15+
16+
```php
17+
Validator::regex('/[a-z]/')->validate('abc'); // true
18+
Validator::regex('/[a-z]/')->validate('123'); // false
19+
20+
// if match is false, assert that the pattern does not match
21+
// in this case, assert that the value does not contain any lowercase letters
22+
Validator::regex('/[a-z]/', match: false)->validate('abc'); // false
23+
Validator::regex('/[a-z]/', match: false)->validate('123'); // true
24+
```
25+
26+
> [!NOTE]
27+
> An `UnexpectedValueException` will be thrown if the `pattern` is not a valid regular expression.
28+
29+
> [!NOTE]
30+
> An `UnexpectedValueException` will be thrown when the input value is not a `string` or an object implementing `\Stringable`.
31+
32+
## Options
33+
34+
### `pattern`
35+
36+
type: `string`
37+
38+
Regular expression pattern to be matched against.
39+
40+
### `match`
41+
42+
type: `bool` default: `true`
43+
44+
- `true` the validation will pass if the given input value matches the regular expression pattern.
45+
- `false` the validation will pass if the given input value *does not* match the regular expression pattern.
46+
47+
### `normalizer`
48+
49+
type: `callable` default: `null`
50+
51+
Allows to define a `callable` that will be applied to the value before checking if it is valid.
52+
53+
For example, use `trim`, or pass your own function, to not evaluate whitespaces at the end of a string:
54+
55+
```php
56+
// allow all chars except whitespaces
57+
Validator::length(pattern: '/^\S*$/')->validate('abc '); // false
58+
59+
Validator::length(pattern: '/^\S*$/', normalizer: 'trim')->validate('abc '); // true
60+
Validator::length(pattern: '/^\S*$/', normalizer: fn($value) => trim($value))->validate('abc '); // true
61+
```
62+
63+
### `message`
64+
65+
type: `?string` default: `The {{ name }} value is not valid.`
66+
67+
Message that will be shown when the input value does not match the regular expression pattern.
68+
69+
The following parameters are available:
70+
71+
| Parameter | Description |
72+
|-----------------|---------------------------|
73+
| `{{ value }}` | The current invalid value |
74+
| `{{ name }}` | Name of the invalid value |
75+
| `{{ pattern }}` | The given pattern |
76+
77+
## Changelog
78+
79+
- `0.8.0` Created

src/ChainedValidatorInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ public function range(
9595
?string $message = null
9696
): ChainedValidatorInterface&Validator;
9797

98+
public function regex(
99+
string $pattern,
100+
bool $match = true,
101+
?callable $normalizer = null,
102+
?string $message = null
103+
): ChainedValidatorInterface&Validator;
104+
98105
public function rule(
99106
RuleInterface $constraint
100107
): ChainedValidatorInterface&Validator;

src/Exception/RegexException.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Exception;
4+
5+
class RegexException extends ValidationException {}

src/Rule/Regex.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Rule;
4+
5+
use ProgrammatorDev\Validator\Exception\RegexException;
6+
use ProgrammatorDev\Validator\Exception\UnexpectedTypeException;
7+
use ProgrammatorDev\Validator\Exception\UnexpectedValueException;
8+
9+
class Regex extends AbstractRule implements RuleInterface
10+
{
11+
/** @var ?callable */
12+
private $normalizer;
13+
private string $message = 'The {{ name }} value is not valid.';
14+
15+
public function __construct(
16+
private readonly string $pattern,
17+
private readonly bool $match = true,
18+
?callable $normalizer = null,
19+
?string $message = null
20+
)
21+
{
22+
$this->normalizer = $normalizer;
23+
$this->message = $message ?? $this->message;
24+
}
25+
26+
public function assert(mixed $value, ?string $name = null): void
27+
{
28+
if (!\is_scalar($value) && !$value instanceof \Stringable) {
29+
throw new UnexpectedTypeException('string|\Stringable', get_debug_type($value));
30+
}
31+
32+
$value = (string) $value;
33+
34+
if ($this->normalizer !== null) {
35+
$value = ($this->normalizer)($value);
36+
}
37+
38+
if (($regex = @\preg_match($this->pattern, $value)) === false) {
39+
throw new UnexpectedValueException('Invalid regular expression pattern.');
40+
}
41+
42+
if ($this->match xor $regex) {
43+
throw new RegexException(
44+
message: $this->message,
45+
parameters: [
46+
'name' => $name,
47+
'value' => $value,
48+
'pattern' => $this->pattern
49+
]
50+
);
51+
}
52+
}
53+
}

src/StaticValidatorInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ public static function range(
9494
?string $message = null
9595
): ChainedValidatorInterface&Validator;
9696

97+
public static function regex(
98+
string $pattern,
99+
bool $match = true,
100+
?callable $normalizer = null,
101+
?string $message = null
102+
): ChainedValidatorInterface&Validator;
103+
97104
public static function rule(
98105
RuleInterface $constraint
99106
): ChainedValidatorInterface&Validator;

tests/RegexTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Test;
4+
5+
use ProgrammatorDev\Validator\Exception\RegexException;
6+
use ProgrammatorDev\Validator\Rule\Regex;
7+
use ProgrammatorDev\Validator\Test\Util\TestRuleFailureConditionTrait;
8+
use ProgrammatorDev\Validator\Test\Util\TestRuleMessageOptionTrait;
9+
use ProgrammatorDev\Validator\Test\Util\TestRuleSuccessConditionTrait;
10+
use ProgrammatorDev\Validator\Test\Util\TestRuleUnexpectedValueTrait;
11+
12+
class RegexTest extends AbstractTest
13+
{
14+
use TestRuleUnexpectedValueTrait;
15+
use TestRuleFailureConditionTrait;
16+
use TestRuleSuccessConditionTrait;
17+
use TestRuleMessageOptionTrait;
18+
19+
public static function provideRuleUnexpectedValueData(): \Generator
20+
{
21+
$unexpectedPatternMessage = '/Invalid regular expression pattern./';
22+
$unexpectedTypeMessage = '/Expected value of type "array|\Stringable", "(.*)" given./';
23+
24+
yield 'invalid pattern' => [new Regex('invalid'), 'abc', $unexpectedPatternMessage];
25+
yield 'invalid value type' => [new Regex('/[a-z]/'), ['abc'], $unexpectedTypeMessage];
26+
}
27+
28+
public static function provideRuleFailureConditionData(): \Generator
29+
{
30+
$value = 'abc';
31+
$exception = RegexException::class;
32+
$message = '/The (.*) value is not valid./';
33+
34+
yield 'match true' => [new Regex('/[0-9]/'), $value, $exception, $message];
35+
yield 'match false' => [new Regex('/[a-z]/', match: false), $value, $exception, $message];
36+
}
37+
38+
public static function provideRuleSuccessConditionData(): \Generator
39+
{
40+
$value = 'abc';
41+
42+
yield 'match true' => [new Regex('/[a-z]/'), $value];
43+
yield 'match false' => [new Regex('/[0-9]/', match: false), $value];
44+
yield 'normalizer' => [new Regex('/^\S*$/', normalizer: 'trim'), 'abc '];
45+
}
46+
47+
public static function provideRuleMessageOptionData(): \Generator
48+
{
49+
yield 'message' => [
50+
new Regex(
51+
pattern: '/[a-z]/',
52+
message: 'The {{ name }} value does not match the pattern {{ pattern }}.'
53+
),
54+
'123',
55+
'The test value does not match the pattern "/[a-z]/".'
56+
];
57+
}
58+
}

0 commit comments

Comments
 (0)