Skip to content

Commit

Permalink
Merge pull request #56 from programmatordev/YAPV-22-create-regex-rule
Browse files Browse the repository at this point in the history
Create Regex rule
  • Loading branch information
andrepimpao authored Mar 26, 2024
2 parents ff5ef71 + 60d32a8 commit 5442cc1
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/03-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [Email](03-rules_email.md)
- [Length](03-rules_length.md)
- [PasswordStrength](03-rules_password-strength.md)
- [Regex](03-rules_regex.md)
- [URL](03-rules_url.md)

## Comparison Rules
Expand Down
2 changes: 1 addition & 1 deletion docs/03-rules_password-strength.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The strength is calculated by measuring the entropy of the password (in bits) ba
```php
PasswordStrength(
string $minStrength = 'medium',
?string $minMessage = null
?string $message = null
);
```

Expand Down
79 changes: 79 additions & 0 deletions docs/03-rules_regex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Regex

Validates that a given regular expression pattern is valid.

```php
Regex(
string $pattern,
bool $match = true,
?callable $normalizer = null,
?string $message = null
);
```

## Basic Usage

```php
Validator::regex('/[a-z]/')->validate('abc'); // true
Validator::regex('/[a-z]/')->validate('123'); // false

// if match is false, assert that the pattern does not match
// in this case, assert that the value does not contain any lowercase letters
Validator::regex('/[a-z]/', match: false)->validate('abc'); // false
Validator::regex('/[a-z]/', match: false)->validate('123'); // true
```

> [!NOTE]
> An `UnexpectedValueException` will be thrown if the `pattern` is not a valid regular expression.
> [!NOTE]
> An `UnexpectedValueException` will be thrown when the input value is not a `string` or an object implementing `\Stringable`.
## Options

### `pattern`

type: `string`

Regular expression pattern to be matched against.

### `match`

type: `bool` default: `true`

- `true` the validation will pass if the given input value matches the regular expression pattern.
- `false` the validation will pass if the given input value *does not* match the regular expression pattern.

### `normalizer`

type: `callable` default: `null`

Allows to define a `callable` that will be applied to the value before checking if it is valid.

For example, use `trim`, or pass your own function, to not evaluate whitespaces at the end of a string:

```php
// allow all chars except whitespaces
Validator::length(pattern: '/^\S*$/')->validate('abc '); // false

Validator::length(pattern: '/^\S*$/', normalizer: 'trim')->validate('abc '); // true
Validator::length(pattern: '/^\S*$/', normalizer: fn($value) => trim($value))->validate('abc '); // true
```

### `message`

type: `?string` default: `The {{ name }} value is not valid.`

Message that will be shown when the input value does not match the regular expression pattern.

The following parameters are available:

| Parameter | Description |
|-----------------|---------------------------|
| `{{ value }}` | The current invalid value |
| `{{ name }}` | Name of the invalid value |
| `{{ pattern }}` | The given pattern |

## Changelog

- `0.8.0` Created
7 changes: 7 additions & 0 deletions src/ChainedValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ public function range(
?string $message = null
): ChainedValidatorInterface&Validator;

public function regex(
string $pattern,
bool $match = true,
?callable $normalizer = null,
?string $message = null
): ChainedValidatorInterface&Validator;

public function rule(
RuleInterface $constraint
): ChainedValidatorInterface&Validator;
Expand Down
5 changes: 5 additions & 0 deletions src/Exception/RegexException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace ProgrammatorDev\Validator\Exception;

class RegexException extends ValidationException {}
53 changes: 53 additions & 0 deletions src/Rule/Regex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace ProgrammatorDev\Validator\Rule;

use ProgrammatorDev\Validator\Exception\RegexException;
use ProgrammatorDev\Validator\Exception\UnexpectedTypeException;
use ProgrammatorDev\Validator\Exception\UnexpectedValueException;

class Regex extends AbstractRule implements RuleInterface
{
/** @var ?callable */
private $normalizer;
private string $message = 'The {{ name }} value is not valid.';

public function __construct(
private readonly string $pattern,
private readonly bool $match = true,
?callable $normalizer = null,
?string $message = null
)
{
$this->normalizer = $normalizer;
$this->message = $message ?? $this->message;
}

public function assert(mixed $value, ?string $name = null): void
{
if (!\is_scalar($value) && !$value instanceof \Stringable) {
throw new UnexpectedTypeException('string|\Stringable', get_debug_type($value));
}

$value = (string) $value;

if ($this->normalizer !== null) {
$value = ($this->normalizer)($value);
}

if (($regex = @\preg_match($this->pattern, $value)) === false) {
throw new UnexpectedValueException('Invalid regular expression pattern.');
}

if ($this->match xor $regex) {
throw new RegexException(
message: $this->message,
parameters: [
'name' => $name,
'value' => $value,
'pattern' => $this->pattern
]
);
}
}
}
7 changes: 7 additions & 0 deletions src/StaticValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ public static function range(
?string $message = null
): ChainedValidatorInterface&Validator;

public static function regex(
string $pattern,
bool $match = true,
?callable $normalizer = null,
?string $message = null
): ChainedValidatorInterface&Validator;

public static function rule(
RuleInterface $constraint
): ChainedValidatorInterface&Validator;
Expand Down
58 changes: 58 additions & 0 deletions tests/RegexTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace ProgrammatorDev\Validator\Test;

use ProgrammatorDev\Validator\Exception\RegexException;
use ProgrammatorDev\Validator\Rule\Regex;
use ProgrammatorDev\Validator\Test\Util\TestRuleFailureConditionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleMessageOptionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleSuccessConditionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleUnexpectedValueTrait;

class RegexTest extends AbstractTest
{
use TestRuleUnexpectedValueTrait;
use TestRuleFailureConditionTrait;
use TestRuleSuccessConditionTrait;
use TestRuleMessageOptionTrait;

public static function provideRuleUnexpectedValueData(): \Generator
{
$unexpectedPatternMessage = '/Invalid regular expression pattern./';
$unexpectedTypeMessage = '/Expected value of type "array|\Stringable", "(.*)" given./';

yield 'invalid pattern' => [new Regex('invalid'), 'abc', $unexpectedPatternMessage];
yield 'invalid value type' => [new Regex('/[a-z]/'), ['abc'], $unexpectedTypeMessage];
}

public static function provideRuleFailureConditionData(): \Generator
{
$value = 'abc';
$exception = RegexException::class;
$message = '/The (.*) value is not valid./';

yield 'match true' => [new Regex('/[0-9]/'), $value, $exception, $message];
yield 'match false' => [new Regex('/[a-z]/', match: false), $value, $exception, $message];
}

public static function provideRuleSuccessConditionData(): \Generator
{
$value = 'abc';

yield 'match true' => [new Regex('/[a-z]/'), $value];
yield 'match false' => [new Regex('/[0-9]/', match: false), $value];
yield 'normalizer' => [new Regex('/^\S*$/', normalizer: 'trim'), 'abc '];
}

public static function provideRuleMessageOptionData(): \Generator
{
yield 'message' => [
new Regex(
pattern: '/[a-z]/',
message: 'The {{ name }} value does not match the pattern {{ pattern }}.'
),
'123',
'The test value does not match the pattern "/[a-z]/".'
];
}
}

0 comments on commit 5442cc1

Please sign in to comment.