From 03334243365d2d1cefce44119884d8d3fc1b969d Mon Sep 17 00:00:00 2001 From: Alexandre Gomes Gaigalas Date: Tue, 20 Jan 2026 12:35:15 -0300 Subject: [PATCH] Setup Continuous Performance A new workflow, continuous-integration-perf.yml was introduced. It: - Checks out the `benchmarks` branch locally. - Runs the benchmarks, accounting for non-existant baselines and target (main/PR). - Stores the .phpbench storage folder and a human-readable report in the `benchmarks` branch. - Does not make a PR fail, and never reports a failure when merging to main. - Allows workflow_dispatch for quick re-runs, and has an option to reset the baseline in case something changes (GitHub runner setup gets faster/slower, major refactors, etc). Thus, it keeps a historical record of all benchmark results. These results can be viewed by exploring GitHub via the web interface and seeing the changes in `latest.md` (the human file commited). Additionally, one can clone the `benchmarks` branch and run `phpbench log` to explore the history in more detail. Some adjustments to previously added benchmarks were made: - Assertions were included in order to track time and memory tresholds. - The benchmarks are now more surgical, and address the concrete validators instead of the whole chain validate. These changes were made to make benchmarks more isolated, with the intention of adding chain-related benchmarks separately in the future. --- .../workflows/continuous-integration-perf.yml | 88 +++++ tests/benchmark/ValidatorBench.php | 21 +- tests/feature/SerializableTest.php | 2 +- tests/library/SmokeTestProvider.php | 330 +++++++++--------- 4 files changed, 271 insertions(+), 170 deletions(-) create mode 100644 .github/workflows/continuous-integration-perf.yml diff --git a/.github/workflows/continuous-integration-perf.yml b/.github/workflows/continuous-integration-perf.yml new file mode 100644 index 000000000..26041eb11 --- /dev/null +++ b/.github/workflows/continuous-integration-perf.yml @@ -0,0 +1,88 @@ +name: Continuous Integration (perf) + +on: + push: + paths-ignore: + - 'bin/**' + - 'docs/**' + pull_request: + paths-ignore: + - 'bin/**' + - 'docs/**' + workflow_dispatch: + inputs: + baseline: + description: 'Baseline mode. latest: compare against latest benchmarks; rebaseline: store new baseline.' + type: choice + default: 'latest' + options: + - 'latest' + - 'rebaseline' + +jobs: + tests: + name: Benchmarks + runs-on: ubuntu-latest + continue-on-error: true # This job is experimental + permissions: + contents: write + pull-requests: write + + strategy: + matrix: + php-version: + - "8.5" + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: true + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: xdebug + + - name: Install Dependencies + run: composer install --prefer-dist ${{ matrix.composer-extra-arguments }} + + - name: Fetch Benchmarks + run: | + git fetch origin benchmarks + mkdir -p .phpbench + git checkout origin/benchmarks -- .phpbench || echo "No previous benchmarks found" + + - name: Run Benchmarks + run: | + # Baseline does not exist or rebaseline requested. Generate it. + if [ -z "$(ls -A .phpbench)" ] || [ "${{ github.event.inputs.baseline || 'latest' }}" = "rebaseline" ]; then + vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} + + # On main branch push, update baseline with tolerance for failures. + elif [ "${GITHUB_REF}" = "refs/heads/main" ] && [ "${GITHUB_EVENT_NAME}" = "push" ]; then + vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} --ref=latest --tolerate-failure + + # On other branches, compare against latest baseline, fails if worse. + else + vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} --ref=latest + fi + + # Generate report for human consumption + vendor/bin/phpbench report --report=aggregate --ref=latest | + tail -n+2 | head -n-2 | tr '+' '|' > report.md + + cat report.md + + - name: Commit Benchmark Results + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout benchmarks + mv -f report.md latest.md + git add .phpbench latest.md + git commit -m "Store benchmark results [skip ci]" || echo "No changes to commit" + git push origin benchmarks diff --git a/tests/benchmark/ValidatorBench.php b/tests/benchmark/ValidatorBench.php index a52ff05b2..d16a9fa4d 100644 --- a/tests/benchmark/ValidatorBench.php +++ b/tests/benchmark/ValidatorBench.php @@ -18,13 +18,26 @@ class ValidatorBench use SmokeTestProvider; /** @param array $params */ + #[Bench\ParamProviders(['provideValidatorInput'])] + #[Bench\BeforeMethods('setFileUploadMock')] #[Bench\Iterations(10)] - #[Bench\RetryThreshold(10)] + #[Bench\RetryThreshold(5)] #[Bench\Revs(5)] - #[Bench\ParamProviders(['provideValidatorInput'])] - public function benchValidate(array $params): void + #[Bench\Warmup(1)] + #[Bench\Assert('mode(variant.time.avg) < mode(baseline.time.avg) +/- 10%')] + #[Bench\Assert('mode(variant.time.net) < mode(baseline.time.net) +/- 10%')] + #[Bench\Assert('mode(variant.mem.peak) < mode(baseline.mem.peak) +/- 10%')] + #[Bench\Assert('mode(variant.mem.real) < mode(baseline.mem.real) +/- 10%')] + #[Bench\Assert('mode(variant.mem.final) < mode(baseline.mem.final) +/- 10%')] + #[Bench\Subject] + public function evaluate(array $params): void { [$v, $input] = $params; - $v->validate($input); + $v->evaluate($input); + } + + public function setFileUploadMock(): void + { + set_mock_is_uploaded_file_return(true); } } diff --git a/tests/feature/SerializableTest.php b/tests/feature/SerializableTest.php index b399115cc..a46a95d19 100644 --- a/tests/feature/SerializableTest.php +++ b/tests/feature/SerializableTest.php @@ -12,7 +12,7 @@ test('Can be serialized and unserialized', function ($validator, $input): void { set_mock_is_uploaded_file_return(true); expect( - unserialize(serialize($validator))->validate($input)->isValid(), + unserialize(serialize($validator))->evaluate($input)->hasPassed, )->toBeTrue(); })->with(fn(): Generator => (new class { use SmokeTestProvider { diff --git a/tests/library/SmokeTestProvider.php b/tests/library/SmokeTestProvider.php index 13e9c3e9b..bac7cd924 100644 --- a/tests/library/SmokeTestProvider.php +++ b/tests/library/SmokeTestProvider.php @@ -10,8 +10,8 @@ namespace Respect\Validation\Test; use Generator; -use Respect\Validation\Mixins\Chain; -use Respect\Validation\ValidatorBuilder as v; +use Respect\Validation\Validator; +use Respect\Validation\Validators as vs; use stdClass; use function fopen; @@ -23,167 +23,167 @@ trait SmokeTestProvider { public static function provideValidatorInput(): Generator { - yield 'All' => [v::all(v::intVal()), [1, 2, 3]]; - yield 'AllOf' => [v::allOf(v::intVal(), v::greaterThan(0)), 5]; - yield 'Alnum' => [v::alnum(), 'abc123']; - yield 'Alpha' => [v::alpha(), 'abc']; - yield 'AlwaysInvalid' => [v::not(v::alwaysInvalid()), 'whatever']; - yield 'AlwaysValid' => [v::alwaysValid(), 'whatever']; - yield 'AnyOf' => [v::anyOf(v::intVal(), v::stringVal()), 5]; - yield 'ArrayType' => [v::arrayType(), []]; - yield 'ArrayVal' => [v::arrayVal(), []]; - yield 'Attributes' => [v::attributes(), (object) ['required' => true]]; - yield 'Base' => [v::base(2), '001001']; - yield 'Base64' => [v::base64(), 'U29tZSBCYXNlNjQgU3RyaW5n']; - yield 'Between' => [v::between(1, 10), 5]; - yield 'BetweenExclusive' => [v::betweenExclusive(1, 10), 5]; - yield 'Blank' => [v::blank(), '']; - yield 'BoolType' => [v::boolType(), true]; - yield 'BoolVal' => [v::boolVal(), true]; - yield 'Bsn' => [v::bsn(), '612890053']; - yield 'Call' => [v::call('array_keys', v::each(v::stringType())), ['a' => 'b']]; - yield 'CallableType' => [v::callableType(), [static::class, 'callableTarget']]; - yield 'Callback' => [v::callback('is_string'), 'valid']; - yield 'Charset' => [v::charset('UTF-8'), 'example']; - yield 'Circuit' => [v::circuit(v::intVal(), v::greaterThan(0)), 5]; - yield 'Cnh' => [v::cnh(), '02650306461']; - yield 'Cnpj' => [v::cnpj(), '11444777000161']; - yield 'Consonant' => [v::consonant(), 'bcdf']; - yield 'Contains' => [v::contains('needle'), 'haystack needle']; - yield 'ContainsAny' => [v::containsAny(['a', 'b']), 'abc']; - yield 'ContainsCount' => [v::containsCount('foo', 2), 'foo bar foo']; - yield 'Control' => [v::control(), "\n\r"]; - yield 'Countable' => [v::countable(), []]; - yield 'CountryCode' => [v::countryCode(), 'US']; - yield 'Cpf' => [v::cpf(), '11598647644']; - yield 'CreditCard' => [v::creditCard(), '4111111111111111']; - yield 'CurrencyCode' => [v::currencyCode(), 'USD']; - yield 'Date' => [v::date(), '2020-01-01']; - yield 'DateTime' => [v::dateTime(), '2020-01-01 12:00:00']; - yield 'DateTimeDiff' => [v::dateTimeDiff('years', v::greaterThan(18), 'd/m/Y'), '09/12/1990']; - yield 'Decimal' => [v::decimal(2), '1.23']; - yield 'Digit' => [v::digit(), '7']; - yield 'Directory' => [v::directory(), 'tests/fixtures']; - yield 'Domain' => [v::domain(), 'example.com']; - yield 'Each' => [v::each(v::stringType()), ['a', 'b']]; - yield 'Email' => [v::email(), 'bob@example.com']; - yield 'Emoji' => [v::emoji(), '😀']; - yield 'EndsWith' => [v::endsWith('.com'), 'example.com']; - yield 'Equals' => [v::equals('x'), 'x']; - yield 'Equivalent' => [v::equivalent(123), 123.0]; - yield 'Even' => [v::even(), 2]; - yield 'Executable' => [v::executable(), 'tests/fixtures/executable']; - yield 'Exists' => [v::exists(), 'tests/fixtures/valid-image.png']; - yield 'Extension' => [v::extension('png'), 'image.png']; - yield 'Factor' => [v::factor(0), 36]; - yield 'FalseVal' => [v::falseVal(), false]; - yield 'Falsy' => [v::falsy(), 0]; - yield 'Fibonacci' => [v::fibonacci(), 13]; - yield 'File' => [v::file(), __FILE__]; - yield 'FilterVar' => [v::filterVar(FILTER_VALIDATE_EMAIL), 'bob@example.com']; - yield 'Finite' => [v::finite(), 1.23]; - yield 'FloatType' => [v::floatType(), 1.23]; - yield 'FloatVal' => [v::floatVal(), 1.23]; - yield 'Graph' => [v::graph(), 'abc123!@#']; - yield 'GreaterThan' => [v::greaterThan(0), 1]; - yield 'GreaterThanOrEqual' => [v::greaterThanOrEqual(1), 1]; - yield 'Hetu' => [v::hetu(), '010106A9012']; - yield 'HexRgbColor' => [v::hexRgbColor(), '#FFAABB']; - yield 'Iban' => [v::iban(), 'SE35 5000 0000 0549 1000 0003']; - yield 'Identical' => [v::identical(123), 123]; - yield 'Image' => [v::image(), 'tests/fixtures/valid-image.png']; - yield 'Imei' => [v::imei(), '490154203237518']; - yield 'In' => [v::in(['a', 'b']), 'a']; - yield 'Infinite' => [v::infinite(), INF]; - yield 'Instance' => [v::instance(stdClass::class), new stdClass()]; - yield 'IntType' => [v::intType(), 123]; - yield 'IntVal' => [v::intVal(), 123]; - yield 'Ip' => [v::ip(), '127.0.0.1']; - yield 'Isbn' => [v::isbn(), '9783161484100']; - yield 'IterableType' => [v::iterableType(), []]; - yield 'IterableVal' => [v::iterableVal(), []]; - yield 'Json' => [v::json(), '{"key":"value"}']; - yield 'Key' => [v::key('name', v::stringType()), ['name' => 'value']]; - yield 'KeyExists' => [v::keyExists('name'), ['name' => 'value']]; - yield 'KeyOptional' => [v::keyOptional('missing', v::stringType()), ['name' => 'value']]; - yield 'KeySet' => [v::keySet(v::key('name', v::stringType())), ['name' => 'value']]; - yield 'LanguageCode' => [v::languageCode(), 'en']; - yield 'Lazy' => [v::lazy([static::class, 'callableLazy']), 123]; - yield 'LeapDate' => [v::leapDate('Y-m-d'), '2020-02-29']; - yield 'LeapYear' => [v::leapYear(), 2020]; - yield 'Length' => [v::length(v::equals(4)), 'abcd']; - yield 'LessThan' => [v::lessThan(10), 5]; - yield 'LessThanOrEqual' => [v::lessThanOrEqual(10), 10]; - yield 'Lowercase' => [v::lowercase(), 'abc']; - yield 'Luhn' => [v::luhn(), '2222400041240011']; - yield 'MacAddress' => [v::macAddress(), '00:11:22:33:44:55']; - yield 'Max' => [v::max(v::equals(30)), [10, 20, 30]]; - yield 'Min' => [v::min(v::equals(10)), [10, 20, 30]]; - yield 'Mimetype' => [v::mimetype('image/png'), 'tests/fixtures/valid-image.png']; - yield 'Multiple' => [v::multiple(3), 9]; - yield 'Named' => [v::named('MyValidator', v::intVal()), 123]; - yield 'Negative' => [v::negative(), -1]; - yield 'NfeAccessKey' => [v::nfeAccessKey(), '52060433009911002506550120000007800267301615']; - yield 'Nif' => [v::nif(), '12345678Z']; - yield 'Nip' => [v::nip(), '1645865777']; - yield 'NoneOf' => [v::noneOf(v::intVal(), v::floatVal()), 'foo']; - yield 'Not' => [v::not(v::trueVal()), false]; - yield 'NullOr' => [v::nullOr(v::intVal()), null]; - yield 'Number' => [v::number(), '123']; - yield 'NullType' => [v::nullType(), null]; - yield 'NumericVal' => [v::numericVal(), '123']; - yield 'ObjectType' => [v::objectType(), new stdClass()]; - yield 'Odd' => [v::odd(), 3]; - yield 'OneOf' => [v::oneOf(v::digit(), v::alpha()), 'AB']; - yield 'PerfectSquare' => [v::perfectSquare(), 16]; - yield 'Pesel' => [v::pesel(), '21120209256']; - yield 'Phone' => [v::phone(), '+1 650 253 00 00']; - yield 'PhpLabel' => [v::phpLabel(), 'valid_label']; - yield 'Pis' => [v::pis(), '120.0340.678-8']; - yield 'PolishIdCard' => [v::polishIdCard(), 'AYW036733']; - yield 'PortugueseNif' => [v::portugueseNif(), '123456789']; - yield 'Positive' => [v::positive(), 1]; - yield 'PostalCode' => [v::postalCode('US'), '12345']; - yield 'PrimeNumber' => [v::primeNumber(), 7]; - yield 'Printable' => [v::printable(), 'abc123!@#']; - yield 'Property' => [v::property('email', v::endsWith('@example.com')), (object) ['email' => 'a@example.com']]; - yield 'PropertyExists' => [v::propertyExists('email'), (object) ['email' => 'a@example.com']]; - yield 'PropertyOptional' => [v::propertyOptional('missing', v::email()), (object) ['email' => 'a@example.com']]; - yield 'PublicDomainSuffix' => [v::publicDomainSuffix(), 'co.uk']; - yield 'Punct' => [v::punct(), '!@#']; - yield 'Readable' => [v::readable(), 'tests/fixtures/valid-image.png']; - yield 'Regex' => [v::regex('/^[a-z]+$/'), 'abc']; - yield 'ResourceType' => [v::resourceType(), fopen('php://temp', 'r')]; - yield 'Roman' => [v::roman(), 'XIV']; - yield 'ScalarVal' => [v::scalarVal(), 'example']; - yield 'Size' => [v::size('KB', v::between(1, 1000)), 'tests/fixtures/valid-image.png']; - yield 'Slug' => [v::slug(), 'a-valid-slug']; - yield 'Sorted' => [v::sorted('ASC'), [1, 2, 3]]; - yield 'Space' => [v::space(), " \t\n"]; - yield 'Spaced' => [v::spaced(), 'a b c']; - yield 'StartsWith' => [v::startsWith('ex'), 'example']; - yield 'StringType' => [v::stringType(), 'example']; - yield 'StringVal' => [v::stringVal(), 'example']; - yield 'SubdivisionCode' => [v::subdivisionCode('US'), 'CA']; - yield 'Subset' => [v::subset(['a', 'b', 'c']), ['a', 'b']]; - yield 'SymbolicLink' => [v::symbolicLink(), 'tests/fixtures/symbolic-link']; - yield 'Templated' => [v::templated('Foo', v::stringVal()), 'foo']; - yield 'Time' => [v::time(), '12:34:56']; - yield 'Tld' => [v::tld(), 'com']; - yield 'TrueVal' => [v::trueVal(), true]; - yield 'Undef' => [v::undef(), null]; - yield 'UndefOr' => [v::undefOr(v::intVal()), null]; - yield 'Unique' => [v::unique(), [1, 2, 3]]; - yield 'Uploaded' => [v::uploaded(), 'tests/fixtures/valid-image.png']; - yield 'Uppercase' => [v::uppercase(), 'ABC']; - yield 'Url' => [v::url(), 'https://example.com']; - yield 'Uuid' => [v::uuid(), '123e4567-e89b-12d3-a456-426655440000']; - yield 'Version' => [v::version(), '1.2.3']; - yield 'VideoUrl' => [v::videoUrl(), 'https://www.youtube.com/watch?v=dQw4w9WgXcQ']; - yield 'Vowel' => [v::vowel(), 'aeiou']; - yield 'When' => [v::when(v::intVal(), v::alwaysValid(), v::alwaysInvalid()), 5]; - yield 'Writable' => [v::writable(), 'tests/fixtures/valid-image.png']; - yield 'Xdigit' => [v::xdigit(), 'AF']; + yield 'All' => [new vs\All(new vs\IntVal()), [1, 2, 3]]; + yield 'AllOf' => [new vs\AllOf(new vs\IntVal(), new vs\GreaterThan(0)), 5]; + yield 'Alnum' => [new vs\Alnum(), 'abc123']; + yield 'Alpha' => [new vs\Alpha(), 'abc']; + yield 'AlwaysInvalid' => [new vs\Not(new vs\AlwaysInvalid()), 'whatever']; + yield 'AlwaysValid' => [new vs\AlwaysValid(), 'whatever']; + yield 'AnyOf' => [new vs\AnyOf(new vs\IntVal(), new vs\StringVal()), 5]; + yield 'ArrayType' => [new vs\ArrayType(), []]; + yield 'ArrayVal' => [new vs\ArrayVal(), []]; + yield 'Attributes' => [new vs\Attributes(), (object) ['required' => true]]; + yield 'Base' => [new vs\Base(2), '001001']; + yield 'Base64' => [new vs\Base64(), 'U29tZSBCYXNlNjQgU3RyaW5n']; + yield 'Between' => [new vs\Between(1, 10), 5]; + yield 'BetweenExclusive' => [new vs\BetweenExclusive(1, 10), 5]; + yield 'Blank' => [new vs\Blank(), '']; + yield 'BoolType' => [new vs\BoolType(), true]; + yield 'BoolVal' => [new vs\BoolVal(), true]; + yield 'Bsn' => [new vs\Bsn(), '612890053']; + yield 'Call' => [new vs\Call('array_keys', new vs\Each(new vs\StringType())), ['a' => 'b']]; + yield 'CallableType' => [new vs\CallableType(), [static::class, 'callableTarget']]; + yield 'Callback' => [new vs\Callback('is_string'), 'valid']; + yield 'Charset' => [new vs\Charset('UTF-8'), 'example']; + yield 'Circuit' => [new vs\Circuit(new vs\IntVal(), new vs\GreaterThan(0)), 5]; + yield 'Cnh' => [new vs\Cnh(), '02650306461']; + yield 'Cnpj' => [new vs\Cnpj(), '11444777000161']; + yield 'Consonant' => [new vs\Consonant(), 'bcdf']; + yield 'Contains' => [new vs\Contains('needle'), 'haystack needle']; + yield 'ContainsAny' => [new vs\ContainsAny(['a', 'b']), 'abc']; + yield 'ContainsCount' => [new vs\ContainsCount('foo', 2), 'foo bar foo']; + yield 'Control' => [new vs\Control(), "\n\r"]; + yield 'Countable' => [new vs\Countable(), []]; + yield 'CountryCode' => [new vs\CountryCode(), 'US']; + yield 'Cpf' => [new vs\Cpf(), '11598647644']; + yield 'CreditCard' => [new vs\CreditCard(), '4111111111111111']; + yield 'CurrencyCode' => [new vs\CurrencyCode(), 'USD']; + yield 'Date' => [new vs\Date(), '2020-01-01']; + yield 'DateTime' => [new vs\DateTime(), '2020-01-01 12:00:00']; + yield 'DateTimeDiff' => [new vs\DateTimeDiff('years', new vs\GreaterThan(18), 'd/m/Y'), '09/12/1990']; + yield 'Decimal' => [new vs\Decimal(2), '1.23']; + yield 'Digit' => [new vs\Digit(), '7']; + yield 'Directory' => [new vs\Directory(), 'tests/fixtures']; + yield 'Domain' => [new vs\Domain(), 'example.com']; + yield 'Each' => [new vs\Each(new vs\StringType()), ['a', 'b']]; + yield 'Email' => [new vs\Email(), 'bob@example.com']; + yield 'Emoji' => [new vs\Emoji(), '😀']; + yield 'EndsWith' => [new vs\EndsWith('.com'), 'example.com']; + yield 'Equals' => [new vs\Equals('x'), 'x']; + yield 'Equivalent' => [new vs\Equivalent(123), 123.0]; + yield 'Even' => [new vs\Even(), 2]; + yield 'Executable' => [new vs\Executable(), 'tests/fixtures/executable']; + yield 'Exists' => [new vs\Exists(), 'tests/fixtures/valid-image.png']; + yield 'Extension' => [new vs\Extension('png'), 'image.png']; + yield 'Factor' => [new vs\Factor(0), 36]; + yield 'FalseVal' => [new vs\FalseVal(), false]; + yield 'Falsy' => [new vs\Falsy(), 0]; + yield 'Fibonacci' => [new vs\Fibonacci(), 13]; + yield 'File' => [new vs\File(), __FILE__]; + yield 'FilterVar' => [new vs\FilterVar(FILTER_VALIDATE_EMAIL), 'bob@example.com']; + yield 'Finite' => [new vs\Finite(), 1.23]; + yield 'FloatType' => [new vs\FloatType(), 1.23]; + yield 'FloatVal' => [new vs\FloatVal(), 1.23]; + yield 'Graph' => [new vs\Graph(), 'abc123!@#']; + yield 'GreaterThan' => [new vs\GreaterThan(0), 1]; + yield 'GreaterThanOrEqual' => [new vs\GreaterThanOrEqual(1), 1]; + yield 'Hetu' => [new vs\Hetu(), '010106A9012']; + yield 'HexRgbColor' => [new vs\HexRgbColor(), '#FFAABB']; + yield 'Iban' => [new vs\Iban(), 'SE35 5000 0000 0549 1000 0003']; + yield 'Identical' => [new vs\Identical(123), 123]; + yield 'Image' => [new vs\Image(), 'tests/fixtures/valid-image.png']; + yield 'Imei' => [new vs\Imei(), '490154203237518']; + yield 'In' => [new vs\In(['a', 'b']), 'a']; + yield 'Infinite' => [new vs\Infinite(), INF]; + yield 'Instance' => [new vs\Instance(stdClass::class), new stdClass()]; + yield 'IntType' => [new vs\IntType(), 123]; + yield 'IntVal' => [new vs\IntVal(), 123]; + yield 'Ip' => [new vs\Ip(), '127.0.0.1']; + yield 'Isbn' => [new vs\Isbn(), '9783161484100']; + yield 'IterableType' => [new vs\IterableType(), []]; + yield 'IterableVal' => [new vs\IterableVal(), []]; + yield 'Json' => [new vs\Json(), '{"key":"value"}']; + yield 'Key' => [new vs\Key('name', new vs\StringType()), ['name' => 'value']]; + yield 'KeyExists' => [new vs\KeyExists('name'), ['name' => 'value']]; + yield 'KeyOptional' => [new vs\KeyOptional('missing', new vs\StringType()), ['name' => 'value']]; + yield 'KeySet' => [new vs\KeySet(new vs\Key('name', new vs\StringType())), ['name' => 'value']]; + yield 'LanguageCode' => [new vs\LanguageCode(), 'en']; + yield 'Lazy' => [new vs\Lazy([static::class, 'callableLazy']), 123]; + yield 'LeapDate' => [new vs\LeapDate('Y-m-d'), '2020-02-29']; + yield 'LeapYear' => [new vs\LeapYear(), 2020]; + yield 'Length' => [new vs\Length(new vs\Equals(4)), 'abcd']; + yield 'LessThan' => [new vs\LessThan(10), 5]; + yield 'LessThanOrEqual' => [new vs\LessThanOrEqual(10), 10]; + yield 'Lowercase' => [new vs\Lowercase(), 'abc']; + yield 'Luhn' => [new vs\Luhn(), '2222400041240011']; + yield 'MacAddress' => [new vs\MacAddress(), '00:11:22:33:44:55']; + yield 'Max' => [new vs\Max(new vs\Equals(30)), [10, 20, 30]]; + yield 'Min' => [new vs\Min(new vs\Equals(10)), [10, 20, 30]]; + yield 'Mimetype' => [new vs\Mimetype('image/png'), 'tests/fixtures/valid-image.png']; + yield 'Multiple' => [new vs\Multiple(3), 9]; + yield 'Named' => [new vs\Named('MyValidator', new vs\IntVal()), 123]; + yield 'Negative' => [new vs\Negative(), -1]; + yield 'NfeAccessKey' => [new vs\NfeAccessKey(), '52060433009911002506550120000007800267301615']; + yield 'Nif' => [new vs\Nif(), '12345678Z']; + yield 'Nip' => [new vs\Nip(), '1645865777']; + yield 'NoneOf' => [new vs\NoneOf(new vs\IntVal(), new vs\FloatVal()), 'foo']; + yield 'Not' => [new vs\Not(new vs\TrueVal()), false]; + yield 'NullOr' => [new vs\NullOr(new vs\IntVal()), null]; + yield 'Number' => [new vs\Number(), '123']; + yield 'NullType' => [new vs\NullType(), null]; + yield 'NumericVal' => [new vs\NumericVal(), '123']; + yield 'ObjectType' => [new vs\ObjectType(), new stdClass()]; + yield 'Odd' => [new vs\Odd(), 3]; + yield 'OneOf' => [new vs\OneOf(new vs\Digit(), new vs\Alpha()), 'AB']; + yield 'PerfectSquare' => [new vs\PerfectSquare(), 16]; + yield 'Pesel' => [new vs\Pesel(), '21120209256']; + yield 'Phone' => [new vs\Phone(), '+1 650 253 00 00']; + yield 'PhpLabel' => [new vs\PhpLabel(), 'valid_label']; + yield 'Pis' => [new vs\Pis(), '120.0340.678-8']; + yield 'PolishIdCard' => [new vs\PolishIdCard(), 'AYW036733']; + yield 'PortugueseNif' => [new vs\PortugueseNif(), '123456789']; + yield 'Positive' => [new vs\Positive(), 1]; + yield 'PostalCode' => [new vs\PostalCode('US'), '12345']; + yield 'PrimeNumber' => [new vs\PrimeNumber(), 7]; + yield 'Printable' => [new vs\Printable(), 'abc123!@#']; + yield 'Property' => [new vs\Property('age', new vs\IntVal()), (object) ['age' => 18]]; + yield 'PropertyExists' => [new vs\PropertyExists('age'), (object) ['age' => 18]]; + yield 'PropertyOptional' => [new vs\PropertyOptional('missing', new vs\Email()), (object) []]; + yield 'PublicDomainSuffix' => [new vs\PublicDomainSuffix(), 'co.uk']; + yield 'Punct' => [new vs\Punct(), '!@#']; + yield 'Readable' => [new vs\Readable(), 'tests/fixtures/valid-image.png']; + yield 'Regex' => [new vs\Regex('/^[a-z]+$/'), 'abc']; + yield 'ResourceType' => [new vs\ResourceType(), fopen('php://temp', 'r')]; + yield 'Roman' => [new vs\Roman(), 'XIV']; + yield 'ScalarVal' => [new vs\ScalarVal(), 'example']; + yield 'Size' => [new vs\Size('KB', new vs\Between(1, 1000)), 'tests/fixtures/valid-image.png']; + yield 'Slug' => [new vs\Slug(), 'a-valid-slug']; + yield 'Sorted' => [new vs\Sorted('ASC'), [1, 2, 3]]; + yield 'Space' => [new vs\Space(), " \t\n"]; + yield 'Spaced' => [new vs\Spaced(), 'a b c']; + yield 'StartsWith' => [new vs\StartsWith('ex'), 'example']; + yield 'StringType' => [new vs\StringType(), 'example']; + yield 'StringVal' => [new vs\StringVal(), 'example']; + yield 'SubdivisionCode' => [new vs\SubdivisionCode('US'), 'CA']; + yield 'Subset' => [new vs\Subset(['a', 'b', 'c']), ['a', 'b']]; + yield 'SymbolicLink' => [new vs\SymbolicLink(), 'tests/fixtures/symbolic-link']; + yield 'Templated' => [new vs\Templated('Foo', new vs\StringVal()), 'foo']; + yield 'Time' => [new vs\Time(), '12:34:56']; + yield 'Tld' => [new vs\Tld(), 'com']; + yield 'TrueVal' => [new vs\TrueVal(), true]; + yield 'Undef' => [new vs\Undef(), null]; + yield 'UndefOr' => [new vs\UndefOr(new vs\IntVal()), null]; + yield 'Unique' => [new vs\Unique(), [1, 2, 3]]; + yield 'Uploaded' => [new vs\Uploaded(), 'tests/fixtures/valid-image.png']; + yield 'Uppercase' => [new vs\Uppercase(), 'ABC']; + yield 'Url' => [new vs\Url(), 'https://example.com']; + yield 'Uuid' => [new vs\Uuid(), '123e4567-e89b-12d3-a456-426655440000']; + yield 'Version' => [new vs\Version(), '1.2.3']; + yield 'VideoUrl' => [new vs\VideoUrl(), 'https://www.youtube.com/watch?v=dQw4w9WgXcQ']; + yield 'Vowel' => [new vs\Vowel(), 'aeiou']; + yield 'When' => [new vs\When(new vs\IntVal(), new vs\AlwaysValid(), new vs\AlwaysInvalid()), 5]; + yield 'Writable' => [new vs\Writable(), 'tests/fixtures/valid-image.png']; + yield 'Xdigit' => [new vs\Xdigit(), 'AF']; } public static function callableTarget(): true @@ -191,8 +191,8 @@ public static function callableTarget(): true return true; } - public static function callableLazy(): v|Chain + public static function callableLazy(): Validator { - return v::intVal(); + return new vs\IntVal(); } }