Skip to content

Commit 0090191

Browse files
committed
Introduce tests for serialization of validators
This commit introduces a new feature test: SerializableTest, that tests several validators for their ability to be serialized and unserialized. It also makes it so that the same list of validators can be used by both simple benchmarks and "smoke tests" of all kinds, including this serialize/unserialize one. Additionally, the FilterVar validator was modified. Previously, due to the use of Callback, it was not serializable, but now it is.
1 parent 9e768cc commit 0090191

6 files changed

Lines changed: 237 additions & 110 deletions

File tree

library/Validators/FilterVar.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@
1212
use Attribute;
1313
use Respect\Validation\Exceptions\InvalidValidatorException;
1414
use Respect\Validation\Message\Template;
15-
use Respect\Validation\Validators\Core\Envelope;
15+
use Respect\Validation\Result;
16+
use Respect\Validation\Validator;
1617

1718
use function array_key_exists;
1819
use function filter_var;
19-
use function is_array;
20-
use function is_int;
2120

2221
use const FILTER_VALIDATE_BOOLEAN;
2322
use const FILTER_VALIDATE_DOMAIN;
@@ -33,7 +32,7 @@
3332
'{{subject}} must be valid',
3433
'{{subject}} must not be valid',
3534
)]
36-
final class FilterVar extends Envelope
35+
final readonly class FilterVar implements Validator
3736
{
3837
private const array ALLOWED_FILTERS = [
3938
FILTER_VALIDATE_BOOLEAN => 'is_bool',
@@ -46,21 +45,22 @@ final class FilterVar extends Envelope
4645
FILTER_VALIDATE_URL => 'is_string',
4746
];
4847

49-
public function __construct(int $filter, mixed $options = null)
48+
public function __construct(private int $filter, private mixed $options = null)
5049
{
5150
if (!array_key_exists($filter, self::ALLOWED_FILTERS)) {
5251
throw new InvalidValidatorException('Cannot accept the given filter');
5352
}
53+
}
5454

55-
$arguments = [$filter];
56-
if (is_array($options) || is_int($options)) {
57-
$arguments[] = $options;
58-
}
59-
60-
parent::__construct(new Callback(static function ($input) use ($filter, $arguments) {
61-
return (self::ALLOWED_FILTERS[$filter])(
62-
filter_var($input, ...$arguments),
63-
);
64-
}));
55+
public function evaluate(mixed $input): Result
56+
{
57+
return Result::of(
58+
(self::ALLOWED_FILTERS[$this->filter])(match ($this->options) {
59+
null => filter_var($input, $this->filter),
60+
default => filter_var($input, $this->filter, $this->options),
61+
}),
62+
$input,
63+
$this,
64+
);
6565
}
6666
}

phpbench.json.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
3-
"runner.bootstrap": "vendor/autoload.php",
3+
"runner.bootstrap": "tests/bootstrap.php",
44
"runner.path": "tests/benchmark",
55
"runner.file_pattern": "*Bench.php"
66
}

phpstan.neon.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ parameters:
2121
path: tests/feature/Validators/SizeTest.php
2222
- message: '/Property Respect\\Validation\\Test\\Stubs\\.+::\$[a-zA-Z]+ is never read, only written./'
2323
path: tests/library/Stubs
24+
- message: '/Call to an undefined method Pest\\PendingCalls\\TestCall|Pest\\Support\\HigherOrderTapProxy::with\(\)./'
25+
path: tests/feature/SerializableTest.php
2426
level: 8
2527
treatPhpDocTypesAsCertain: false
2628
paths:

tests/benchmark/ValidatorBench.php

Lines changed: 9 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
<?php
22

3-
declare(strict_types=1);
4-
5-
namespace Respect\Validation\Benchmarks;
6-
73
/*
84
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
95
* SPDX-License-Identifier: MIT
106
*/
117

12-
use Generator;
8+
declare(strict_types=1);
9+
10+
namespace Respect\Validation\Benchmarks;
11+
1312
use PhpBench\Attributes as Bench;
13+
use Respect\Validation\Test\SmokeTestProvider;
1414
use Respect\Validation\Validator;
15-
use Respect\Validation\ValidatorBuilder as v;
1615

1716
class ValidatorBench
1817
{
19-
/** @param array<Validator, mixed> $params */ #[Bench\Iterations(10)]
18+
use SmokeTestProvider;
19+
20+
/** @param array<Validator, mixed> $params */
21+
#[Bench\Iterations(10)]
2022
#[Bench\RetryThreshold(10)]
2123
#[Bench\Revs(5)]
2224
#[Bench\ParamProviders(['provideValidatorInput'])]
@@ -25,91 +27,4 @@ public function benchValidate(array $params): void
2527
[$v, $input] = $params;
2628
$v->validate($input);
2729
}
28-
29-
public function provideValidatorInput(): Generator
30-
{
31-
yield 'AllOf' => [v::allOf(v::intVal(), v::greaterThan(0)), 5];
32-
yield 'Alnum' => [v::alnum(), 'abc123'];
33-
yield 'Alpha' => [v::alpha(), 'abc'];
34-
yield 'AnyOf' => [v::anyOf(v::intVal(), v::stringVal()), 5];
35-
yield 'ArrayType' => [v::arrayType(), []];
36-
yield 'ArrayVal' => [v::arrayVal(), []];
37-
yield 'Between' => [v::between(1, 10), 5];
38-
yield 'BetweenExclusive' => [v::betweenExclusive(1, 10), 5];
39-
yield 'BoolType' => [v::boolType(), true];
40-
yield 'BoolVal' => [v::boolVal(), true];
41-
yield 'Bsn' => [v::bsn(), '612890053'];
42-
yield 'Call' => [v::call('array_keys', v::each(v::stringType())), ['a' => 'b']];
43-
yield 'Charset' => [v::charset('UTF-8'), 'example'];
44-
yield 'Circuit' => [v::circuit(v::intVal(), v::trueVal()), 123];
45-
yield 'Cnpj' => [v::cnpj(), '11444777000161'];
46-
yield 'Consonant' => [v::consonant(), 'bcdf'];
47-
yield 'Contains' => [v::contains('needle'), 'haystack needle'];
48-
yield 'ContainsAny' => [v::containsAny(['a', 'b']), 'abc'];
49-
yield 'ContainsCount' => [v::containsCount('a', 3), 'banana'];
50-
yield 'Control' => [v::control(), "\n\r"];
51-
yield 'Countable' => [v::countable(), []];
52-
yield 'CountryCode' => [v::countryCode(), 'US'];
53-
yield 'Cpf' => [v::cpf(), '11598647644'];
54-
yield 'CurrencyCode' => [v::currencyCode(), 'USD'];
55-
yield 'Date' => [v::date(), '2020-01-01'];
56-
yield 'DateTime' => [v::dateTime(), '2020-01-01 12:00:00'];
57-
yield 'Decimal' => [v::decimal(2), '1.23'];
58-
yield 'Digit' => [v::digit(), '7'];
59-
yield 'Domain' => [v::domain(), 'example.com'];
60-
yield 'Each' => [v::each(v::stringType()), ['a', 'b']];
61-
yield 'Email' => [v::email(), 'bob@example.com'];
62-
yield 'EndsWith' => [v::endsWith('.com'), 'example.com'];
63-
yield 'Equals' => [v::equals('x'), 'x'];
64-
yield 'Even' => [v::even(), 2];
65-
yield 'Executable' => [v::executable(), 'tests/fixtures/executable'];
66-
yield 'Exists' => [v::exists(), 'tests/fixtures/valid-image.png'];
67-
yield 'FalseVal' => [v::falseVal(), false];
68-
yield 'Fibonacci' => [v::fibonacci(), 13];
69-
yield 'File' => [v::file(), __FILE__];
70-
yield 'FloatType' => [v::floatType(), 1.23];
71-
yield 'FloatVal' => [v::floatVal(), 1.23];
72-
yield 'GreaterThan' => [v::greaterThan(0), 1];
73-
yield 'GreaterThanOrEqual' => [v::greaterThanOrEqual(1), 1];
74-
yield 'Hetu' => [v::hetu(), '010106A9012'];
75-
yield 'Iban' => [v::iban(), 'SE35 5000 0000 0549 1000 0003'];
76-
yield 'Identical' => [v::identical(123), 123];
77-
yield 'In' => [v::in(['a', 'b']), 'a'];
78-
yield 'IntType' => [v::intType(), 123];
79-
yield 'IntVal' => [v::intVal(), 123];
80-
yield 'Ip' => [v::ip(), '127.0.0.1'];
81-
yield 'IterableVal' => [v::iterableVal(), []];
82-
yield 'LanguageCode' => [v::languageCode(), 'en'];
83-
yield 'LessThan' => [v::lessThan(10), 5];
84-
yield 'LessThanOrEqual' => [v::lessThanOrEqual(10), 10];
85-
yield 'Lowercase' => [v::lowercase(), 'abc'];
86-
yield 'Luhn' => [v::luhn(), '2222400041240011'];
87-
yield 'MacAddress' => [v::macAddress(), '00:11:22:33:44:55'];
88-
yield 'Negative' => [v::negative(), -1];
89-
yield 'Nip' => [v::nip(), '1645865777'];
90-
yield 'Not' => [v::not(v::trueVal()), false];
91-
yield 'NullType' => [v::nullType(), null];
92-
yield 'NumericVal' => [v::numericVal(), '123'];
93-
yield 'Odd' => [v::odd(), 3];
94-
yield 'PerfectSquare' => [v::perfectSquare(), 16];
95-
yield 'Pesel' => [v::pesel(), '21120209256'];
96-
yield 'Property' => [v::property('email', v::endsWith('@example.com')), (object) ['email' => 'a@example.com']];
97-
yield 'PropertyExists' => [v::propertyExists('email'), (object) ['email' => 'a@example.com']];
98-
yield 'PropertyOptional' => [v::propertyOptional('missing', v::email()), (object) ['email' => 'a@example.com']];
99-
yield 'Readable' => [v::readable(), 'tests/fixtures/valid-image.png'];
100-
yield 'ScalarVal' => [v::scalarVal(), 'example'];
101-
yield 'Slug' => [v::slug(), 'a-valid-slug'];
102-
yield 'StartsWith' => [v::startsWith('ex'), 'example'];
103-
yield 'StringType' => [v::stringType(), 'example'];
104-
yield 'StringVal' => [v::stringVal(), 'example'];
105-
yield 'SymbolicLink' => [v::symbolicLink(), 'tests/fixtures/symbolic-link'];
106-
yield 'Time' => [v::time(), '12:34:56'];
107-
yield 'TrueVal' => [v::trueVal(), true];
108-
yield 'Unique' => [v::unique(), [1, 2, 3]];
109-
yield 'Uppercase' => [v::uppercase(), 'ABC'];
110-
yield 'Uuid' => [v::uuid(), '123e4567-e89b-12d3-a456-426655440000'];
111-
yield 'When' => [v::when(v::trueVal(), v::intVal()), 123];
112-
yield 'Writable' => [v::writable(), 'tests/fixtures/valid-image.png'];
113-
yield 'Xdigit' => [v::xdigit(), 'AF'];
114-
}
11530
}

tests/feature/SerializableTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
use Respect\Validation\Test\SmokeTestProvider;
11+
12+
test('Can be serialized and unserialized', function ($validator, $input): void {
13+
expect(
14+
unserialize(serialize($validator))->validate($input)->isValid(),
15+
)->toBeTrue();
16+
})->with(fn(): Generator => (new class {
17+
use SmokeTestProvider {
18+
provideValidatorInput as public __invoke;
19+
}
20+
})());

0 commit comments

Comments
 (0)