diff --git a/.github/workflows/ci-code.yml b/.github/workflows/ci-code.yml
index 97d20f988..272b83e6a 100644
--- a/.github/workflows/ci-code.yml
+++ b/.github/workflows/ci-code.yml
@@ -56,4 +56,5 @@ jobs:
- run: composer phpcs
- run: composer phpstan
+ - run: composer inference
- run: bin/console lint:mixin
diff --git a/REUSE.toml b/REUSE.toml
index b4829af26..4dce1d282 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -1,6 +1,6 @@
version = 1
[[annotations]]
-path = [ "*.yml", "*.yaml", ".git*", "*.dist", "docs/.pages", "docs/validators/.pages", "composer.json", "composer.lock", "tests/fixtures/*", ".github/*.yml", ".github/actions/**.yml", ".github/workflows/**.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/ISSUE_TEMPLATE/**" ]
+path = [ "fluent.neon", "*.yml", "*.yaml", ".git*", "*.dist", "docs/.pages", "docs/validators/.pages", "composer.json", "composer.lock", "tests/fixtures/*", ".github/*.yml", ".github/actions/**.yml", ".github/workflows/**.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/ISSUE_TEMPLATE/**" ]
SPDX-FileCopyrightText = "Respect Project Contributors"
SPDX-License-Identifier = "MIT"
diff --git a/composer.json b/composer.json
index 0177f54d2..12cd73a86 100644
--- a/composer.json
+++ b/composer.json
@@ -23,6 +23,7 @@
"php": ">=8.5",
"php-di/php-di": "^7.1",
"psr/container": "^2.0",
+ "respect/fluent": "^2.0",
"respect/string-formatter": "^1.7",
"respect/stringifier": "^3.0",
"symfony/polyfill-intl-idn": "^1.33",
@@ -36,13 +37,15 @@
"pestphp/pest": "^4.2",
"phpbench/phpbench": "^1.4",
"phpstan/extension-installer": "^1.4",
- "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan": "^2.1",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^12.5",
"psr/http-message": "^1.0 || ^2.0",
"ramsey/uuid": "^4",
"respect/coding-standard": "^5.0",
+ "respect/fluent-analysis": "^2.0",
+ "respect/fluentgen": "^2.0",
"sebastian/diff": "^7.0",
"sokil/php-isocodes": "^4.2.1",
"sokil/php-isocodes-db-only": "^4.0",
@@ -61,6 +64,11 @@
"sokil/php-isocodes": "Enable rules that validate ISO codes",
"sokil/php-isocodes-db-only": "Enable rules that validate ISO codes"
},
+ "extra": {
+ "phpstan": {
+ "includes": ["fluent.neon"]
+ }
+ },
"autoload": {
"psr-4": {
"Respect\\Validation\\": "src/"
@@ -71,7 +79,8 @@
"psr-4": {
"Respect\\Dev\\": "src-dev/",
"Respect\\Validation\\": "tests/unit/",
- "Respect\\Validation\\Test\\": "tests/src/"
+ "Respect\\Validation\\Test\\": "tests/src/",
+ "Respect\\Validation\\Test\\Inference\\": "tests/inference/"
}
},
"scripts": {
@@ -84,6 +93,7 @@
"phpcs": "vendor/bin/phpcs",
"phpstan": "vendor/bin/phpstan analyze",
"phpunit": "vendor/bin/phpunit --testsuite=unit",
+ "inference": "vendor/bin/phpunit --testsuite=inference",
"smoke-complete": "bin/console smoke-tests:check-complete",
"spdx-lint": "bin/console lint:spdx",
"qa": [
@@ -92,6 +102,7 @@
"@phpstan",
"@phpunit",
"@pest",
+ "@inference",
"@docs",
"@smoke-complete"
]
diff --git a/composer.lock b/composer.lock
index 8a5920080..81b98b6da 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "9851274a86bdca1f0835e29e06bd8858",
+ "content-hash": "0e648fd112253307245f3508fa199796",
"packages": [
{
"name": "laravel/serializable-closure",
- "version": "v2.0.9",
+ "version": "v2.0.10",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "8f631589ab07b7b52fead814965f5a800459cb3e"
+ "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e",
- "reference": "8f631589ab07b7b52fead814965f5a800459cb3e",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669",
+ "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669",
"shasum": ""
},
"require": {
@@ -65,7 +65,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2026-02-03T06:55:34+00:00"
+ "time": "2026-02-20T19:59:49+00:00"
},
{
"name": "php-di/invoker",
@@ -248,6 +248,59 @@
},
"time": "2021-11-05T16:47:00+00:00"
},
+ {
+ "name": "respect/fluent",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Respect/Fluent.git",
+ "reference": "f32c76e37a82a9e63d6fe700a27201534f72da60"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Respect/Fluent/zipball/f32c76e37a82a9e63d6fe700a27201534f72da60",
+ "reference": "f32c76e37a82a9e63d6fe700a27201534f72da60",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.5"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^12.5",
+ "respect/coding-standard": "^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Respect\\Fluent\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Respect/Fluent Contributors",
+ "homepage": "https://github.com/Respect/Fluent/graphs/contributors"
+ }
+ ],
+ "description": "Namespace-aware fluent class resolution",
+ "keywords": [
+ "builder",
+ "fluent",
+ "respect"
+ ],
+ "support": {
+ "issues": "https://github.com/Respect/Fluent/issues",
+ "source": "https://github.com/Respect/Fluent/tree/2.0.1"
+ },
+ "time": "2026-03-26T04:24:51+00:00"
+ },
{
"name": "respect/string-formatter",
"version": "1.7.0",
@@ -706,16 +759,16 @@
"packages-dev": [
{
"name": "brianium/paratest",
- "version": "v7.19.0",
+ "version": "v7.19.2",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
- "reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6"
+ "reference": "66e4f7910cecf67736bccf2b8bd53a2e3eb98bd9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paratestphp/paratest/zipball/7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6",
- "reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6",
+ "url": "https://api.github.com/repos/paratestphp/paratest/zipball/66e4f7910cecf67736bccf2b8bd53a2e3eb98bd9",
+ "reference": "66e4f7910cecf67736bccf2b8bd53a2e3eb98bd9",
"shasum": ""
},
"require": {
@@ -729,9 +782,9 @@
"phpunit/php-code-coverage": "^12.5.3 || ^13.0.1",
"phpunit/php-file-iterator": "^6.0.1 || ^7",
"phpunit/php-timer": "^8 || ^9",
- "phpunit/phpunit": "^12.5.9 || ^13",
+ "phpunit/phpunit": "^12.5.14 || ^13.0.5",
"sebastian/environment": "^8.0.3 || ^9",
- "symfony/console": "^7.4.4 || ^8.0.4",
+ "symfony/console": "^7.4.7 || ^8.0.7",
"symfony/process": "^7.4.5 || ^8.0.5"
},
"require-dev": {
@@ -739,11 +792,11 @@
"ext-pcntl": "*",
"ext-pcov": "*",
"ext-posix": "*",
- "phpstan/phpstan": "^2.1.38",
- "phpstan/phpstan-deprecation-rules": "^2.0.3",
- "phpstan/phpstan-phpunit": "^2.0.12",
- "phpstan/phpstan-strict-rules": "^2.0.8",
- "symfony/filesystem": "^7.4.0 || ^8.0.1"
+ "phpstan/phpstan": "^2.1.40",
+ "phpstan/phpstan-deprecation-rules": "^2.0.4",
+ "phpstan/phpstan-phpunit": "^2.0.16",
+ "phpstan/phpstan-strict-rules": "^2.0.10",
+ "symfony/filesystem": "^7.4.6 || ^8.0.6"
},
"bin": [
"bin/paratest",
@@ -783,7 +836,7 @@
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
- "source": "https://github.com/paratestphp/paratest/tree/v7.19.0"
+ "source": "https://github.com/paratestphp/paratest/tree/v7.19.2"
},
"funding": [
{
@@ -795,20 +848,20 @@
"type": "paypal"
}
],
- "time": "2026-02-06T10:53:26+00:00"
+ "time": "2026-03-09T14:33:17+00:00"
},
{
"name": "brick/math",
- "version": "0.14.7",
+ "version": "0.14.8",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
- "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50"
+ "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50",
- "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50",
+ "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629",
+ "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629",
"shasum": ""
},
"require": {
@@ -847,7 +900,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
- "source": "https://github.com/brick/math/tree/0.14.7"
+ "source": "https://github.com/brick/math/tree/0.14.8"
},
"funding": [
{
@@ -855,7 +908,7 @@
"type": "github"
}
],
- "time": "2026-02-07T10:57:35+00:00"
+ "time": "2026-02-10T14:33:43+00:00"
},
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
@@ -2068,20 +2121,20 @@
},
{
"name": "pestphp/pest",
- "version": "v4.4.2",
+ "version": "v4.4.3",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest.git",
- "reference": "5d42e8fe3ae1d9fdf7c9f73ee88138fd30265701"
+ "reference": "e6ab897594312728ef2e32d586cb4f6780b1b495"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pestphp/pest/zipball/5d42e8fe3ae1d9fdf7c9f73ee88138fd30265701",
- "reference": "5d42e8fe3ae1d9fdf7c9f73ee88138fd30265701",
+ "url": "https://api.github.com/repos/pestphp/pest/zipball/e6ab897594312728ef2e32d586cb4f6780b1b495",
+ "reference": "e6ab897594312728ef2e32d586cb4f6780b1b495",
"shasum": ""
},
"require": {
- "brianium/paratest": "^7.19.0",
+ "brianium/paratest": "^7.19.2",
"nunomaduro/collision": "^8.9.1",
"nunomaduro/termwind": "^2.4.0",
"pestphp/pest-plugin": "^4.0.0",
@@ -2089,12 +2142,12 @@
"pestphp/pest-plugin-mutate": "^4.0.1",
"pestphp/pest-plugin-profanity": "^4.2.1",
"php": "^8.3.0",
- "phpunit/phpunit": "^12.5.12",
+ "phpunit/phpunit": "^12.5.14",
"symfony/process": "^7.4.5|^8.0.5"
},
"conflict": {
"filp/whoops": "<2.18.3",
- "phpunit/phpunit": ">12.5.12",
+ "phpunit/phpunit": ">12.5.14",
"sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0"
},
@@ -2168,7 +2221,7 @@
],
"support": {
"issues": "https://github.com/pestphp/pest/issues",
- "source": "https://github.com/pestphp/pest/tree/v4.4.2"
+ "source": "https://github.com/pestphp/pest/tree/v4.4.3"
},
"funding": [
{
@@ -2180,7 +2233,7 @@
"type": "github"
}
],
- "time": "2026-03-10T21:09:12+00:00"
+ "time": "2026-03-21T13:14:39+00:00"
},
{
"name": "pestphp/pest-plugin",
@@ -2625,16 +2678,16 @@
},
{
"name": "phpbench/phpbench",
- "version": "1.5.1",
+ "version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/phpbench/phpbench.git",
- "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c"
+ "reference": "661c8c6abbc7734986cf7bc6062c237fbb450461"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpbench/phpbench/zipball/9a28fd0833f11171b949843c6fd663eb69b6d14c",
- "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c",
+ "url": "https://api.github.com/repos/phpbench/phpbench/zipball/661c8c6abbc7734986cf7bc6062c237fbb450461",
+ "reference": "661c8c6abbc7734986cf7bc6062c237fbb450461",
"shasum": ""
},
"require": {
@@ -2712,7 +2765,7 @@
],
"support": {
"issues": "https://github.com/phpbench/phpbench/issues",
- "source": "https://github.com/phpbench/phpbench/tree/1.5.1"
+ "source": "https://github.com/phpbench/phpbench/tree/1.6.1"
},
"funding": [
{
@@ -2720,7 +2773,7 @@
"type": "github"
}
],
- "time": "2026-03-05T08:18:58+00:00"
+ "time": "2026-03-22T10:27:20+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@@ -2777,16 +2830,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
- "version": "6.0.2",
+ "version": "6.0.3",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "897b5986ece6b4f9d8413fea345c7d49c757d6bf"
+ "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/897b5986ece6b4f9d8413fea345c7d49c757d6bf",
- "reference": "897b5986ece6b4f9d8413fea345c7d49c757d6bf",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582",
+ "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582",
"shasum": ""
},
"require": {
@@ -2836,9 +2889,9 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
- "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.2"
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3"
},
- "time": "2026-03-01T18:43:49+00:00"
+ "time": "2026-03-18T20:49:53+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@@ -2995,11 +3048,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "2.1.42",
+ "version": "2.1.44",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0",
- "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4a88c083c668b2c364a425c9b3171b2d9ea5d218",
+ "reference": "4a88c083c668b2c364a425c9b3171b2d9ea5d218",
"shasum": ""
},
"require": {
@@ -3044,7 +3097,7 @@
"type": "github"
}
],
- "time": "2026-03-17T14:58:32+00:00"
+ "time": "2026-03-25T17:34:21+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -3500,16 +3553,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.5.12",
+ "version": "12.5.14",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "418e06b3b46b0d54bad749ff4907fc7dfb530199"
+ "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/418e06b3b46b0d54bad749ff4907fc7dfb530199",
- "reference": "418e06b3b46b0d54bad749ff4907fc7dfb530199",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0",
+ "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0",
"shasum": ""
},
"require": {
@@ -3578,7 +3631,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.12"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.14"
},
"funding": [
{
@@ -3602,7 +3655,7 @@
"type": "tidelift"
}
],
- "time": "2026-02-16T08:34:36+00:00"
+ "time": "2026-02-18T12:38:40+00:00"
},
{
"name": "psr/cache",
@@ -4004,6 +4057,115 @@
},
"time": "2026-01-19T10:34:07+00:00"
},
+ {
+ "name": "respect/fluent-analysis",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Respect/FluentAnalysis.git",
+ "reference": "c290b858838b1e71cec2d7f2dec72b0e6b82ed49"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Respect/FluentAnalysis/zipball/c290b858838b1e71cec2d7f2dec72b0e6b82ed49",
+ "reference": "c290b858838b1e71cec2d7f2dec72b0e6b82ed49",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.5",
+ "phpstan/phpstan": "^2.1",
+ "respect/fluent": "^2.0",
+ "symfony/console": "^6.0|^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.5",
+ "respect/coding-standard": "^5.0"
+ },
+ "bin": [
+ "bin/fluent-analysis"
+ ],
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Respect\\FluentAnalysis\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "description": "PHPStan extension for Respect/Fluent builder method resolution",
+ "support": {
+ "issues": "https://github.com/Respect/FluentAnalysis/issues",
+ "source": "https://github.com/Respect/FluentAnalysis/tree/2.0.1"
+ },
+ "time": "2026-03-26T04:27:08+00:00"
+ },
+ {
+ "name": "respect/fluentgen",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Respect/FluentGen.git",
+ "reference": "6a9065516f403c5f5abc86646290bd08e44c538e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Respect/FluentGen/zipball/6a9065516f403c5f5abc86646290bd08e44c538e",
+ "reference": "6a9065516f403c5f5abc86646290bd08e44c538e",
+ "shasum": ""
+ },
+ "require": {
+ "nette/php-generator": "^4.1",
+ "php": "^8.5"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^12.5",
+ "respect/coding-standard": "^5.0",
+ "respect/fluent": "^2.0"
+ },
+ "suggest": {
+ "respect/fluent": "Enables #[Composable] prefix composition support"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Respect\\FluentGen\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Respect/FluentGen Contributors",
+ "homepage": "https://github.com/Respect/FluentGen/graphs/contributors"
+ }
+ ],
+ "description": "Code generation for fluent builder interfaces",
+ "keywords": [
+ "fluent",
+ "fluentgen",
+ "mixin",
+ "respect"
+ ],
+ "support": {
+ "issues": "https://github.com/Respect/FluentGen/issues",
+ "source": "https://github.com/Respect/FluentGen/tree/2.0.0"
+ },
+ "time": "2026-03-25T05:50:09+00:00"
+ },
{
"name": "sebastian/cli-parser",
"version": "4.2.0",
@@ -4292,16 +4454,16 @@
},
{
"name": "sebastian/environment",
- "version": "8.0.3",
+ "version": "8.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68"
+ "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
- "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11",
+ "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11",
"shasum": ""
},
"require": {
@@ -4344,7 +4506,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
- "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3"
+ "source": "https://github.com/sebastianbergmann/environment/tree/8.0.4"
},
"funding": [
{
@@ -4364,7 +4526,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-12T14:11:56+00:00"
+ "time": "2026-03-15T07:05:40+00:00"
},
{
"name": "sebastian/exporter",
@@ -4967,32 +5129,32 @@
},
{
"name": "slevomat/coding-standard",
- "version": "8.27.1",
+ "version": "8.28.1",
"source": {
"type": "git",
"url": "https://github.com/slevomat/coding-standard.git",
- "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769"
+ "reference": "66151cfbd25b50e8becd9f809fb704f01fd4d6f2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/29bdaee8b65e7ed2b8e702b01852edba8bae1769",
- "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/66151cfbd25b50e8becd9f809fb704f01fd4d6f2",
+ "reference": "66151cfbd25b50e8becd9f809fb704f01fd4d6f2",
"shasum": ""
},
"require": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.2.0",
"php": "^7.4 || ^8.0",
- "phpstan/phpdoc-parser": "^2.3.1",
+ "phpstan/phpdoc-parser": "^2.3.2",
"squizlabs/php_codesniffer": "^4.0.1"
},
"require-dev": {
- "phing/phing": "3.0.1|3.1.1",
+ "phing/phing": "3.0.1|3.1.2",
"php-parallel-lint/php-parallel-lint": "1.4.0",
- "phpstan/phpstan": "2.1.37",
- "phpstan/phpstan-deprecation-rules": "2.0.3",
- "phpstan/phpstan-phpunit": "2.0.12",
- "phpstan/phpstan-strict-rules": "2.0.7",
- "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.49|12.5.7"
+ "phpstan/phpstan": "2.1.42",
+ "phpstan/phpstan-deprecation-rules": "2.0.4",
+ "phpstan/phpstan-phpunit": "2.0.16",
+ "phpstan/phpstan-strict-rules": "2.0.10",
+ "phpunit/phpunit": "9.6.34|10.5.63|11.4.4|11.5.50|12.5.14"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -5016,7 +5178,7 @@
],
"support": {
"issues": "https://github.com/slevomat/coding-standard/issues",
- "source": "https://github.com/slevomat/coding-standard/tree/8.27.1"
+ "source": "https://github.com/slevomat/coding-standard/tree/8.28.1"
},
"funding": [
{
@@ -5028,7 +5190,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-25T15:57:07+00:00"
+ "time": "2026-03-22T17:22:38+00:00"
},
{
"name": "sokil/php-isocodes",
@@ -5264,39 +5426,47 @@
},
{
"name": "symfony/console",
- "version": "v8.0.7",
+ "version": "v7.4.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
+ "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
- "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
+ "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d",
+ "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d",
"shasum": ""
},
"require": {
- "php": ">=8.4",
- "symfony/polyfill-mbstring": "^1.0",
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^7.4|^8.0"
+ "symfony/string": "^7.2|^8.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
- "symfony/config": "^7.4|^8.0",
- "symfony/dependency-injection": "^7.4|^8.0",
- "symfony/event-dispatcher": "^7.4|^8.0",
- "symfony/http-foundation": "^7.4|^8.0",
- "symfony/http-kernel": "^7.4|^8.0",
- "symfony/lock": "^7.4|^8.0",
- "symfony/messenger": "^7.4|^8.0",
- "symfony/process": "^7.4|^8.0",
- "symfony/stopwatch": "^7.4|^8.0",
- "symfony/var-dumper": "^7.4|^8.0"
+ "symfony/config": "^6.4|^7.0|^8.0",
+ "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+ "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
+ "symfony/http-foundation": "^6.4|^7.0|^8.0",
+ "symfony/http-kernel": "^6.4|^7.0|^8.0",
+ "symfony/lock": "^6.4|^7.0|^8.0",
+ "symfony/messenger": "^6.4|^7.0|^8.0",
+ "symfony/process": "^6.4|^7.0|^8.0",
+ "symfony/stopwatch": "^6.4|^7.0|^8.0",
+ "symfony/var-dumper": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
@@ -5330,7 +5500,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v8.0.7"
+ "source": "https://github.com/symfony/console/tree/v7.4.7"
},
"funding": [
{
@@ -5350,7 +5520,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-06T14:06:22+00:00"
+ "time": "2026-03-06T14:06:20+00:00"
},
{
"name": "symfony/deprecation-contracts",
diff --git a/fluent.neon b/fluent.neon
new file mode 100644
index 000000000..e2b468514
--- /dev/null
+++ b/fluent.neon
@@ -0,0 +1,4 @@
+parameters:
+ fluent:
+ builders:
+ - builder: Respect\Validation\ValidatorBuilder
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 20d17139b..f00329292 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -23,6 +23,10 @@
tests/Pest.php
+ tests/inference/
+
+
+ tests/inference/
src/Mixins/
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 9a0d4638f..734c91938 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -1,3 +1,6 @@
+includes:
+ - fluent.neon
+
parameters:
fileExtensions:
- php
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 51339be5c..8dae82703 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -14,6 +14,9 @@
./tests/feature
+
+ tests/inference/
+
diff --git a/src-dev/CodeGen/Config.php b/src-dev/CodeGen/Config.php
deleted file mode 100644
index af3a8864f..000000000
--- a/src-dev/CodeGen/Config.php
+++ /dev/null
@@ -1,23 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen;
-
-final readonly class Config
-{
- public function __construct(
- public string $sourceDir,
- public string $sourceNamespace,
- public string $outputDir,
- public string $outputNamespace,
- public OutputFormatter $outputFormatter = new OutputFormatter(),
- ) {
- }
-}
diff --git a/src-dev/CodeGen/FluentBuilder/MethodBuilder.php b/src-dev/CodeGen/FluentBuilder/MethodBuilder.php
deleted file mode 100644
index 72da85787..000000000
--- a/src-dev/CodeGen/FluentBuilder/MethodBuilder.php
+++ /dev/null
@@ -1,169 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen\FluentBuilder;
-
-use Nette\PhpGenerator\Method;
-use Nette\PhpGenerator\PhpNamespace;
-use ReflectionClass;
-use ReflectionNamedType;
-use ReflectionParameter;
-use ReflectionUnionType;
-
-use function count;
-use function implode;
-use function in_array;
-use function is_object;
-use function lcfirst;
-use function preg_replace;
-use function sort;
-use function str_starts_with;
-use function ucfirst;
-
-final class MethodBuilder
-{
- /**
- * @param array $excludedTypePrefixes
- * @param array $excludedTypeNames
- */
- public function __construct(
- private readonly array $excludedTypePrefixes = [],
- private readonly array $excludedTypeNames = [],
- ) {
- }
-
- public function build(
- PhpNamespace $namespace,
- ReflectionClass $nodeReflection,
- string $returnType,
- string|null $prefix = null,
- bool $static = false,
- ReflectionParameter|null $prefixParameter = null,
- ): Method {
- $originalName = $nodeReflection->getShortName();
- $name = $prefix ? $prefix . ucfirst($originalName) : lcfirst($originalName);
-
- $method = new Method($name);
- $method->setPublic()->setReturnType($returnType);
-
- if ($static) {
- $method->setStatic();
- }
-
- if ($prefixParameter !== null) {
- $this->addPrefixParameter($method, $prefixParameter);
- }
-
- $constructor = $nodeReflection->getConstructor();
- if ($constructor === null) {
- return $method;
- }
-
- $comment = $constructor->getDocComment();
- if ($comment) {
- $method->addComment(preg_replace('@(/\*\* *| +\* +| +\*/)@', '', $comment));
- }
-
- foreach ($constructor->getParameters() as $reflectionParameter) {
- $this->addParameter($method, $reflectionParameter, $namespace);
- }
-
- return $method;
- }
-
- private function addPrefixParameter(Method $method, ReflectionParameter $reflectionParameter): void
- {
- $type = $reflectionParameter->getType();
- $types = [];
-
- if ($type instanceof ReflectionUnionType) {
- foreach ($type->getTypes() as $subType) {
- $types[] = $subType->getName();
- }
-
- sort($types);
- } elseif ($type instanceof ReflectionNamedType) {
- $types[] = $type->getName();
- }
-
- $method->addParameter($reflectionParameter->getName())->setType(implode('|', $types));
- }
-
- private function addParameter(
- Method $method,
- ReflectionParameter $reflectionParameter,
- PhpNamespace $namespace,
- ): void {
- if ($reflectionParameter->isVariadic()) {
- $method->setVariadic();
- }
-
- $type = $reflectionParameter->getType();
- $types = [];
-
- if ($type instanceof ReflectionUnionType) {
- foreach ($type->getTypes() as $subType) {
- $types[] = $subType->getName();
- if ($subType->isBuiltin()) {
- continue;
- }
-
- $namespace->addUse($subType->getName());
- }
- } elseif ($type instanceof ReflectionNamedType) {
- $types[] = $type->getName();
-
- if ($this->isExcludedType($type->getName())) {
- return;
- }
-
- if (!$type->isBuiltin()) {
- $namespace->addUse($type->getName());
- }
- }
-
- $parameter = $method->addParameter($reflectionParameter->getName());
- $parameter->setType(implode('|', $types));
-
- if (!$reflectionParameter->isDefaultValueAvailable()) {
- $parameter->setNullable($reflectionParameter->isOptional());
- }
-
- if (count($types) > 1 || $reflectionParameter->isVariadic()) {
- $parameter->setNullable(false);
- }
-
- if (!$reflectionParameter->isDefaultValueAvailable()) {
- return;
- }
-
- $defaultValue = $reflectionParameter->getDefaultValue();
- if (is_object($defaultValue)) {
- $parameter->setDefaultValue(null);
- $parameter->setNullable(true);
-
- return;
- }
-
- $parameter->setDefaultValue($defaultValue);
- $parameter->setNullable(false);
- }
-
- private function isExcludedType(string $typeName): bool
- {
- foreach ($this->excludedTypePrefixes as $excludedPrefix) {
- if (str_starts_with($typeName, $excludedPrefix)) {
- return true;
- }
- }
-
- return in_array($typeName, $this->excludedTypeNames, true);
- }
-}
diff --git a/src-dev/CodeGen/FluentBuilder/Mixin.php b/src-dev/CodeGen/FluentBuilder/Mixin.php
deleted file mode 100644
index 0e211711c..000000000
--- a/src-dev/CodeGen/FluentBuilder/Mixin.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen\FluentBuilder;
-
-use Attribute;
-
-#[Attribute(Attribute::TARGET_CLASS)]
-final readonly class Mixin
-{
- /**
- * @param array $exclude
- * @param array $include
- */
- public function __construct(
- public string|null $prefix = null,
- public bool $prefixParameter = false,
- public bool $requireInclusion = false,
- public array $exclude = [],
- public array $include = [],
- ) {
- }
-}
diff --git a/src-dev/CodeGen/FluentBuilder/MixinGenerator.php b/src-dev/CodeGen/FluentBuilder/MixinGenerator.php
deleted file mode 100644
index 3f605873a..000000000
--- a/src-dev/CodeGen/FluentBuilder/MixinGenerator.php
+++ /dev/null
@@ -1,253 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen\FluentBuilder;
-
-use Nette\PhpGenerator\PhpNamespace;
-use Nette\PhpGenerator\Printer;
-use ReflectionClass;
-use ReflectionParameter;
-use Respect\Dev\CodeGen\CodeGenerator;
-use Respect\Dev\CodeGen\Config;
-use Respect\Dev\CodeGen\InterfaceConfig;
-use Respect\Dev\CodeGen\NamespaceScanner;
-
-use function file_get_contents;
-use function in_array;
-use function is_file;
-use function is_readable;
-use function ksort;
-
-final class MixinGenerator implements CodeGenerator
-{
- /** @param array $interfaces */
- public function __construct(
- private readonly Config $config,
- private readonly MethodBuilder $methodBuilder = new MethodBuilder(),
- private readonly array $interfaces = [],
- ) {
- }
-
- /** @return array filename => content */
- public function generate(): array
- {
- $nodes = NamespaceScanner::scan($this->config->sourceDir, $this->config->sourceNamespace);
- $prefixes = $this->discoverPrefixes($nodes);
- $filters = $this->discoverFilters($nodes);
-
- $files = [];
-
- foreach ($this->interfaces as $interfaceConfig) {
- $prefixInterfaceNames = [];
-
- foreach ($prefixes as $prefix) {
- $interfaceName = $prefix['name'] . $interfaceConfig->suffix;
- $prefixInterfaceNames[] = $this->config->outputNamespace . '\\' . $interfaceName;
-
- $this->generateInterface(
- $interfaceName,
- $interfaceConfig,
- $nodes,
- $filters,
- $prefix,
- $files,
- );
- }
-
- $this->generateRootInterface(
- $interfaceConfig,
- $prefixInterfaceNames,
- $nodes,
- $filters,
- $files,
- );
- }
-
- return $files;
- }
-
- /**
- * @param array $nodes
- *
- * @return array
- */
- private function discoverPrefixes(array $nodes): array
- {
- $prefixes = [];
-
- foreach ($nodes as $reflection) {
- $attributes = $reflection->getAttributes(Mixin::class);
- if ($attributes === []) {
- continue;
- }
-
- $mixin = $attributes[0]->newInstance();
- if ($mixin->prefix === null) {
- continue;
- }
-
- $constructor = $reflection->getConstructor();
- $prefixParameter = null;
-
- if ($mixin->prefixParameter && $constructor !== null) {
- $parameters = $constructor->getParameters();
- if ($parameters !== []) {
- $prefixParameter = $parameters[0];
- }
- }
-
- $prefixes[$mixin->prefix] = [
- 'name' => $reflection->getShortName(),
- 'prefix' => $mixin->prefix,
- 'requireInclusion' => $mixin->requireInclusion,
- 'prefixParameter' => $prefixParameter,
- ];
- }
-
- ksort($prefixes);
-
- return $prefixes;
- }
-
- /**
- * @param array $nodes
- *
- * @return array
- */
- private function discoverFilters(array $nodes): array
- {
- $filters = [];
-
- foreach ($nodes as $name => $reflection) {
- $attributes = $reflection->getAttributes(Mixin::class);
- if ($attributes === []) {
- continue;
- }
-
- $filters[$name] = $attributes[0]->newInstance();
- }
-
- return $filters;
- }
-
- /**
- * @param array $nodes
- * @param array $filters
- * @param array{name: string, prefix: string, requireInclusion: bool, prefixParameter: ?ReflectionParameter} $prefix
- * @param array $files
- */
- private function generateInterface(
- string $interfaceName,
- InterfaceConfig $config,
- array $nodes,
- array $filters,
- array $prefix,
- array &$files,
- ): void {
- $namespace = new PhpNamespace($this->config->outputNamespace);
- $interface = $namespace->addInterface($interfaceName);
-
- foreach ($nodes as $name => $reflection) {
- $mixin = $filters[$name] ?? null;
-
- if ($prefix['requireInclusion']) {
- if ($mixin === null || !in_array($prefix['prefix'], $mixin->include, true)) {
- continue;
- }
- } elseif ($mixin !== null && in_array($prefix['prefix'], $mixin->exclude, true)) {
- continue;
- }
-
- $method = $this->methodBuilder->build(
- $namespace,
- $reflection,
- $config->returnType,
- $prefix['prefix'],
- $config->static,
- $prefix['prefixParameter'],
- );
-
- $interface->addMember($method);
- }
-
- $this->addFile($interfaceName, $namespace, $files);
- }
-
- /**
- * @param array $prefixInterfaceNames
- * @param array $nodes
- * @param array $filters
- * @param array $files
- */
- private function generateRootInterface(
- InterfaceConfig $config,
- array $prefixInterfaceNames,
- array $nodes,
- array $filters,
- array &$files,
- ): void {
- $interfaceName = $config->suffix;
- $namespace = new PhpNamespace($this->config->outputNamespace);
- $interface = $namespace->addInterface($interfaceName);
-
- foreach ($config->rootExtends as $extend) {
- $namespace->addUse($extend);
- $interface->addExtend($extend);
- }
-
- foreach ($prefixInterfaceNames as $prefixInterfaceName) {
- $namespace->addUse($prefixInterfaceName);
- $interface->addExtend($prefixInterfaceName);
- }
-
- if ($config->rootComment !== null) {
- $interface->addComment($config->rootComment);
- }
-
- foreach ($config->rootUses as $use) {
- $namespace->addUse($use);
- }
-
- foreach ($nodes as $reflection) {
- $method = $this->methodBuilder->build(
- $namespace,
- $reflection,
- $config->returnType,
- null,
- $config->static,
- );
-
- $interface->addMember($method);
- }
-
- $this->addFile($interfaceName, $namespace, $files);
- }
-
- /** @param array $files */
- private function addFile(string $interfaceName, PhpNamespace $namespace, array &$files): void
- {
- $filename = $this->config->outputDir . '/' . $interfaceName . '.php';
-
- $printer = new Printer();
- $printer->wrapLength = 300;
-
- $existingContent = '';
- if (is_file($filename) && is_readable($filename)) {
- $existingContent = file_get_contents($filename) ?: '';
- }
-
- $formattedContent = $this->config->outputFormatter->format(
- $printer->printNamespace($namespace),
- $existingContent,
- );
-
- $files[$filename] = $formattedContent;
- }
-}
diff --git a/src-dev/CodeGen/FluentBuilder/PrefixMapGenerator.php b/src-dev/CodeGen/FluentBuilder/PrefixMapGenerator.php
deleted file mode 100644
index a91a9c63f..000000000
--- a/src-dev/CodeGen/FluentBuilder/PrefixMapGenerator.php
+++ /dev/null
@@ -1,160 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen\FluentBuilder;
-
-use Nette\PhpGenerator\PhpNamespace;
-use Nette\PhpGenerator\Printer;
-use ReflectionClass;
-use Respect\Dev\CodeGen\CodeGenerator;
-use Respect\Dev\CodeGen\Config;
-use Respect\Dev\CodeGen\NamespaceScanner;
-
-use function array_keys;
-use function ctype_upper;
-use function file_get_contents;
-use function is_file;
-use function is_readable;
-use function ksort;
-use function lcfirst;
-use function str_starts_with;
-use function strlen;
-use function uksort;
-
-final class PrefixMapGenerator implements CodeGenerator
-{
- public function __construct(
- private readonly Config $config,
- private readonly string $outputClassName,
- ) {
- }
-
- /** @return array filename => content */
- public function generate(): array
- {
- $nodes = NamespaceScanner::scan($this->config->sourceDir, $this->config->sourceNamespace);
- $prefixes = $this->discoverPrefixes($nodes);
- $composable = $this->buildComposable($nodes, $prefixes);
- $composableWithArgument = $this->buildComposableWithArgument($prefixes);
-
- $namespace = new PhpNamespace($this->config->outputNamespace);
- $class = $namespace->addClass($this->outputClassName);
- $class->setFinal();
-
- $class->addConstant('COMPOSABLE', $composable)->setPublic()->setType('array');
- $class->addConstant('COMPOSABLE_WITH_ARGUMENT', $composableWithArgument)->setPublic()->setType('array');
-
- $printer = new Printer();
- $printer->wrapLength = 300;
-
- $outputFile = $this->config->outputDir . '/' . $this->outputClassName . '.php';
-
- $existingContent = '';
- if (is_file($outputFile) && is_readable($outputFile)) {
- $existingContent = file_get_contents($outputFile) ?: '';
- }
-
- $formattedContent = $this->config->outputFormatter->format(
- $printer->printNamespace($namespace),
- $existingContent,
- );
-
- return [$outputFile => $formattedContent];
- }
-
- /**
- * @param array $nodes
- *
- * @return array
- */
- private function discoverPrefixes(array $nodes): array
- {
- $prefixes = [];
-
- foreach ($nodes as $reflection) {
- $attributes = $reflection->getAttributes(Mixin::class);
- if ($attributes === []) {
- continue;
- }
-
- $mixin = $attributes[0]->newInstance();
- if ($mixin->prefix === null) {
- continue;
- }
-
- $prefixes[$mixin->prefix] = [
- 'prefix' => $mixin->prefix,
- 'prefixParameter' => $mixin->prefixParameter,
- ];
- }
-
- ksort($prefixes);
-
- return $prefixes;
- }
-
- /**
- * @param array $nodes
- * @param array $prefixes
- *
- * @return array
- */
- private function buildComposable(array $nodes, array $prefixes): array
- {
- $composable = [];
-
- foreach (array_keys($prefixes) as $prefix) {
- $composable[$prefix] = true;
-
- foreach (array_keys($nodes) as $name) {
- $lcName = lcfirst($name);
- if ($lcName === $prefix) {
- continue;
- }
-
- if (!str_starts_with($lcName, $prefix)) {
- continue;
- }
-
- if (!ctype_upper($lcName[strlen($prefix)])) {
- continue;
- }
-
- $composable[$lcName] = true;
- }
- }
-
- uksort($composable, static fn(string $a, string $b): int => strlen($b) <=> strlen($a) ?: $a <=> $b);
-
- return $composable;
- }
-
- /**
- * @param array $prefixes
- *
- * @return array
- */
- private function buildComposableWithArgument(array $prefixes): array
- {
- $composableWithArgument = [];
-
- foreach ($prefixes as $prefix => $info) {
- if (!$info['prefixParameter']) {
- continue;
- }
-
- $composableWithArgument[$prefix] = true;
- }
-
- ksort($composableWithArgument);
-
- return $composableWithArgument;
- }
-}
diff --git a/src-dev/CodeGen/InterfaceConfig.php b/src-dev/CodeGen/InterfaceConfig.php
deleted file mode 100644
index 92b16482d..000000000
--- a/src-dev/CodeGen/InterfaceConfig.php
+++ /dev/null
@@ -1,28 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen;
-
-final readonly class InterfaceConfig
-{
- /**
- * @param array $rootExtends
- * @param array $rootUses
- */
- public function __construct(
- public string $suffix,
- public string $returnType,
- public bool $static = false,
- public array $rootExtends = [],
- public string|null $rootComment = null,
- public array $rootUses = [],
- ) {
- }
-}
diff --git a/src-dev/CodeGen/NamespaceScanner.php b/src-dev/CodeGen/NamespaceScanner.php
deleted file mode 100644
index 5f9066aa4..000000000
--- a/src-dev/CodeGen/NamespaceScanner.php
+++ /dev/null
@@ -1,44 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen;
-
-use DirectoryIterator;
-use ReflectionClass;
-
-use function ksort;
-
-final class NamespaceScanner
-{
- /** @return array */
- public static function scan(string $directory, string $namespace): array
- {
- $nodes = [];
-
- foreach (new DirectoryIterator($directory) as $file) {
- if (!$file->isFile()) {
- continue;
- }
-
- $className = $namespace . '\\' . $file->getBasename('.php');
- $reflection = new ReflectionClass($className);
-
- if ($reflection->isAbstract()) {
- continue;
- }
-
- $nodes[$reflection->getShortName()] = $reflection;
- }
-
- ksort($nodes);
-
- return $nodes;
- }
-}
diff --git a/src-dev/CodeGen/OutputFormatter.php b/src-dev/CodeGen/OutputFormatter.php
deleted file mode 100644
index 9b9066ce8..000000000
--- a/src-dev/CodeGen/OutputFormatter.php
+++ /dev/null
@@ -1,47 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Dev\CodeGen;
-
-use function array_keys;
-use function array_values;
-use function implode;
-use function preg_match;
-use function preg_replace;
-use function trim;
-
-use const PHP_EOL;
-
-final class OutputFormatter
-{
- public function format(string $content, string $existingContent): string
- {
- preg_match('/^<\?php\s*\/\*[\s\S]*?\*\//', $existingContent, $matches);
- $existingHeader = $matches[0] ?? '';
-
- $replacements = [
- '/\n\n\t(public|private|\/\*\*)/m' => PHP_EOL . ' $1',
- '/\t/m' => ' ',
- '/\?([a-zA-Z]+) \$/' => '$1|null $',
- '/\/\*\*\n +\* (.+)\n +\*\//m' => '/** $1 */',
- ];
-
- return implode(PHP_EOL, [
- trim($existingHeader) . PHP_EOL,
- 'declare(strict_types=1);',
- '',
- preg_replace(
- array_keys($replacements),
- array_values($replacements),
- $content,
- ),
- ]);
- }
-}
diff --git a/src-dev/Commands/LintMixinCommand.php b/src-dev/Commands/LintMixinCommand.php
index d9c6828de..c9fb1db2d 100644
--- a/src-dev/Commands/LintMixinCommand.php
+++ b/src-dev/Commands/LintMixinCommand.php
@@ -11,13 +11,14 @@
namespace Respect\Dev\Commands;
-use Respect\Dev\CodeGen\Config;
-use Respect\Dev\CodeGen\FluentBuilder\MethodBuilder;
-use Respect\Dev\CodeGen\FluentBuilder\MixinGenerator;
-use Respect\Dev\CodeGen\FluentBuilder\PrefixMapGenerator;
-use Respect\Dev\CodeGen\InterfaceConfig;
use Respect\Dev\Differ\ConsoleDiffer;
use Respect\Dev\Differ\Item;
+use Respect\FluentGen\Config;
+use Respect\FluentGen\Fluent\InterfaceConfig;
+use Respect\FluentGen\Fluent\MethodBuilder;
+use Respect\FluentGen\Fluent\MixinGenerator;
+use Respect\FluentGen\Fluent\PrefixConstantsGenerator;
+use Respect\FluentGen\NamespaceScanner;
use Respect\Validation\Mixins\Chain;
use Respect\Validation\Validator;
use Respect\Validation\ValidatorBuilder;
@@ -67,8 +68,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
outputNamespace: 'Respect\\Validation\\Mixins',
);
+ $scanner = new NamespaceScanner();
+
$generator = new MixinGenerator(
config: $config,
+ scanner: $scanner,
methodBuilder: new MethodBuilder(
excludedTypePrefixes: ['Sokil', 'Egulias'],
excludedTypeNames: ['finfo'],
@@ -89,9 +93,10 @@ interfaces: [
],
);
- $prefixMapGenerator = new PrefixMapGenerator(
+ $prefixMapGenerator = new PrefixConstantsGenerator(
config: $config,
- outputClassName: 'PrefixMap',
+ scanner: $scanner,
+ outputClassName: 'PrefixConstants',
);
$files = $generator->generate() + $prefixMapGenerator->generate();
diff --git a/src/ContainerRegistry.php b/src/ContainerRegistry.php
index 0dcd8c536..6f6f0938b 100644
--- a/src/ContainerRegistry.php
+++ b/src/ContainerRegistry.php
@@ -14,6 +14,9 @@
use DI\Container;
use libphonenumber\PhoneNumberUtil;
use Psr\Container\ContainerInterface;
+use ReflectionClass;
+use Respect\Fluent\Attributes\FluentNamespace;
+use Respect\Fluent\FluentFactory;
use Respect\StringFormatter\BypassTranslator;
use Respect\StringFormatter\Modifier;
use Respect\StringFormatter\Modifiers\FormatterModifier;
@@ -40,8 +43,6 @@
use Respect\Validation\Message\Parameters\ResultHandler;
use Respect\Validation\Message\Renderer;
use Respect\Validation\Message\TemplateRegistry;
-use Respect\Validation\Transformers\Prefix;
-use Respect\Validation\Transformers\Transformer;
use Symfony\Contracts\Translation\TranslatorInterface;
use function DI\autowire;
@@ -57,7 +58,6 @@ public static function createContainer(array $definitions = []): Container
{
return new Container($definitions + [
PhoneNumberUtil::class => factory(static fn() => PhoneNumberUtil::getInstance()),
- Transformer::class => create(Prefix::class),
TemplateRegistry::class => create(TemplateRegistry::class),
TemplateResolver::class => autowire(TemplateResolver::class),
TranslatorInterface::class => autowire(BypassTranslator::class),
@@ -67,11 +67,19 @@ public static function createContainer(array $definitions = []): Container
'respect.validation.formatter.full_message' => autowire(NestedListStringFormatter::class),
'respect.validation.formatter.messages' => autowire(NestedArrayFormatter::class),
'respect.validation.ignored_backtrace_paths' => [__DIR__ . '/ValidatorBuilder.php'],
- 'respect.validation.rule_factory.namespaces' => ['Respect\\Validation\\Validators'],
- ValidatorFactory::class => factory(static fn(Container $container) => new NamespacedValidatorFactory(
- $container->get(Transformer::class),
- $container->get('respect.validation.rule_factory.namespaces'),
- )),
+ 'respect.validation.rule_factory.namespaces' => [],
+ FluentFactory::class => factory(static function (Container $container) {
+ $factory = (new ReflectionClass(ValidatorBuilder::class))
+ ->getAttributes(FluentNamespace::class)[0]
+ ->newInstance()
+ ->factory;
+
+ foreach ($container->get('respect.validation.rule_factory.namespaces') as $namespace) {
+ $factory = $factory->withNamespace($namespace);
+ }
+
+ return $factory;
+ }),
Quoter::class => create(CodeQuoter::class)->constructor(120),
Handler::class => factory(static function (Container $container) {
$handler = CompositeHandler::create();
@@ -101,7 +109,7 @@ public static function createContainer(array $definitions = []): Container
$container->get(TranslatorInterface::class),
)),
ValidatorBuilder::class => factory(static fn(Container $container) => new ValidatorBuilder(
- $container->get(ValidatorFactory::class),
+ $container->get(FluentFactory::class),
$container->get(Renderer::class),
$container->get('respect.validation.formatter.message'),
$container->get('respect.validation.formatter.full_message'),
diff --git a/src/Mixins/Builder.php b/src/Mixins/Builder.php
index d4042eda0..431dabd04 100644
--- a/src/Mixins/Builder.php
+++ b/src/Mixins/Builder.php
@@ -182,11 +182,11 @@ public static function iterableVal(): Chain;
public static function json(): Chain;
- public static function key(string|int $key, Validator $validator): Chain;
+ public static function key(int|string $key, Validator $validator): Chain;
- public static function keyExists(string|int $key): Chain;
+ public static function keyExists(int|string $key): Chain;
- public static function keyOptional(string|int $key, Validator $validator): Chain;
+ public static function keyOptional(int|string $key, Validator $validator): Chain;
public static function keySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/Mixins/Chain.php b/src/Mixins/Chain.php
index acc75aef5..850938790 100644
--- a/src/Mixins/Chain.php
+++ b/src/Mixins/Chain.php
@@ -184,11 +184,11 @@ public function iterableVal(): Chain;
public function json(): Chain;
- public function key(string|int $key, Validator $validator): Chain;
+ public function key(int|string $key, Validator $validator): Chain;
- public function keyExists(string|int $key): Chain;
+ public function keyExists(int|string $key): Chain;
- public function keyOptional(string|int $key, Validator $validator): Chain;
+ public function keyOptional(int|string $key, Validator $validator): Chain;
public function keySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/Mixins/NotBuilder.php b/src/Mixins/NotBuilder.php
index 41c6c44ab..225a5e009 100644
--- a/src/Mixins/NotBuilder.php
+++ b/src/Mixins/NotBuilder.php
@@ -179,11 +179,11 @@ public static function notIterableVal(): Chain;
public static function notJson(): Chain;
- public static function notKey(string|int $key, Validator $validator): Chain;
+ public static function notKey(int|string $key, Validator $validator): Chain;
- public static function notKeyExists(string|int $key): Chain;
+ public static function notKeyExists(int|string $key): Chain;
- public static function notKeyOptional(string|int $key, Validator $validator): Chain;
+ public static function notKeyOptional(int|string $key, Validator $validator): Chain;
public static function notKeySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/Mixins/NotChain.php b/src/Mixins/NotChain.php
index 113737fac..8ea5fd991 100644
--- a/src/Mixins/NotChain.php
+++ b/src/Mixins/NotChain.php
@@ -179,11 +179,11 @@ public function notIterableVal(): Chain;
public function notJson(): Chain;
- public function notKey(string|int $key, Validator $validator): Chain;
+ public function notKey(int|string $key, Validator $validator): Chain;
- public function notKeyExists(string|int $key): Chain;
+ public function notKeyExists(int|string $key): Chain;
- public function notKeyOptional(string|int $key, Validator $validator): Chain;
+ public function notKeyOptional(int|string $key, Validator $validator): Chain;
public function notKeySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/Mixins/NullOrBuilder.php b/src/Mixins/NullOrBuilder.php
index 12f54b0b2..e32cd0939 100644
--- a/src/Mixins/NullOrBuilder.php
+++ b/src/Mixins/NullOrBuilder.php
@@ -179,11 +179,11 @@ public static function nullOrIterableVal(): Chain;
public static function nullOrJson(): Chain;
- public static function nullOrKey(string|int $key, Validator $validator): Chain;
+ public static function nullOrKey(int|string $key, Validator $validator): Chain;
- public static function nullOrKeyExists(string|int $key): Chain;
+ public static function nullOrKeyExists(int|string $key): Chain;
- public static function nullOrKeyOptional(string|int $key, Validator $validator): Chain;
+ public static function nullOrKeyOptional(int|string $key, Validator $validator): Chain;
public static function nullOrKeySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/Mixins/NullOrChain.php b/src/Mixins/NullOrChain.php
index 329659911..66e87b606 100644
--- a/src/Mixins/NullOrChain.php
+++ b/src/Mixins/NullOrChain.php
@@ -179,11 +179,11 @@ public function nullOrIterableVal(): Chain;
public function nullOrJson(): Chain;
- public function nullOrKey(string|int $key, Validator $validator): Chain;
+ public function nullOrKey(int|string $key, Validator $validator): Chain;
- public function nullOrKeyExists(string|int $key): Chain;
+ public function nullOrKeyExists(int|string $key): Chain;
- public function nullOrKeyOptional(string|int $key, Validator $validator): Chain;
+ public function nullOrKeyOptional(int|string $key, Validator $validator): Chain;
public function nullOrKeySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/Mixins/PrefixConstants.php b/src/Mixins/PrefixConstants.php
new file mode 100644
index 000000000..9afcf99e2
--- /dev/null
+++ b/src/Mixins/PrefixConstants.php
@@ -0,0 +1,56 @@
+
+ */
+
+declare(strict_types=1);
+
+namespace Respect\Validation\Mixins;
+
+final class PrefixConstants
+{
+ public const array COMPOSABLE = [
+ 'propertyOptional' => true,
+ 'propertyExists' => true,
+ 'keyOptional' => true,
+ 'keyExists' => true,
+ 'property' => true,
+ 'undefOr' => true,
+ 'keySet' => true,
+ 'length' => true,
+ 'nullOr' => true,
+ 'allOf' => true,
+ 'all' => true,
+ 'key' => true,
+ 'max' => true,
+ 'min' => true,
+ 'not' => true,
+ ];
+ public const array COMPOSABLE_WITH_ARGUMENT = ['key' => true, 'property' => true];
+ public const array FORBIDDEN = [
+ 'All' => ['all' => true],
+ 'Attributes' => ['all' => true, 'key' => true, 'not' => true, 'property' => true, 'undefOr' => true],
+ 'Blank' => ['nullOr' => true, 'undefOr' => true],
+ 'Exists' => ['all' => true, 'key' => true, 'property' => true],
+ 'Formatted' => ['all' => true, 'key' => true, 'property' => true],
+ 'Key' => ['all' => true, 'key' => true, 'property' => true],
+ 'KeyExists' => ['all' => true, 'key' => true, 'property' => true],
+ 'KeyOptional' => ['all' => true, 'key' => true, 'property' => true],
+ 'KeySet' => ['all' => true, 'key' => true, 'property' => true],
+ 'Length' => ['all' => true, 'key' => true, 'length' => true, 'max' => true, 'min' => true, 'not' => true, 'nullOr' => true, 'property' => true, 'undefOr' => true],
+ 'Max' => ['all' => true, 'key' => true, 'length' => true, 'max' => true, 'min' => true, 'not' => true, 'nullOr' => true, 'property' => true, 'undefOr' => true],
+ 'Min' => ['all' => true, 'key' => true, 'length' => true, 'max' => true, 'min' => true, 'not' => true, 'nullOr' => true, 'property' => true, 'undefOr' => true],
+ 'Named' => ['all' => true, 'key' => true, 'not' => true, 'nullOr' => true, 'property' => true, 'undefOr' => true],
+ 'Not' => ['not' => true],
+ 'NullOr' => ['all' => true, 'key' => true, 'not' => true, 'nullOr' => true, 'property' => true, 'undefOr' => true],
+ 'Property' => ['all' => true, 'key' => true, 'property' => true],
+ 'PropertyExists' => ['all' => true, 'key' => true, 'property' => true],
+ 'PropertyOptional' => ['all' => true, 'key' => true, 'property' => true],
+ 'Templated' => ['all' => true, 'key' => true, 'not' => true, 'nullOr' => true, 'property' => true, 'undefOr' => true],
+ 'Undef' => ['nullOr' => true, 'undefOr' => true],
+ 'UndefOr' => ['all' => true, 'key' => true, 'not' => true, 'nullOr' => true, 'property' => true, 'undefOr' => true],
+ ];
+}
diff --git a/src/Mixins/PrefixMap.php b/src/Mixins/PrefixMap.php
deleted file mode 100644
index d27356be4..000000000
--- a/src/Mixins/PrefixMap.php
+++ /dev/null
@@ -1,33 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation\Mixins;
-
-final class PrefixMap
-{
- public const array COMPOSABLE = [
- 'propertyOptional' => true,
- 'propertyExists' => true,
- 'keyOptional' => true,
- 'keyExists' => true,
- 'property' => true,
- 'undefOr' => true,
- 'keySet' => true,
- 'length' => true,
- 'nullOr' => true,
- 'allOf' => true,
- 'all' => true,
- 'key' => true,
- 'max' => true,
- 'min' => true,
- 'not' => true,
- ];
- public const array COMPOSABLE_WITH_ARGUMENT = ['key' => true, 'property' => true];
-}
diff --git a/src/Mixins/UndefOrBuilder.php b/src/Mixins/UndefOrBuilder.php
index c8c6273ec..c03840e6b 100644
--- a/src/Mixins/UndefOrBuilder.php
+++ b/src/Mixins/UndefOrBuilder.php
@@ -177,11 +177,11 @@ public static function undefOrIterableVal(): Chain;
public static function undefOrJson(): Chain;
- public static function undefOrKey(string|int $key, Validator $validator): Chain;
+ public static function undefOrKey(int|string $key, Validator $validator): Chain;
- public static function undefOrKeyExists(string|int $key): Chain;
+ public static function undefOrKeyExists(int|string $key): Chain;
- public static function undefOrKeyOptional(string|int $key, Validator $validator): Chain;
+ public static function undefOrKeyOptional(int|string $key, Validator $validator): Chain;
public static function undefOrKeySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/Mixins/UndefOrChain.php b/src/Mixins/UndefOrChain.php
index aee52fcae..7d04d2463 100644
--- a/src/Mixins/UndefOrChain.php
+++ b/src/Mixins/UndefOrChain.php
@@ -177,11 +177,11 @@ public function undefOrIterableVal(): Chain;
public function undefOrJson(): Chain;
- public function undefOrKey(string|int $key, Validator $validator): Chain;
+ public function undefOrKey(int|string $key, Validator $validator): Chain;
- public function undefOrKeyExists(string|int $key): Chain;
+ public function undefOrKeyExists(int|string $key): Chain;
- public function undefOrKeyOptional(string|int $key, Validator $validator): Chain;
+ public function undefOrKeyOptional(int|string $key, Validator $validator): Chain;
public function undefOrKeySet(Validator $validator, Validator ...$validators): Chain;
diff --git a/src/NamespacedValidatorFactory.php b/src/NamespacedValidatorFactory.php
deleted file mode 100644
index 369dd8994..000000000
--- a/src/NamespacedValidatorFactory.php
+++ /dev/null
@@ -1,98 +0,0 @@
-
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation;
-
-use ReflectionClass;
-use ReflectionException;
-use Respect\Validation\Exceptions\ComponentException;
-use Respect\Validation\Exceptions\InvalidClassException;
-use Respect\Validation\Transformers\Transformer;
-use Respect\Validation\Transformers\ValidatorSpec;
-
-use function array_merge;
-use function Respect\Stringifier\stringify;
-use function sprintf;
-use function trim;
-use function ucfirst;
-
-final readonly class NamespacedValidatorFactory implements ValidatorFactory
-{
- /** @param array $rulesNamespaces */
- public function __construct(
- private Transformer $transformer,
- private array $rulesNamespaces,
- ) {
- }
-
- public function withNamespace(string $rulesNamespace): self
- {
- return clone ($this, ['rulesNamespaces' => [trim($rulesNamespace, '\\'), ...$this->rulesNamespaces]]);
- }
-
- /** @param array $arguments */
- public function create(string $ruleName, array $arguments = []): Validator
- {
- return $this->createValidatorSpec($this->transformer->transform(new ValidatorSpec($ruleName, $arguments)));
- }
-
- private function createValidatorSpec(ValidatorSpec $validatorSpec): Validator
- {
- $validator = $this->createRule($validatorSpec->name, $validatorSpec->arguments);
- if ($validatorSpec->wrapper !== null) {
- return $this->createRule(
- $validatorSpec->wrapper->name,
- array_merge($validatorSpec->wrapper->arguments, [$validator]),
- );
- }
-
- return $validator;
- }
-
- /** @param array $arguments */
- private function createRule(string $ruleName, array $arguments = []): Validator
- {
- $reflection = null;
-
- foreach ($this->rulesNamespaces as $namespace) {
- try {
- /** @var class-string $name */
- $name = $namespace . '\\' . ucfirst($ruleName);
- $reflection = new ReflectionClass($name);
- if (!$reflection->isSubclassOf(Validator::class)) {
- throw new InvalidClassException(
- sprintf('"%s" must be an instance of "%s"', $name, Validator::class),
- );
- }
-
- if (!$reflection->isInstantiable()) {
- throw new InvalidClassException(sprintf('"%s" must be instantiable', $name));
- }
-
- break;
- } catch (ReflectionException) {
- continue;
- }
- }
-
- if (!$reflection) {
- throw new ComponentException(sprintf('"%s" is not a valid rule name', $ruleName));
- }
-
- try {
- return $reflection->newInstanceArgs($arguments);
- } catch (ReflectionException) {
- throw new InvalidClassException(
- sprintf('"%s" could not be instantiated with arguments %s', $ruleName, stringify($arguments)),
- );
- }
- }
-}
diff --git a/src/Transformers/Prefix.php b/src/Transformers/Prefix.php
deleted file mode 100644
index d6205bb84..000000000
--- a/src/Transformers/Prefix.php
+++ /dev/null
@@ -1,71 +0,0 @@
-
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation\Transformers;
-
-use Respect\Validation\Mixins\PrefixMap;
-
-use function array_keys;
-use function array_slice;
-use function implode;
-use function preg_match;
-use function sprintf;
-
-final class Prefix implements Transformer
-{
- private static string|null $regex = null;
-
- public function transform(ValidatorSpec $validatorSpec): ValidatorSpec
- {
- $matches = $this->match($validatorSpec);
- if ($matches === []) {
- return $validatorSpec;
- }
-
- if (!isset(PrefixMap::COMPOSABLE_WITH_ARGUMENT[$matches['prefix']])) {
- return new ValidatorSpec(
- $matches['suffix'],
- $validatorSpec->arguments,
- new ValidatorSpec($matches['prefix']),
- );
- }
-
- return new ValidatorSpec(
- $matches['suffix'],
- array_slice($validatorSpec->arguments, 1),
- new ValidatorSpec($matches['prefix'], [$validatorSpec->arguments[0]]),
- );
- }
-
- /** @return array{}|array{prefix: string, suffix: string} */
- private function match(ValidatorSpec $validatorSpec): array
- {
- if ($validatorSpec->wrapper !== null || isset(PrefixMap::COMPOSABLE[$validatorSpec->name])) {
- return [];
- }
-
- preg_match(self::getRegex(), $validatorSpec->name, $matches);
-
- if ($matches === []) {
- return [];
- }
-
- return ['prefix' => $matches['prefix'], 'suffix' => $matches['suffix']];
- }
-
- private static function getRegex(): string
- {
- return self::$regex ?? self::$regex = sprintf(
- '/^(?%s)(?.+)$/',
- implode('|', array_keys(PrefixMap::COMPOSABLE)),
- );
- }
-}
diff --git a/src/Transformers/Transformer.php b/src/Transformers/Transformer.php
deleted file mode 100644
index f812487d7..000000000
--- a/src/Transformers/Transformer.php
+++ /dev/null
@@ -1,17 +0,0 @@
-
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation\Transformers;
-
-interface Transformer
-{
- public function transform(ValidatorSpec $validatorSpec): ValidatorSpec;
-}
diff --git a/src/Transformers/ValidatorSpec.php b/src/Transformers/ValidatorSpec.php
deleted file mode 100644
index 33220a71c..000000000
--- a/src/Transformers/ValidatorSpec.php
+++ /dev/null
@@ -1,23 +0,0 @@
-
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation\Transformers;
-
-final readonly class ValidatorSpec
-{
- /** @param array $arguments */
- public function __construct(
- public string $name,
- public array $arguments = [],
- public ValidatorSpec|null $wrapper = null,
- ) {
- }
-}
diff --git a/src/ValidatorBuilder.php b/src/ValidatorBuilder.php
index 5c279989f..9a82dfa72 100644
--- a/src/ValidatorBuilder.php
+++ b/src/ValidatorBuilder.php
@@ -12,12 +12,22 @@
namespace Respect\Validation;
+use Respect\Fluent\Attributes\AssuranceAssertion;
+use Respect\Fluent\Attributes\AssuranceParameter;
+use Respect\Fluent\Attributes\FluentNamespace;
+use Respect\Fluent\Builders\Append;
+use Respect\Fluent\Factories\ComposingLookup;
+use Respect\Fluent\Factories\NamespaceLookup;
+use Respect\Fluent\FluentFactory;
+use Respect\Fluent\Resolvers\ComposableMap;
+use Respect\Fluent\Resolvers\Ucfirst;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Message\ArrayFormatter;
use Respect\Validation\Message\Renderer;
use Respect\Validation\Message\StringFormatter;
use Respect\Validation\Mixins\Builder;
+use Respect\Validation\Mixins\PrefixConstants;
use Respect\Validation\Validators\AllOf;
use Respect\Validation\Validators\Core\Nameable;
use Respect\Validation\Validators\Core\ShortCircuitable;
@@ -31,14 +41,15 @@
use function is_string;
/** @mixin Builder */
-final readonly class ValidatorBuilder implements Nameable, ShortCircuitable
+#[FluentNamespace(new ComposingLookup(
+ new NamespaceLookup(new Ucfirst(), Validator::class, 'Respect\\Validation\\Validators'),
+ new ComposableMap(PrefixConstants::COMPOSABLE, PrefixConstants::COMPOSABLE_WITH_ARGUMENT),
+))]
+final readonly class ValidatorBuilder extends Append implements Nameable, ShortCircuitable
{
- /** @var array */
- private array $validators;
-
/** @param array $ignoredBacktracePaths */
public function __construct(
- private ValidatorFactory $validatorFactory,
+ FluentFactory $factory,
private Renderer $renderer,
private StringFormatter $mainMessageFormatter,
private StringFormatter $fullMessageFormatter,
@@ -47,7 +58,7 @@ public function __construct(
private array $ignoredBacktracePaths,
Validator ...$validators,
) {
- $this->validators = $validators;
+ parent::__construct($factory, ...$validators);
}
public static function init(Validator ...$validators): self
@@ -56,15 +67,17 @@ public static function init(Validator ...$validators): self
return ContainerRegistry::getContainer()->get(self::class);
}
- return ContainerRegistry::getContainer()->get(self::class)->with(...$validators);
+ return ContainerRegistry::getContainer()->get(self::class)->attach(...$validators);
}
public function evaluate(mixed $input): Result
{
- $validator = match (count($this->validators)) {
+ $validators = $this->getValidators();
+
+ $validator = match (count($validators)) {
0 => throw new ComponentException('No validators have been added.'),
- 1 => current($this->validators),
- default => new AllOf(...$this->validators),
+ 1 => current($validators),
+ default => new AllOf(...$validators),
};
return $validator->evaluate($input);
@@ -72,7 +85,9 @@ public function evaluate(mixed $input): Result
public function evaluateShortCircuit(mixed $input): Result
{
- return (new ShortCircuit(...$this->validators))->evaluate($input);
+ $validators = $this->getValidators();
+
+ return (new ShortCircuit(...$validators))->evaluate($input);
}
/** @param array|string|null $template */
@@ -81,38 +96,46 @@ public function validate(mixed $input, array|string|null $template = null): Resu
return $this->toResultQuery($this->evaluate($input), $template);
}
- public function isValid(mixed $input): bool
- {
+ #[AssuranceAssertion]
+ public function isValid(
+ #[AssuranceParameter]
+ mixed $input,
+ ): bool {
return $this->evaluateShortCircuit($input)->hasPassed;
}
/** @param array|callable(ValidationException): Throwable|string|Throwable|null $template */
- public function check(mixed $input, array|string|Throwable|callable|null $template = null): void
- {
+ #[AssuranceAssertion]
+ public function check(
+ #[AssuranceParameter]
+ mixed $input,
+ array|string|Throwable|callable|null $template = null,
+ ): void {
$this->throwOnFailure($this->evaluateShortCircuit($input), $template);
}
/** @param array|callable(ValidationException): Throwable|string|Throwable|null $template */
- public function assert(mixed $input, array|string|Throwable|callable|null $template = null): void
- {
+ #[AssuranceAssertion]
+ public function assert(
+ #[AssuranceParameter]
+ mixed $input,
+ array|string|Throwable|callable|null $template = null,
+ ): void {
$this->throwOnFailure($this->evaluate($input), $template);
}
- public function with(Validator $validator, Validator ...$validators): self
- {
- return clone ($this, ['validators' => [...$this->validators, $validator, ...$validators]]);
- }
-
/** @return array */
public function getValidators(): array
{
- return $this->validators;
+ return $this->getNodes();
}
public function getName(): Name|null
{
- if (count($this->validators) === 1 && current($this->validators) instanceof Nameable) {
- return current($this->validators)->getName();
+ $validators = $this->getNodes();
+
+ if (count($validators) === 1 && current($validators) instanceof Nameable) {
+ return current($validators)->getName();
}
return null;
@@ -153,14 +176,8 @@ private function throwOnFailure(Result $result, array|callable|Throwable|string|
}
/** @param array $arguments */
- public static function __callStatic(string $ruleName, array $arguments): self
+ public static function __callStatic(string $ruleName, array $arguments): static
{
return self::init()->__call($ruleName, $arguments);
}
-
- /** @param array $arguments */
- public function __call(string $ruleName, array $arguments): self
- {
- return $this->with($this->validatorFactory->create($ruleName, $arguments));
- }
}
diff --git a/src/ValidatorFactory.php b/src/ValidatorFactory.php
deleted file mode 100644
index 24c25473e..000000000
--- a/src/ValidatorFactory.php
+++ /dev/null
@@ -1,18 +0,0 @@
-
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation;
-
-interface ValidatorFactory
-{
- /** @param array $arguments */
- public function create(string $ruleName, array $arguments = []): Validator;
-}
diff --git a/src/Validators/All.php b/src/Validators/All.php
index 6a7d728b5..4593aa1e1 100644
--- a/src/Validators/All.php
+++ b/src/Validators/All.php
@@ -15,7 +15,9 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceFrom;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
use Respect\Validation\Path;
@@ -23,9 +25,10 @@
use Respect\Validation\Validators\Core\FilteredArray;
use Respect\Validation\Validators\Core\ShortCircuitable;
-#[Mixin(prefix: 'all', exclude: ['all'])]
+#[Composable(prefix: self::class, without: [self::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template('Every item in', 'Every item in')]
+#[Assurance(from: AssuranceFrom::Elements)]
final class All extends FilteredArray implements ShortCircuitable
{
use CanEvaluateShortCircuit;
diff --git a/src/Validators/AllOf.php b/src/Validators/AllOf.php
index 75701b6cc..88470c9bd 100644
--- a/src/Validators/AllOf.php
+++ b/src/Validators/AllOf.php
@@ -15,6 +15,8 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceCompose;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -38,6 +40,7 @@
'{{subject}} must pass all the rules',
self::TEMPLATE_ALL,
)]
+#[Assurance(compose: AssuranceCompose::Intersect)]
final class AllOf extends LogicalComposite implements ShortCircuitable
{
use CanEvaluateShortCircuit;
diff --git a/src/Validators/Alnum.php b/src/Validators/Alnum.php
index d734082b1..7c87a508b 100644
--- a/src/Validators/Alnum.php
+++ b/src/Validators/Alnum.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -31,6 +32,7 @@
'{{subject}} must not consist only of letters (a-z), digits (0-9), or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Alnum extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/Alpha.php b/src/Validators/Alpha.php
index 986554c6a..90bbc846d 100644
--- a/src/Validators/Alpha.php
+++ b/src/Validators/Alpha.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -31,6 +32,7 @@
'{{subject}} must not consist only of letters (a-z) or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Alpha extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/AnyOf.php b/src/Validators/AnyOf.php
index de9b8aadd..9d061b3d1 100644
--- a/src/Validators/AnyOf.php
+++ b/src/Validators/AnyOf.php
@@ -15,6 +15,8 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceCompose;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -30,6 +32,7 @@
'{{subject}} must pass at least one of the rules',
'{{subject}} must pass at least one of the rules',
)]
+#[Assurance(compose: AssuranceCompose::Union)]
final class AnyOf extends LogicalComposite implements ShortCircuitable
{
use CanEvaluateShortCircuit;
diff --git a/src/Validators/ArrayType.php b/src/Validators/ArrayType.php
index 9ada2804e..a4a9a1df0 100644
--- a/src/Validators/ArrayType.php
+++ b/src/Validators/ArrayType.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must be an array',
'{{subject}} must not be an array',
)]
+#[Assurance(type: 'array')]
final class ArrayType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/ArrayVal.php b/src/Validators/ArrayVal.php
index cede9834c..f12267328 100644
--- a/src/Validators/ArrayVal.php
+++ b/src/Validators/ArrayVal.php
@@ -18,6 +18,7 @@
use ArrayAccess;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SimpleXMLElement;
@@ -29,6 +30,7 @@
'{{subject}} must be an array',
'{{subject}} must not be an array',
)]
+#[Assurance(type: ['array', ArrayAccess::class, SimpleXMLElement::class])]
final class ArrayVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Attributes.php b/src/Validators/Attributes.php
index 78ff90583..13210b052 100644
--- a/src/Validators/Attributes.php
+++ b/src/Validators/Attributes.php
@@ -17,13 +17,13 @@
use ReflectionClass;
use ReflectionObject;
use ReflectionProperty;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Id;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use Respect\Validation\Validators\Core\Reducer;
-#[Mixin(exclude: ['all', 'key', 'property', 'not', 'undefOr'])]
+#[Composable(without: [All::class, Key::class, Property::class, Not::class, UndefOr::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Attributes implements Validator
{
diff --git a/src/Validators/Base64.php b/src/Validators/Base64.php
index 560857a02..21cf3543e 100644
--- a/src/Validators/Base64.php
+++ b/src/Validators/Base64.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -29,6 +30,7 @@
'{{subject}} must be a base64-encoded string',
'{{subject}} must not be a base64-encoded string',
)]
+#[Assurance(type: 'string')]
final class Base64 extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Between.php b/src/Validators/Between.php
index 7ef768e25..862dd6a86 100644
--- a/src/Validators/Between.php
+++ b/src/Validators/Between.php
@@ -15,13 +15,13 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Helpers\CanCompareValues;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Envelope;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be between {{minValue}} and {{maxValue}}',
diff --git a/src/Validators/BetweenExclusive.php b/src/Validators/BetweenExclusive.php
index 41ee52218..3fd65054c 100644
--- a/src/Validators/BetweenExclusive.php
+++ b/src/Validators/BetweenExclusive.php
@@ -12,13 +12,13 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Helpers\CanCompareValues;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Envelope;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be greater than {{minValue}} and less than {{maxValue}}',
diff --git a/src/Validators/Blank.php b/src/Validators/Blank.php
index 067864ce6..c83737ba6 100644
--- a/src/Validators/Blank.php
+++ b/src/Validators/Blank.php
@@ -15,7 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -27,7 +27,7 @@
use function is_string;
use function trim;
-#[Mixin(exclude: ['nullOr', 'undefOr'])]
+#[Composable(without: [NullOr::class, UndefOr::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be blank',
diff --git a/src/Validators/BoolType.php b/src/Validators/BoolType.php
index 49626f90b..4e7c1e1af 100644
--- a/src/Validators/BoolType.php
+++ b/src/Validators/BoolType.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -25,6 +26,7 @@
'{{subject}} must be a boolean',
'{{subject}} must not be a boolean',
)]
+#[Assurance(type: 'bool')]
final class BoolType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/BoolVal.php b/src/Validators/BoolVal.php
index 277ca2409..24b4b06a7 100644
--- a/src/Validators/BoolVal.php
+++ b/src/Validators/BoolVal.php
@@ -18,6 +18,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -32,6 +33,7 @@
'{{subject}} must be a boolean',
'{{subject}} must not be a boolean',
)]
+#[Assurance(type: 'bool')]
final class BoolVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Bsn.php b/src/Validators/Bsn.php
index 7d4ac1838..b357c0979 100644
--- a/src/Validators/Bsn.php
+++ b/src/Validators/Bsn.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -29,6 +30,7 @@
'{{subject}} must be a BSN',
'{{subject}} must not be a BSN',
)]
+#[Assurance(type: 'string')]
final class Bsn extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/CallableType.php b/src/Validators/CallableType.php
index 04dc72cb0..3a543eaf7 100644
--- a/src/Validators/CallableType.php
+++ b/src/Validators/CallableType.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -24,6 +25,7 @@
'{{subject}} must be a callable function',
'{{subject}} must not be a callable function',
)]
+#[Assurance(type: 'callable')]
final class CallableType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Charset.php b/src/Validators/Charset.php
index 7ac4c01d3..a4cbb77b4 100644
--- a/src/Validators/Charset.php
+++ b/src/Validators/Charset.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -33,6 +34,7 @@
'{{subject}} must consist only of characters from the {{charset|list:or}} character-set',
'{{subject}} must not consist only of characters from the {{charset|list:or}} character-set',
)]
+#[Assurance(type: 'string')]
final readonly class Charset implements Validator
{
/** @var non-empty-array */
diff --git a/src/Validators/Cnh.php b/src/Validators/Cnh.php
index 50b9a5664..eef2cd35b 100644
--- a/src/Validators/Cnh.php
+++ b/src/Validators/Cnh.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -30,6 +31,7 @@
'{{subject}} must be a CNH',
'{{subject}} must not be a CNH',
)]
+#[Assurance(type: 'string')]
final class Cnh extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Cnpj.php b/src/Validators/Cnpj.php
index 3cfc56918..d226baee8 100644
--- a/src/Validators/Cnpj.php
+++ b/src/Validators/Cnpj.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -34,6 +35,7 @@
'{{subject}} must be a CNPJ',
'{{subject}} must not be a CNPJ',
)]
+#[Assurance(type: 'string')]
final class Cnpj extends Simple
{
private const int BASE_ASCII = 48;
diff --git a/src/Validators/Consonant.php b/src/Validators/Consonant.php
index da517afb5..fd5d2be35 100644
--- a/src/Validators/Consonant.php
+++ b/src/Validators/Consonant.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -33,6 +34,7 @@
'{{subject}} must not consist only of consonants or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Consonant extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/Control.php b/src/Validators/Control.php
index dc6dea0db..da9c0f618 100644
--- a/src/Validators/Control.php
+++ b/src/Validators/Control.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -32,6 +33,7 @@
'{{subject}} must not consist only of control characters or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Control extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/Countable.php b/src/Validators/Countable.php
index 7b75be4f0..e61d0cf28 100644
--- a/src/Validators/Countable.php
+++ b/src/Validators/Countable.php
@@ -20,6 +20,7 @@
use Attribute;
use Countable as CountableInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -30,6 +31,7 @@
'{{subject}} must be countable',
'{{subject}} must not be countable',
)]
+#[Assurance(type: ['array', CountableInterface::class])]
final class Countable extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/CountryCode.php b/src/Validators/CountryCode.php
index 93894f565..6a53f68cf 100644
--- a/src/Validators/CountryCode.php
+++ b/src/Validators/CountryCode.php
@@ -19,6 +19,7 @@
use Attribute;
use Psr\Container\NotFoundExceptionInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\ContainerRegistry;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
@@ -35,6 +36,7 @@
'{{subject}} must be a country code',
'{{subject}} must not be a country code',
)]
+#[Assurance(type: 'string')]
final readonly class CountryCode implements Validator
{
private Countries $countries;
diff --git a/src/Validators/Cpf.php b/src/Validators/Cpf.php
index 1e2f69f81..2fd72c869 100644
--- a/src/Validators/Cpf.php
+++ b/src/Validators/Cpf.php
@@ -20,6 +20,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -33,6 +34,7 @@
'{{subject}} must be a CPF',
'{{subject}} must not be a CPF',
)]
+#[Assurance(type: 'string')]
final class Cpf extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/CreditCard.php b/src/Validators/CreditCard.php
index 23ed28857..fd429fe02 100644
--- a/src/Validators/CreditCard.php
+++ b/src/Validators/CreditCard.php
@@ -20,6 +20,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -41,6 +42,7 @@
'{{subject}} must not be a {{brand|raw}} credit card number',
self::TEMPLATE_BRANDED,
)]
+#[Assurance(type: 'string')]
final readonly class CreditCard implements Validator
{
public const string TEMPLATE_BRANDED = '__branded__';
diff --git a/src/Validators/CurrencyCode.php b/src/Validators/CurrencyCode.php
index 74af78c08..f4314e98e 100644
--- a/src/Validators/CurrencyCode.php
+++ b/src/Validators/CurrencyCode.php
@@ -16,6 +16,7 @@
use Attribute;
use Psr\Container\NotFoundExceptionInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\ContainerRegistry;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
@@ -31,6 +32,7 @@
'{{subject}} must be a currency code',
'{{subject}} must not be a currency code',
)]
+#[Assurance(type: 'string')]
final readonly class CurrencyCode implements Validator
{
private Currencies $currencies;
diff --git a/src/Validators/Date.php b/src/Validators/Date.php
index 151ee9b0a..d9f391e56 100644
--- a/src/Validators/Date.php
+++ b/src/Validators/Date.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
@@ -33,6 +34,7 @@
'{{subject}} must be a date in the {{sample}} format',
'{{subject}} must not be a date in the {{sample}} format',
)]
+#[Assurance(type: 'string')]
final readonly class Date implements Validator
{
use CanValidateDateTime;
diff --git a/src/Validators/DateTime.php b/src/Validators/DateTime.php
index 8cef8308e..f9ef97590 100644
--- a/src/Validators/DateTime.php
+++ b/src/Validators/DateTime.php
@@ -18,6 +18,7 @@
use Attribute;
use DateTimeInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -38,6 +39,7 @@
'{{subject}} must not be a date/time in the {{sample}} format',
self::TEMPLATE_FORMAT,
)]
+#[Assurance(type: ['string', DateTimeInterface::class])]
final class DateTime implements Validator
{
use CanValidateDateTime;
diff --git a/src/Validators/DateTimeDiff.php b/src/Validators/DateTimeDiff.php
index 194a117d1..96373a362 100644
--- a/src/Validators/DateTimeDiff.php
+++ b/src/Validators/DateTimeDiff.php
@@ -14,6 +14,7 @@
use Attribute;
use DateTimeImmutable;
use DateTimeInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
@@ -44,6 +45,7 @@
'For comparison with {{now|raw}}, {{subject}} must not be a datetime in the format {{sample|raw}}',
self::TEMPLATE_WRONG_FORMAT,
)]
+#[Assurance(type: ['string', DateTimeInterface::class])]
final readonly class DateTimeDiff implements Validator
{
use CanValidateDateTime;
diff --git a/src/Validators/Decimal.php b/src/Validators/Decimal.php
index 424109587..238275b4b 100644
--- a/src/Validators/Decimal.php
+++ b/src/Validators/Decimal.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -28,6 +29,7 @@
'{{subject}} must have {{decimals}} decimal places',
'{{subject}} must not have {{decimals}} decimal places',
)]
+#[Assurance(type: 'string')]
final readonly class Decimal implements Validator
{
public function __construct(
diff --git a/src/Validators/Digit.php b/src/Validators/Digit.php
index 5d432749b..213cbcf04 100644
--- a/src/Validators/Digit.php
+++ b/src/Validators/Digit.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -31,6 +32,7 @@
'{{subject}} must not consist only of digits (0-9) or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Digit extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/Directory.php b/src/Validators/Directory.php
index 4437ac4f0..f3987dca3 100644
--- a/src/Validators/Directory.php
+++ b/src/Validators/Directory.php
@@ -16,6 +16,7 @@
use Attribute;
use Directory as NativeDirectory;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SplFileInfo;
@@ -28,6 +29,7 @@
'{{subject}} must be an accessible existing directory',
'{{subject}} must not be an accessible existing directory',
)]
+#[Assurance(type: ['string', SplFileInfo::class])]
final class Directory extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Domain.php b/src/Validators/Domain.php
index e4ec03bc0..5053fe80a 100644
--- a/src/Validators/Domain.php
+++ b/src/Validators/Domain.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -30,6 +31,7 @@
'{{subject}} must be an internet domain',
'{{subject}} must not be an internet domain',
)]
+#[Assurance(type: 'string')]
final class Domain implements Validator
{
private readonly Validator $genericRule;
diff --git a/src/Validators/Each.php b/src/Validators/Each.php
index 43d24f04f..c3aa48ebd 100644
--- a/src/Validators/Each.php
+++ b/src/Validators/Each.php
@@ -18,6 +18,8 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceFrom;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
use Respect\Validation\Path;
@@ -32,6 +34,7 @@
'Each item in {{subject}} must be valid',
'Each item in {{subject}} must be invalid',
)]
+#[Assurance(from: AssuranceFrom::Elements)]
final class Each extends FilteredArray implements ShortCircuitable
{
use CanEvaluateShortCircuit;
diff --git a/src/Validators/Email.php b/src/Validators/Email.php
index 5877204e5..bed560404 100644
--- a/src/Validators/Email.php
+++ b/src/Validators/Email.php
@@ -21,6 +21,7 @@
use Attribute;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -36,6 +37,7 @@
'{{subject}} must be an email address',
'{{subject}} must not be an email address',
)]
+#[Assurance(type: 'string')]
final class Email extends Simple
{
private readonly EmailValidator|null $validator;
diff --git a/src/Validators/Emoji.php b/src/Validators/Emoji.php
index fa258c4c1..141d084bb 100644
--- a/src/Validators/Emoji.php
+++ b/src/Validators/Emoji.php
@@ -12,6 +12,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -23,6 +24,7 @@
'{{subject}} must be an emoji',
'{{subject}} must not be an emoji',
)]
+#[Assurance(type: 'string')]
final class Emoji extends Simple
{
private const string REGEX = <<<'REGEX'
diff --git a/src/Validators/Equals.php b/src/Validators/Equals.php
index d998f5df0..04ab4932a 100644
--- a/src/Validators/Equals.php
+++ b/src/Validators/Equals.php
@@ -15,14 +15,14 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use function is_scalar;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be equal to {{compareTo}}',
diff --git a/src/Validators/Equivalent.php b/src/Validators/Equivalent.php
index fc3cf6247..a9c41a8b7 100644
--- a/src/Validators/Equivalent.php
+++ b/src/Validators/Equivalent.php
@@ -15,14 +15,14 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Comparison;
use function is_scalar;
use function mb_strtoupper;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be equivalent to {{compareTo}}',
diff --git a/src/Validators/Even.php b/src/Validators/Even.php
index 5208abf90..4fea0f782 100644
--- a/src/Validators/Even.php
+++ b/src/Validators/Even.php
@@ -18,7 +18,8 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -26,12 +27,13 @@
use const FILTER_VALIDATE_INT;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be an even number',
'{{subject}} must be an odd number',
)]
+#[Assurance(type: 'int')]
final class Even extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Executable.php b/src/Validators/Executable.php
index 6e056f740..b1e4b965b 100644
--- a/src/Validators/Executable.php
+++ b/src/Validators/Executable.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SplFileInfo;
@@ -27,6 +28,7 @@
'{{subject}} must be an accessible existing executable file',
'{{subject}} must not be an accessible existing executable file',
)]
+#[Assurance(type: ['string', SplFileInfo::class])]
final class Executable extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Exists.php b/src/Validators/Exists.php
index dee5bab98..3d7ce0def 100644
--- a/src/Validators/Exists.php
+++ b/src/Validators/Exists.php
@@ -15,7 +15,8 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SplFileInfo;
@@ -23,12 +24,13 @@
use function file_exists;
use function is_string;
-#[Mixin(exclude: ['all', 'key', 'property'])]
+#[Composable(without: [All::class, Key::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be an existing file',
'{{subject}} must not be an existing file',
)]
+#[Assurance(type: 'string')]
final class Exists extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Extension.php b/src/Validators/Extension.php
index 317fef0c6..7d4e7b742 100644
--- a/src/Validators/Extension.php
+++ b/src/Validators/Extension.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -28,6 +29,7 @@
'{{subject}} must have the {{extension}} extension',
'{{subject}} must not have the {{extension}} extension',
)]
+#[Assurance(type: ['string', SplFileInfo::class])]
final readonly class Extension implements Validator
{
public function __construct(
diff --git a/src/Validators/Factor.php b/src/Validators/Factor.php
index bfdcdcacc..c1a501410 100644
--- a/src/Validators/Factor.php
+++ b/src/Validators/Factor.php
@@ -14,7 +14,8 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -24,12 +25,13 @@
use function is_numeric;
use function preg_match;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be a factor of {{dividend|raw}}',
'{{subject}} must not be a factor of {{dividend|raw}}',
)]
+#[Assurance(type: 'int')]
final readonly class Factor implements Validator
{
public function __construct(
diff --git a/src/Validators/FalseVal.php b/src/Validators/FalseVal.php
index e5f1fc76a..c09599b6a 100644
--- a/src/Validators/FalseVal.php
+++ b/src/Validators/FalseVal.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -26,6 +27,7 @@
'{{subject}} must evaluate to `false`',
'{{subject}} must not evaluate to `false`',
)]
+#[Assurance(type: 'false')]
final class FalseVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/File.php b/src/Validators/File.php
index 2d48d3d55..e0625d7d7 100644
--- a/src/Validators/File.php
+++ b/src/Validators/File.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SplFileInfo;
@@ -27,6 +28,7 @@
'{{subject}} must be an accessible existing file',
'{{subject}} must not be an accessible existing file',
)]
+#[Assurance(type: ['string', SplFileInfo::class])]
final class File extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Finite.php b/src/Validators/Finite.php
index 9c917861b..dcd835a9e 100644
--- a/src/Validators/Finite.php
+++ b/src/Validators/Finite.php
@@ -15,19 +15,21 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use function is_finite;
use function is_numeric;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be a finite number',
'{{subject}} must not be a finite number',
)]
+#[Assurance(type: 'int|float|numeric-string')]
final class Finite extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/FloatType.php b/src/Validators/FloatType.php
index a94de207c..f87feb076 100644
--- a/src/Validators/FloatType.php
+++ b/src/Validators/FloatType.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -25,6 +26,7 @@
'{{subject}} must be a float',
'{{subject}} must not be a float',
)]
+#[Assurance(type: 'float')]
final class FloatType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/FloatVal.php b/src/Validators/FloatVal.php
index 515327314..3f034baca 100644
--- a/src/Validators/FloatVal.php
+++ b/src/Validators/FloatVal.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -29,6 +30,7 @@
'{{subject}} must be a floating-point number',
'{{subject}} must not be a floating-point number',
)]
+#[Assurance(type: 'int|float|numeric-string')]
final class FloatVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Format.php b/src/Validators/Format.php
index 99a35a975..c56e19332 100644
--- a/src/Validators/Format.php
+++ b/src/Validators/Format.php
@@ -12,16 +12,19 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\StringFormatter\Formatter;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
+use Stringable;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be formatted as {{formatted}}',
'{{subject}} must not be formatted as {{formatted}}',
)]
+#[Assurance(type: ['scalar', Stringable::class])]
final readonly class Format implements Validator
{
public function __construct(
diff --git a/src/Validators/Formatted.php b/src/Validators/Formatted.php
index 23696da5c..eda66f220 100644
--- a/src/Validators/Formatted.php
+++ b/src/Validators/Formatted.php
@@ -11,12 +11,12 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\StringFormatter\Formatter;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(exclude: ['all', 'key', 'property'])]
+#[Composable(without: [All::class, Key::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final readonly class Formatted implements Validator
{
diff --git a/src/Validators/Graph.php b/src/Validators/Graph.php
index 4c0c40de5..e36bed049 100644
--- a/src/Validators/Graph.php
+++ b/src/Validators/Graph.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -32,6 +33,7 @@
'{{subject}} must not consist only of printable non-spacing characters or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Graph extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/GreaterThan.php b/src/Validators/GreaterThan.php
index cafa657b5..af6f5cf9a 100644
--- a/src/Validators/GreaterThan.php
+++ b/src/Validators/GreaterThan.php
@@ -15,11 +15,11 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Comparison;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be greater than {{compareTo}}',
diff --git a/src/Validators/GreaterThanOrEqual.php b/src/Validators/GreaterThanOrEqual.php
index 0d273fc23..41a22a2ac 100644
--- a/src/Validators/GreaterThanOrEqual.php
+++ b/src/Validators/GreaterThanOrEqual.php
@@ -14,11 +14,11 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Comparison;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be greater than or equal to {{compareTo}}',
diff --git a/src/Validators/Hetu.php b/src/Validators/Hetu.php
index c3025f403..c9203f164 100644
--- a/src/Validators/Hetu.php
+++ b/src/Validators/Hetu.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must be a Finnish personal identity code',
'{{subject}} must not be a Finnish personal identity code',
)]
+#[Assurance(type: 'string')]
final class Hetu extends Simple
{
use CanValidateDateTime;
diff --git a/src/Validators/HexRgbColor.php b/src/Validators/HexRgbColor.php
index 5203ee073..c1b85b8ff 100644
--- a/src/Validators/HexRgbColor.php
+++ b/src/Validators/HexRgbColor.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Envelope;
@@ -21,6 +22,7 @@
'{{subject}} must be a hex RGB color',
'{{subject}} must not be a hex RGB color',
)]
+#[Assurance(type: 'string')]
final class HexRgbColor extends Envelope
{
public function __construct()
diff --git a/src/Validators/Iban.php b/src/Validators/Iban.php
index 5f0d5eeec..b28fbedb9 100644
--- a/src/Validators/Iban.php
+++ b/src/Validators/Iban.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -31,6 +32,7 @@
'{{subject}} must be an IBAN',
'{{subject}} must not be an IBAN',
)]
+#[Assurance(type: 'string')]
final class Iban extends Simple
{
private const array COUNTRIES_LENGTHS = [
diff --git a/src/Validators/Identical.php b/src/Validators/Identical.php
index 25380531e..ebfb1e444 100644
--- a/src/Validators/Identical.php
+++ b/src/Validators/Identical.php
@@ -15,17 +15,20 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceFrom;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be identical to {{compareTo}}',
'{{subject}} must not be identical to {{compareTo}}',
)]
+#[Assurance(from: AssuranceFrom::Value)]
final readonly class Identical implements Validator
{
public function __construct(
diff --git a/src/Validators/Image.php b/src/Validators/Image.php
index da81a7da0..c5f24014f 100644
--- a/src/Validators/Image.php
+++ b/src/Validators/Image.php
@@ -15,6 +15,7 @@
use Attribute;
use finfo;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -31,6 +32,7 @@
'{{subject}} must be an accessible existing image file',
'{{subject}} must not be an accessible existing image file',
)]
+#[Assurance(type: 'string')]
final readonly class Image implements Validator
{
public function evaluate(mixed $input): Result
diff --git a/src/Validators/Imei.php b/src/Validators/Imei.php
index 9f8b4ae6a..487eee327 100644
--- a/src/Validators/Imei.php
+++ b/src/Validators/Imei.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must be an IMEI number',
'{{subject}} must not be an IMEI number',
)]
+#[Assurance(type: 'string')]
final class Imei extends Simple
{
private const int IMEI_SIZE = 15;
diff --git a/src/Validators/In.php b/src/Validators/In.php
index 53d46d9b7..d6870e162 100644
--- a/src/Validators/In.php
+++ b/src/Validators/In.php
@@ -16,7 +16,9 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceFrom;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -25,12 +27,13 @@
use function is_array;
use function mb_strpos;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be in {{haystack}}',
'{{subject}} must not be in {{haystack}}',
)]
+#[Assurance(from: AssuranceFrom::Member)]
final readonly class In implements Validator
{
public function __construct(
diff --git a/src/Validators/Infinite.php b/src/Validators/Infinite.php
index 34bcc67cd..eaff0a239 100644
--- a/src/Validators/Infinite.php
+++ b/src/Validators/Infinite.php
@@ -15,19 +15,21 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use function is_infinite;
use function is_numeric;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be an infinite number',
'{{subject}} must not be an infinite number',
)]
+#[Assurance(type: 'int|float|numeric-string')]
final class Infinite extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Instance.php b/src/Validators/Instance.php
index 5c63858fa..a924198e3 100644
--- a/src/Validators/Instance.php
+++ b/src/Validators/Instance.php
@@ -15,6 +15,8 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceParameter;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -24,10 +26,12 @@
'{{subject}} must be an instance of {{class|quote}}',
'{{subject}} must not be an instance of {{class|quote}}',
)]
+#[Assurance]
final readonly class Instance implements Validator
{
/** @param class-string $class */
public function __construct(
+ #[AssuranceParameter]
private string $class,
) {
}
diff --git a/src/Validators/IntType.php b/src/Validators/IntType.php
index 85ad69e57..16ccf8461 100644
--- a/src/Validators/IntType.php
+++ b/src/Validators/IntType.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -24,6 +25,7 @@
'{{subject}} must be an integer',
'{{subject}} must not be an integer',
)]
+#[Assurance(type: 'int')]
final class IntType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/IntVal.php b/src/Validators/IntVal.php
index d20783cc8..94b52b226 100644
--- a/src/Validators/IntVal.php
+++ b/src/Validators/IntVal.php
@@ -20,6 +20,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -32,6 +33,7 @@
'{{subject}} must be an integer',
'{{subject}} must not be an integer',
)]
+#[Assurance(type: 'int|numeric-string')]
final class IntVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Ip.php b/src/Validators/Ip.php
index f4c26e2b9..3c0971778 100644
--- a/src/Validators/Ip.php
+++ b/src/Validators/Ip.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -49,6 +50,7 @@
'{{subject}} must not be an IP address in the {{range|raw}} range',
self::TEMPLATE_NETWORK_RANGE,
)]
+#[Assurance(type: 'string')]
final class Ip implements Validator
{
public const string TEMPLATE_NETWORK_RANGE = '__network_range__';
diff --git a/src/Validators/Isbn.php b/src/Validators/Isbn.php
index a9c8716be..eb147a0ac 100644
--- a/src/Validators/Isbn.php
+++ b/src/Validators/Isbn.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -26,6 +27,7 @@
'{{subject}} must be an ISBN',
'{{subject}} must not be an ISBN',
)]
+#[Assurance(type: 'string')]
final class Isbn extends Simple
{
/** @see https://howtodoinjava.com/regex/java-regex-validate-international-standard-book-number-isbns */
diff --git a/src/Validators/IterableType.php b/src/Validators/IterableType.php
index 249b98359..d6c45650d 100644
--- a/src/Validators/IterableType.php
+++ b/src/Validators/IterableType.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -25,6 +26,7 @@
'{{subject}} must be iterable',
'{{subject}} must not iterable',
)]
+#[Assurance(type: 'iterable')]
final class IterableType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/IterableVal.php b/src/Validators/IterableVal.php
index a8b39f7b3..d7b80428e 100644
--- a/src/Validators/IterableVal.php
+++ b/src/Validators/IterableVal.php
@@ -16,15 +16,19 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Helpers\CanValidateIterable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
+use stdClass;
+use Traversable;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be iterable',
'{{subject}} must not be iterable',
)]
+#[Assurance(type: ['array', stdClass::class, Traversable::class])]
final class IterableVal extends Simple
{
use CanValidateIterable;
diff --git a/src/Validators/Json.php b/src/Validators/Json.php
index 643f80052..9e9eea5f5 100644
--- a/src/Validators/Json.php
+++ b/src/Validators/Json.php
@@ -20,6 +20,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -31,6 +32,7 @@
'{{subject}} must be a JSON string',
'{{subject}} must not be a JSON string',
)]
+#[Assurance(type: 'string')]
final class Json extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Key.php b/src/Validators/Key.php
index f7ac017ce..0308d1b5d 100644
--- a/src/Validators/Key.php
+++ b/src/Validators/Key.php
@@ -16,17 +16,19 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
+use Respect\Fluent\Attributes\ComposableParameter;
use Respect\Validation\Path;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use Respect\Validation\Validators\Core\KeyRelated;
-#[Mixin(prefix: 'key', prefixParameter: true, exclude: ['all', 'key', 'property'])]
+#[Composable(prefix: self::class, without: [All::class, self::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final readonly class Key implements KeyRelated
{
public function __construct(
+ #[ComposableParameter]
private int|string $key,
private Validator $validator,
) {
diff --git a/src/Validators/KeyExists.php b/src/Validators/KeyExists.php
index c8e0a73aa..f7bf8462b 100644
--- a/src/Validators/KeyExists.php
+++ b/src/Validators/KeyExists.php
@@ -13,7 +13,7 @@
use ArrayAccess;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Path;
use Respect\Validation\Result;
@@ -23,7 +23,7 @@
use function array_key_exists;
use function is_array;
-#[Mixin(exclude: ['all', 'key', 'property'])]
+#[Composable(without: [All::class, Key::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be present',
diff --git a/src/Validators/KeyOptional.php b/src/Validators/KeyOptional.php
index 3bd94596b..d802151e2 100644
--- a/src/Validators/KeyOptional.php
+++ b/src/Validators/KeyOptional.php
@@ -13,12 +13,12 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use Respect\Validation\Validators\Core\KeyRelated;
-#[Mixin(exclude: ['all', 'key', 'property'])]
+#[Composable(without: [All::class, Key::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final readonly class KeyOptional implements KeyRelated
{
diff --git a/src/Validators/KeySet.php b/src/Validators/KeySet.php
index 499a66238..5f878d044 100644
--- a/src/Validators/KeySet.php
+++ b/src/Validators/KeySet.php
@@ -15,7 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
@@ -33,7 +33,7 @@
use function array_merge;
use function array_slice;
-#[Mixin(exclude: ['all', 'key', 'property'])]
+#[Composable(without: [All::class, Key::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} validation failed',
diff --git a/src/Validators/LanguageCode.php b/src/Validators/LanguageCode.php
index 0199c1ae9..ed28ea9ce 100644
--- a/src/Validators/LanguageCode.php
+++ b/src/Validators/LanguageCode.php
@@ -15,6 +15,7 @@
use Attribute;
use Psr\Container\NotFoundExceptionInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\ContainerRegistry;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
@@ -31,6 +32,7 @@
'{{subject}} must be a language code',
'{{subject}} must not be a language code',
)]
+#[Assurance(type: 'string')]
final readonly class LanguageCode implements Validator
{
private Languages $languages;
diff --git a/src/Validators/LeapDate.php b/src/Validators/LeapDate.php
index 08c608826..abd433e80 100644
--- a/src/Validators/LeapDate.php
+++ b/src/Validators/LeapDate.php
@@ -18,6 +18,7 @@
use Attribute;
use DateTimeImmutable;
use DateTimeInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -28,6 +29,7 @@
'{{subject}} must be a leap date',
'{{subject}} must not be a leap date',
)]
+#[Assurance(type: 'string')]
final class LeapDate extends Simple
{
public function __construct(
diff --git a/src/Validators/LeapYear.php b/src/Validators/LeapYear.php
index b0fafefaf..165ca58b2 100644
--- a/src/Validators/LeapYear.php
+++ b/src/Validators/LeapYear.php
@@ -17,6 +17,7 @@
use Attribute;
use DateTimeInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -31,6 +32,7 @@
'{{subject}} must be a leap year',
'{{subject}} must not be a leap year',
)]
+#[Assurance(type: 'int|string')]
final class LeapYear extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Length.php b/src/Validators/Length.php
index feafa0ee6..5f7eead39 100644
--- a/src/Validators/Length.php
+++ b/src/Validators/Length.php
@@ -20,7 +20,7 @@
use Attribute;
use Countable as PhpCountable;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -30,7 +30,7 @@
use function is_string;
use function mb_strlen;
-#[Mixin(prefix: 'length', requireInclusion: true)]
+#[Composable(prefix: self::class, optIn: true)]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'The length of',
diff --git a/src/Validators/LessThan.php b/src/Validators/LessThan.php
index eb2039b69..715d98337 100644
--- a/src/Validators/LessThan.php
+++ b/src/Validators/LessThan.php
@@ -15,11 +15,11 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Comparison;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be less than {{compareTo}}',
diff --git a/src/Validators/LessThanOrEqual.php b/src/Validators/LessThanOrEqual.php
index 7734befb5..28e22730a 100644
--- a/src/Validators/LessThanOrEqual.php
+++ b/src/Validators/LessThanOrEqual.php
@@ -14,11 +14,11 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Comparison;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be less than or equal to {{compareTo}}',
diff --git a/src/Validators/Lowercase.php b/src/Validators/Lowercase.php
index dddd38ce3..d7031879d 100644
--- a/src/Validators/Lowercase.php
+++ b/src/Validators/Lowercase.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must consist only of lowercase letters',
'{{subject}} must not consist only of lowercase letters',
)]
+#[Assurance(type: 'string')]
final class Lowercase extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Luhn.php b/src/Validators/Luhn.php
index f77197f80..2e8f074d0 100644
--- a/src/Validators/Luhn.php
+++ b/src/Validators/Luhn.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -28,6 +29,7 @@
'{{subject}} must be a Luhn number',
'{{subject}} must not be a Luhn number',
)]
+#[Assurance(type: 'string')]
final class Luhn extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/MacAddress.php b/src/Validators/MacAddress.php
index 158d07858..40f610005 100644
--- a/src/Validators/MacAddress.php
+++ b/src/Validators/MacAddress.php
@@ -18,6 +18,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -29,6 +30,7 @@
'{{subject}} must be a MAC address',
'{{subject}} must not be a MAC address',
)]
+#[Assurance(type: 'string')]
final class MacAddress extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Max.php b/src/Validators/Max.php
index 9d89607bb..d31f30374 100644
--- a/src/Validators/Max.php
+++ b/src/Validators/Max.php
@@ -15,14 +15,14 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validators\Core\FilteredArray;
use function max;
-#[Mixin(prefix: 'max', requireInclusion: true)]
+#[Composable(prefix: self::class, optIn: true)]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template('The maximum of', 'The maximum of')]
final class Max extends FilteredArray
diff --git a/src/Validators/Mimetype.php b/src/Validators/Mimetype.php
index affef309c..45fd4a753 100644
--- a/src/Validators/Mimetype.php
+++ b/src/Validators/Mimetype.php
@@ -14,6 +14,7 @@
use Attribute;
use finfo;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -29,6 +30,7 @@
'{{subject}} must have the {{mimetype}} MIME type',
'{{subject}} must not have the {{mimetype}} MIME type',
)]
+#[Assurance(type: 'string')]
final readonly class Mimetype implements Validator
{
public function __construct(
diff --git a/src/Validators/Min.php b/src/Validators/Min.php
index 9083b43c8..6d1812cbf 100644
--- a/src/Validators/Min.php
+++ b/src/Validators/Min.php
@@ -15,14 +15,14 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validators\Core\FilteredArray;
use function min;
-#[Mixin(prefix: 'min', requireInclusion: true)]
+#[Composable(prefix: self::class, optIn: true)]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template('The minimum of', 'The minimum of')]
final class Min extends FilteredArray
diff --git a/src/Validators/Multiple.php b/src/Validators/Multiple.php
index e780ff802..afbddd97a 100644
--- a/src/Validators/Multiple.php
+++ b/src/Validators/Multiple.php
@@ -17,17 +17,19 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be a multiple of {{multipleOf}}',
'{{subject}} must not be a multiple of {{multipleOf}}',
)]
+#[Assurance(type: 'int')]
final readonly class Multiple implements Validator
{
public function __construct(
diff --git a/src/Validators/Named.php b/src/Validators/Named.php
index db58b9dc4..7da7b0a5c 100644
--- a/src/Validators/Named.php
+++ b/src/Validators/Named.php
@@ -12,7 +12,7 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Name;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -20,7 +20,7 @@
use function is_string;
-#[Mixin(exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
+#[Composable(without: [All::class, Key::class, Property::class, Not::class, NullOr::class, UndefOr::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final readonly class Named implements Nameable
{
diff --git a/src/Validators/Negative.php b/src/Validators/Negative.php
index 6eebb5ee4..f03ea6ae5 100644
--- a/src/Validators/Negative.php
+++ b/src/Validators/Negative.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -25,6 +26,7 @@
'{{subject}} must be a negative number',
'{{subject}} must not be a negative number',
)]
+#[Assurance(type: 'int|float|numeric-string')]
final class Negative extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/NfeAccessKey.php b/src/Validators/NfeAccessKey.php
index 33dcc0139..d99d5c38e 100644
--- a/src/Validators/NfeAccessKey.php
+++ b/src/Validators/NfeAccessKey.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -29,6 +30,7 @@
'{{subject}} must be a NFe access key',
'{{subject}} must not be a NFe access key',
)]
+#[Assurance(type: 'string')]
final class NfeAccessKey extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Nif.php b/src/Validators/Nif.php
index ef01ae7b8..169b7f76d 100644
--- a/src/Validators/Nif.php
+++ b/src/Validators/Nif.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -31,6 +32,7 @@
'{{subject}} must be a NIF',
'{{subject}} must not be a NIF',
)]
+#[Assurance(type: 'string')]
final class Nif extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Nip.php b/src/Validators/Nip.php
index fa015a0a0..253afc2c7 100644
--- a/src/Validators/Nip.php
+++ b/src/Validators/Nip.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must be a Polish VAT identification number',
'{{subject}} must not be a Polish VAT identification number',
)]
+#[Assurance(type: 'string')]
final class Nip extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/NoneOf.php b/src/Validators/NoneOf.php
index e0838ce87..1f309db98 100644
--- a/src/Validators/NoneOf.php
+++ b/src/Validators/NoneOf.php
@@ -15,6 +15,9 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceCompose;
+use Respect\Fluent\Attributes\AssuranceModifier;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -34,6 +37,7 @@
'{{subject}} must pass all the rules',
self::TEMPLATE_ALL,
)]
+#[Assurance(compose: AssuranceCompose::Union, modifier: AssuranceModifier::Exclude)]
final class NoneOf extends LogicalComposite implements ShortCircuitable
{
use CanEvaluateShortCircuit;
diff --git a/src/Validators/Not.php b/src/Validators/Not.php
index 1026bb6c6..2363fce30 100644
--- a/src/Validators/Not.php
+++ b/src/Validators/Not.php
@@ -18,12 +18,15 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceModifier;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(prefix: 'not', exclude: ['not'])]
+#[Composable(prefix: self::class, without: [self::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+#[Assurance(modifier: AssuranceModifier::Exclude)]
final readonly class Not implements Validator
{
public function __construct(
diff --git a/src/Validators/NullOr.php b/src/Validators/NullOr.php
index 9b02f1500..a026090ba 100644
--- a/src/Validators/NullOr.php
+++ b/src/Validators/NullOr.php
@@ -14,19 +14,25 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceModifier;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use function array_map;
-#[Mixin(prefix: 'nullOr', exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
+#[Composable(
+ prefix: self::class,
+ without: [All::class, Key::class, Property::class, Not::class, self::class, UndefOr::class],
+)]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'or must be null',
'and must not be null',
)]
+#[Assurance(modifier: AssuranceModifier::Nullable)]
final readonly class NullOr implements Validator
{
public function __construct(
diff --git a/src/Validators/NullType.php b/src/Validators/NullType.php
index d6c15df8c..da29bd2ee 100644
--- a/src/Validators/NullType.php
+++ b/src/Validators/NullType.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -22,6 +23,7 @@
'{{subject}} must be null',
'{{subject}} must not be null',
)]
+#[Assurance(type: 'null')]
final class NullType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Number.php b/src/Validators/Number.php
index eac0169fa..220a88c70 100644
--- a/src/Validators/Number.php
+++ b/src/Validators/Number.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -28,6 +29,7 @@
'{{subject}} must be a number',
'{{subject}} must not be a number',
)]
+#[Assurance(type: 'int|float|numeric-string')]
final class Number extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/NumericVal.php b/src/Validators/NumericVal.php
index 5a5f27253..94d103981 100644
--- a/src/Validators/NumericVal.php
+++ b/src/Validators/NumericVal.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -25,6 +26,7 @@
'{{subject}} must be numeric',
'{{subject}} must not be numeric',
)]
+#[Assurance(type: 'int|float|numeric-string')]
final class NumericVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/ObjectType.php b/src/Validators/ObjectType.php
index 5c9dfccaa..5ac989ac6 100644
--- a/src/Validators/ObjectType.php
+++ b/src/Validators/ObjectType.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -24,6 +25,7 @@
'{{subject}} must be an object',
'{{subject}} must not be an object',
)]
+#[Assurance(type: 'object')]
final class ObjectType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Odd.php b/src/Validators/Odd.php
index bc0331fd7..ec252b8c9 100644
--- a/src/Validators/Odd.php
+++ b/src/Validators/Odd.php
@@ -17,7 +17,8 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -26,12 +27,13 @@
use const FILTER_VALIDATE_INT;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be an odd number',
'{{subject}} must be an even number',
)]
+#[Assurance(type: 'int')]
final class Odd extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/OneOf.php b/src/Validators/OneOf.php
index 11c871130..1d42c6196 100644
--- a/src/Validators/OneOf.php
+++ b/src/Validators/OneOf.php
@@ -16,6 +16,8 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceCompose;
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -40,6 +42,7 @@
'{{subject}} must pass only one of the rules',
self::TEMPLATE_MORE_THAN_ONE,
)]
+#[Assurance(compose: AssuranceCompose::Union)]
final class OneOf extends LogicalComposite implements ShortCircuitable
{
use CanEvaluateShortCircuit;
diff --git a/src/Validators/Pesel.php b/src/Validators/Pesel.php
index 67f2421bc..936f6f6f4 100644
--- a/src/Validators/Pesel.php
+++ b/src/Validators/Pesel.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -25,6 +26,7 @@
'{{subject}} must be a PESEL',
'{{subject}} must not be a PESEL',
)]
+#[Assurance(type: 'string')]
final class Pesel extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Phone.php b/src/Validators/Phone.php
index 78f8f3881..95452d907 100644
--- a/src/Validators/Phone.php
+++ b/src/Validators/Phone.php
@@ -22,6 +22,7 @@
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use Psr\Container\NotFoundExceptionInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\ContainerRegistry;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
@@ -43,6 +44,7 @@
'{{subject}} must not be a phone number for country {{countryName|trans}}',
self::TEMPLATE_FOR_COUNTRY,
)]
+#[Assurance(type: 'string')]
final class Phone implements Validator
{
public const string TEMPLATE_FOR_COUNTRY = '__for_country__';
diff --git a/src/Validators/Pis.php b/src/Validators/Pis.php
index 9ddfbdbf2..7e3811577 100644
--- a/src/Validators/Pis.php
+++ b/src/Validators/Pis.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must be a PIS number',
'{{subject}} must not be a PIS number',
)]
+#[Assurance(type: 'string')]
final class Pis extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/PolishIdCard.php b/src/Validators/PolishIdCard.php
index 602a1267a..636204a14 100644
--- a/src/Validators/PolishIdCard.php
+++ b/src/Validators/PolishIdCard.php
@@ -12,6 +12,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -25,6 +26,7 @@
'{{subject}} must be a Polish Identity Card number',
'{{subject}} must not be a Polish Identity Card number',
)]
+#[Assurance(type: 'string')]
final class PolishIdCard extends Simple
{
private const int ASCII_CODE_0 = 48;
diff --git a/src/Validators/PortugueseNif.php b/src/Validators/PortugueseNif.php
index c163d8d0a..9b4fb7aca 100644
--- a/src/Validators/PortugueseNif.php
+++ b/src/Validators/PortugueseNif.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -32,6 +33,7 @@
'{{subject}} must be a Portuguese NIF',
'{{subject}} must not be a Portuguese NIF',
)]
+#[Assurance(type: 'string')]
final class PortugueseNif extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Positive.php b/src/Validators/Positive.php
index 748180d63..9607a69b1 100644
--- a/src/Validators/Positive.php
+++ b/src/Validators/Positive.php
@@ -15,18 +15,20 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use function is_numeric;
-#[Mixin(include: ['length', 'max', 'min'])]
+#[Composable(with: [Length::class, Max::class, Min::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be a positive number',
'{{subject}} must not be a positive number',
)]
+#[Assurance(type: 'int|float|numeric-string')]
final class Positive extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/PostalCode.php b/src/Validators/PostalCode.php
index 9c74df8ab..508982b25 100644
--- a/src/Validators/PostalCode.php
+++ b/src/Validators/PostalCode.php
@@ -26,6 +26,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Helpers\DataLoader;
use Respect\Validation\Message\Template;
@@ -37,6 +38,7 @@
'{{subject}} must be a postal code for {{countryCode}}',
'{{subject}} must not be a postal code for {{countryCode}}',
)]
+#[Assurance(type: 'string')]
final class PostalCode extends Envelope
{
private const array POSTAL_CODES_EXTRA = [
diff --git a/src/Validators/Printable.php b/src/Validators/Printable.php
index df6fa7a7b..60ba77d24 100644
--- a/src/Validators/Printable.php
+++ b/src/Validators/Printable.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -32,6 +33,7 @@
'{{subject}} must not consist only of printable characters or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Printable extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/Property.php b/src/Validators/Property.php
index 023ab0e71..273f11706 100644
--- a/src/Validators/Property.php
+++ b/src/Validators/Property.php
@@ -19,16 +19,18 @@
use Attribute;
use ReflectionClass;
use ReflectionObject;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
+use Respect\Fluent\Attributes\ComposableParameter;
use Respect\Validation\Path;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(prefix: 'property', prefixParameter: true, exclude: ['all', 'key', 'property'])]
+#[Composable(prefix: self::class, without: [All::class, Key::class, self::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final readonly class Property implements Validator
{
public function __construct(
+ #[ComposableParameter]
private string $propertyName,
private Validator $validator,
) {
diff --git a/src/Validators/PropertyExists.php b/src/Validators/PropertyExists.php
index efa55c00e..d207ece8d 100644
--- a/src/Validators/PropertyExists.php
+++ b/src/Validators/PropertyExists.php
@@ -14,7 +14,7 @@
use Attribute;
use ReflectionClass;
use ReflectionObject;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Message\Template;
use Respect\Validation\Path;
use Respect\Validation\Result;
@@ -22,7 +22,7 @@
use function is_object;
-#[Mixin(exclude: ['all', 'key', 'property'])]
+#[Composable(without: [All::class, Key::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be present',
diff --git a/src/Validators/PropertyOptional.php b/src/Validators/PropertyOptional.php
index 56ed4f54e..519e948ef 100644
--- a/src/Validators/PropertyOptional.php
+++ b/src/Validators/PropertyOptional.php
@@ -13,11 +13,11 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(exclude: ['all', 'key', 'property'])]
+#[Composable(without: [All::class, Key::class, Property::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final readonly class PropertyOptional implements Validator
{
diff --git a/src/Validators/PublicDomainSuffix.php b/src/Validators/PublicDomainSuffix.php
index 002f88d86..b82b073ee 100644
--- a/src/Validators/PublicDomainSuffix.php
+++ b/src/Validators/PublicDomainSuffix.php
@@ -12,6 +12,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Helpers\DataLoader;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -36,6 +37,7 @@
'{{subject}} must be a public domain suffix',
'{{subject}} must not be a public domain suffix',
)]
+#[Assurance(type: 'string')]
final class PublicDomainSuffix extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Punct.php b/src/Validators/Punct.php
index b781f299d..a1e81face 100644
--- a/src/Validators/Punct.php
+++ b/src/Validators/Punct.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -32,6 +33,7 @@
'{{subject}} must not consist only of punctuation characters or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Punct extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/Readable.php b/src/Validators/Readable.php
index 641e50812..2424ecb31 100644
--- a/src/Validators/Readable.php
+++ b/src/Validators/Readable.php
@@ -17,6 +17,7 @@
use Attribute;
use Psr\Http\Message\StreamInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SplFileInfo;
@@ -29,6 +30,7 @@
'{{subject}} must be readable',
'{{subject}} must not be readable',
)]
+#[Assurance(type: ['string', SplFileInfo::class, StreamInterface::class])]
final class Readable extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Regex.php b/src/Validators/Regex.php
index 679d277b3..562f90b60 100644
--- a/src/Validators/Regex.php
+++ b/src/Validators/Regex.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -28,6 +29,7 @@
'{{subject}} must match the {{regex|quote}} pattern',
'{{subject}} must not match the {{regex|quote}} pattern',
)]
+#[Assurance(type: 'string')]
final readonly class Regex implements Validator
{
public function __construct(
diff --git a/src/Validators/ResourceType.php b/src/Validators/ResourceType.php
index 5fcd6a767..d78fa50a9 100644
--- a/src/Validators/ResourceType.php
+++ b/src/Validators/ResourceType.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -24,6 +25,7 @@
'{{subject}} must be an internal resource',
'{{subject}} must not be an internal resource',
)]
+#[Assurance(type: 'resource')]
final class ResourceType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Roman.php b/src/Validators/Roman.php
index d1c7959f2..73ea46b1a 100644
--- a/src/Validators/Roman.php
+++ b/src/Validators/Roman.php
@@ -18,6 +18,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Envelope;
@@ -26,6 +27,7 @@
'{{subject}} must be a Roman numeral',
'{{subject}} must not be a Roman numeral',
)]
+#[Assurance(type: 'string')]
final class Roman extends Envelope
{
public function __construct()
diff --git a/src/Validators/ScalarVal.php b/src/Validators/ScalarVal.php
index 2edbcefd1..e7f2fc9d7 100644
--- a/src/Validators/ScalarVal.php
+++ b/src/Validators/ScalarVal.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -24,6 +25,7 @@
'{{subject}} must be a scalar',
'{{subject}} must not be a scalar',
)]
+#[Assurance(type: 'int|float|bool|string')]
final class ScalarVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Slug.php b/src/Validators/Slug.php
index dfd9f2b7e..f849f965a 100644
--- a/src/Validators/Slug.php
+++ b/src/Validators/Slug.php
@@ -19,6 +19,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -31,6 +32,7 @@
'{{subject}} must be a slug',
'{{subject}} must not be a slug',
)]
+#[Assurance(type: 'string')]
final class Slug extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Sorted.php b/src/Validators/Sorted.php
index b8debd798..222783c71 100644
--- a/src/Validators/Sorted.php
+++ b/src/Validators/Sorted.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -35,6 +36,7 @@
'{{subject}} must not be sorted in descending order',
self::TEMPLATE_DESCENDING,
)]
+#[Assurance(type: 'array|string')]
final readonly class Sorted implements Validator
{
public const string TEMPLATE_ASCENDING = '__ascending__';
diff --git a/src/Validators/Space.php b/src/Validators/Space.php
index b39cb6503..96752df7b 100644
--- a/src/Validators/Space.php
+++ b/src/Validators/Space.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -32,6 +33,7 @@
'{{subject}} must not consist only of space characters or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Space extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/Spaced.php b/src/Validators/Spaced.php
index b8c020d4c..a407b2a76 100644
--- a/src/Validators/Spaced.php
+++ b/src/Validators/Spaced.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must contain at least one whitespace',
'{{subject}} must not contain whitespace',
)]
+#[Assurance(type: 'string')]
final class Spaced extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/StringType.php b/src/Validators/StringType.php
index 50bc93356..e9d456c2a 100644
--- a/src/Validators/StringType.php
+++ b/src/Validators/StringType.php
@@ -14,6 +14,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -24,6 +25,7 @@
'{{subject}} must be a string',
'{{subject}} must not be a string',
)]
+#[Assurance(type: 'string')]
final class StringType extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/StringVal.php b/src/Validators/StringVal.php
index 9126ce561..4c2270a02 100644
--- a/src/Validators/StringVal.php
+++ b/src/Validators/StringVal.php
@@ -17,8 +17,10 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
+use Stringable;
use function is_object;
use function is_scalar;
@@ -29,6 +31,7 @@
'{{subject}} must be a string',
'{{subject}} must not be a string',
)]
+#[Assurance(type: ['scalar', Stringable::class])]
final class StringVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/SubdivisionCode.php b/src/Validators/SubdivisionCode.php
index da3c721f1..21fb99083 100644
--- a/src/Validators/SubdivisionCode.php
+++ b/src/Validators/SubdivisionCode.php
@@ -13,6 +13,7 @@
use Attribute;
use Psr\Container\NotFoundExceptionInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\ContainerRegistry;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
@@ -28,6 +29,7 @@
'{{subject}} must be a subdivision code of {{countryName|trans}}',
'{{subject}} must not be a subdivision code of {{countryName|trans}}',
)]
+#[Assurance(type: 'string')]
final readonly class SubdivisionCode implements Validator
{
use CanValidateUndefined;
diff --git a/src/Validators/Subset.php b/src/Validators/Subset.php
index dbde4cdf0..1897663b8 100644
--- a/src/Validators/Subset.php
+++ b/src/Validators/Subset.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -29,6 +30,7 @@
'{{subject}} must be subset of {{superset}}',
'{{subject}} must not be subset of {{superset}}',
)]
+#[Assurance(type: 'array')]
final readonly class Subset implements Validator
{
/** @param mixed[] $superset */
diff --git a/src/Validators/SymbolicLink.php b/src/Validators/SymbolicLink.php
index fc7513260..6b8aa619e 100644
--- a/src/Validators/SymbolicLink.php
+++ b/src/Validators/SymbolicLink.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SplFileInfo;
@@ -27,6 +28,7 @@
'{{subject}} must be an accessible existing symbolic link',
'{{subject}} must not be an accessible existing symbolic link',
)]
+#[Assurance(type: ['string', SplFileInfo::class])]
final class SymbolicLink extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Templated.php b/src/Validators/Templated.php
index 4e02ff847..45fdc4ddd 100644
--- a/src/Validators/Templated.php
+++ b/src/Validators/Templated.php
@@ -12,11 +12,11 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
+#[Composable(without: [All::class, Key::class, Property::class, Not::class, NullOr::class, UndefOr::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final readonly class Templated implements Validator
{
diff --git a/src/Validators/Time.php b/src/Validators/Time.php
index 297c4439a..47799a85c 100644
--- a/src/Validators/Time.php
+++ b/src/Validators/Time.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
@@ -33,6 +34,7 @@
'{{subject}} must be a time in the {{sample}} format',
'{{subject}} must not be a time in the {{sample}} format',
)]
+#[Assurance(type: 'string')]
final readonly class Time implements Validator
{
use CanValidateDateTime;
diff --git a/src/Validators/Tld.php b/src/Validators/Tld.php
index 9221bed8a..af9f52a94 100644
--- a/src/Validators/Tld.php
+++ b/src/Validators/Tld.php
@@ -20,6 +20,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Helpers\DataLoader;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Envelope;
@@ -29,6 +30,7 @@
'{{subject}} must be a top-level domain name',
'{{subject}} must not be a top-level domain name',
)]
+#[Assurance(type: 'string')]
final class Tld extends Envelope
{
public function __construct()
diff --git a/src/Validators/Trimmed.php b/src/Validators/Trimmed.php
index 88588aa1b..d3ef59c5a 100644
--- a/src/Validators/Trimmed.php
+++ b/src/Validators/Trimmed.php
@@ -11,6 +11,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validator;
use Respect\Validation\Validators\Core\Envelope;
@@ -26,6 +27,7 @@
'{{subject}} must contain leading or trailing {{trimValues|list:or}}',
self::TEMPLATE_CUSTOM,
)]
+#[Assurance(type: 'string')]
final class Trimmed extends Envelope
{
public const string TEMPLATE_CUSTOM = '__custom__';
diff --git a/src/Validators/TrueVal.php b/src/Validators/TrueVal.php
index 7d99308f7..2c21fffcc 100644
--- a/src/Validators/TrueVal.php
+++ b/src/Validators/TrueVal.php
@@ -13,6 +13,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -26,6 +27,7 @@
'{{subject}} must evaluate to `true`',
'{{subject}} must not evaluate to `true`',
)]
+#[Assurance(type: 'true')]
final class TrueVal extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Undef.php b/src/Validators/Undef.php
index 2ea85a48a..19905afaa 100644
--- a/src/Validators/Undef.php
+++ b/src/Validators/Undef.php
@@ -16,13 +16,13 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Helpers\CanValidateUndefined;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
-#[Mixin(exclude: ['nullOr', 'undefOr'])]
+#[Composable(without: [NullOr::class, UndefOr::class])]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must be undefined',
diff --git a/src/Validators/UndefOr.php b/src/Validators/UndefOr.php
index 576fd2def..fcffbef11 100644
--- a/src/Validators/UndefOr.php
+++ b/src/Validators/UndefOr.php
@@ -13,7 +13,9 @@
namespace Respect\Validation\Validators;
use Attribute;
-use Respect\Dev\CodeGen\FluentBuilder\Mixin;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceModifier;
+use Respect\Fluent\Attributes\Composable;
use Respect\Validation\Helpers\CanValidateUndefined;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -21,12 +23,16 @@
use function array_map;
-#[Mixin(prefix: 'undefOr', exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
+#[Composable(
+ prefix: self::class,
+ without: [All::class, Key::class, Property::class, Not::class, NullOr::class, self::class],
+)]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'or must be undefined',
'and must not be undefined',
)]
+#[Assurance(modifier: AssuranceModifier::Nullable)]
final readonly class UndefOr implements Validator
{
use CanValidateUndefined;
diff --git a/src/Validators/Unique.php b/src/Validators/Unique.php
index 724539bb5..c31fb7686 100644
--- a/src/Validators/Unique.php
+++ b/src/Validators/Unique.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -29,6 +30,7 @@
'{{subject}} must not contain duplicates',
'{{subject}} must contain duplicates',
)]
+#[Assurance(type: 'array')]
final class Unique extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Uppercase.php b/src/Validators/Uppercase.php
index 5ae7785f0..42bb6a418 100644
--- a/src/Validators/Uppercase.php
+++ b/src/Validators/Uppercase.php
@@ -16,6 +16,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must consist only of uppercase letters',
'{{subject}} must not consist only of uppercase letters',
)]
+#[Assurance(type: 'string')]
final class Uppercase extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Url.php b/src/Validators/Url.php
index 92600a2ab..456d1cdbb 100644
--- a/src/Validators/Url.php
+++ b/src/Validators/Url.php
@@ -12,6 +12,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
@@ -26,6 +27,7 @@
'{{subject}} must be a URL',
'{{subject}} must not be a URL',
)]
+#[Assurance(type: 'string')]
final class Url implements Validator
{
private readonly Validator $validator;
diff --git a/src/Validators/Uuid.php b/src/Validators/Uuid.php
index 654df0a0c..c7d59bb67 100644
--- a/src/Validators/Uuid.php
+++ b/src/Validators/Uuid.php
@@ -22,6 +22,7 @@
use Ramsey\Uuid\Rfc4122\FieldsInterface;
use Ramsey\Uuid\UuidFactory;
use Ramsey\Uuid\UuidInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\ContainerRegistry;
use Respect\Validation\Exceptions\InvalidValidatorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
@@ -43,6 +44,7 @@
'{{subject}} must not be a UUID v{{version|raw}}',
self::TEMPLATE_VERSION,
)]
+#[Assurance(type: 'string')]
final class Uuid implements Validator
{
public const string TEMPLATE_VERSION = '__version__';
diff --git a/src/Validators/Version.php b/src/Validators/Version.php
index 62d311db6..7b2343a6a 100644
--- a/src/Validators/Version.php
+++ b/src/Validators/Version.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
@@ -27,6 +28,7 @@
'{{subject}} must be a version number',
'{{subject}} must not be a version number',
)]
+#[Assurance(type: 'string')]
final class Version extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Vowel.php b/src/Validators/Vowel.php
index 699e9695e..e5e67b66f 100644
--- a/src/Validators/Vowel.php
+++ b/src/Validators/Vowel.php
@@ -17,6 +17,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -33,6 +34,7 @@
'{{subject}} must not consist of vowels or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Vowel extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/src/Validators/When.php b/src/Validators/When.php
index 75cbcd7ef..8f7fa226f 100644
--- a/src/Validators/When.php
+++ b/src/Validators/When.php
@@ -17,10 +17,13 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
+use Respect\Fluent\Attributes\AssuranceCompose;
use Respect\Validation\Result;
use Respect\Validation\Validator;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+#[Assurance(compose: AssuranceCompose::Union, composeRange: [1, null])]
final readonly class When implements Validator
{
public function __construct(
diff --git a/src/Validators/Writable.php b/src/Validators/Writable.php
index 6f33716a0..221c3d0f2 100644
--- a/src/Validators/Writable.php
+++ b/src/Validators/Writable.php
@@ -17,6 +17,7 @@
use Attribute;
use Psr\Http\Message\StreamInterface;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\Simple;
use SplFileInfo;
@@ -29,6 +30,7 @@
'{{subject}} must be an accessible existing writable filesystem entry',
'{{subject}} must not be an accessible existing writable filesystem entry',
)]
+#[Assurance(type: ['string', SplFileInfo::class, StreamInterface::class])]
final class Writable extends Simple
{
public function isValid(mixed $input): bool
diff --git a/src/Validators/Xdigit.php b/src/Validators/Xdigit.php
index 4544c0632..1a654ab73 100644
--- a/src/Validators/Xdigit.php
+++ b/src/Validators/Xdigit.php
@@ -15,6 +15,7 @@
namespace Respect\Validation\Validators;
use Attribute;
+use Respect\Fluent\Attributes\Assurance;
use Respect\Validation\Message\Template;
use Respect\Validation\Validators\Core\FilteredString;
@@ -31,6 +32,7 @@
'{{subject}} must not consist only of hexadecimal digits or {{additionalChars}}',
self::TEMPLATE_EXTRA,
)]
+#[Assurance(type: 'string')]
final class Xdigit extends FilteredString
{
protected function isValid(string $input): bool
diff --git a/tests/benchmark/PrefixBench.php b/tests/benchmark/PrefixBench.php
deleted file mode 100644
index 233660fc4..000000000
--- a/tests/benchmark/PrefixBench.php
+++ /dev/null
@@ -1,42 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation\Benchmarks;
-
-use PhpBench\Attributes as Bench;
-use Respect\Validation\Transformers\Prefix;
-use Respect\Validation\Transformers\ValidatorSpec;
-
-final class PrefixBench
-{
- /** @param array{0: Prefix, 1: ValidatorSpec} $params */
- #[Bench\ParamProviders(['provideTransformerSpec'])]
- #[Bench\Iterations(10)]
- #[Bench\RetryThreshold(5)]
- #[Bench\Revs(100)]
- #[Bench\Warmup(1)]
- #[Bench\Subject]
- public function prefixTransformer(array $params): void
- {
- $params[0]->transform($params[1]);
- }
-
- /** @return array */
- public static function provideTransformerSpec(): array
- {
- return [
- [new Prefix(), new ValidatorSpec('keyName', ['value', 'other'])],
- [new Prefix(), new ValidatorSpec('propertyTitle', ['value', 'other'])],
- [new Prefix(), new ValidatorSpec('notSomething', ['value'])],
- [new Prefix(), new ValidatorSpec('not')],
- [new Prefix(), new ValidatorSpec('arrayVal')],
- ];
- }
-}
diff --git a/tests/inference/NarrowingTest.php b/tests/inference/NarrowingTest.php
new file mode 100644
index 000000000..3fcfb1aa2
--- /dev/null
+++ b/tests/inference/NarrowingTest.php
@@ -0,0 +1,39 @@
+ */
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [
+ __DIR__ . '/../../vendor/respect/fluent-analysis/extension.neon',
+ __DIR__ . '/../../fluent.neon',
+ ];
+ }
+
+ /** @return iterable */
+ public static function dataFileAsserts(): iterable
+ {
+ yield from self::gatherAssertTypes(__DIR__ . '/assertions/narrowing.php');
+ }
+
+ #[Test]
+ #[DataProvider('dataFileAsserts')]
+ public function fileAsserts(string $assertType, string $file, mixed ...$args): void
+ {
+ $this->assertFileAsserts($assertType, $file, ...$args);
+ }
+}
diff --git a/tests/inference/assertions/narrowing.php b/tests/inference/assertions/narrowing.php
new file mode 100644
index 000000000..df2d62468
--- /dev/null
+++ b/tests/inference/assertions/narrowing.php
@@ -0,0 +1,475 @@
+assert($x);
+ assertType('int', $x);
+}
+
+function stringTypeAssert(mixed $x): void
+{
+ v::stringType()->assert($x);
+ assertType('string', $x);
+}
+
+function floatTypeAssert(mixed $x): void
+{
+ v::floatType()->assert($x);
+ assertType('float', $x);
+}
+
+function boolTypeAssert(mixed $x): void
+{
+ v::boolType()->assert($x);
+ assertType('bool', $x);
+}
+
+function nullTypeAssert(mixed $x): void
+{
+ v::nullType()->assert($x);
+ assertType('null', $x);
+}
+
+function arrayTypeAssert(mixed $x): void
+{
+ v::arrayType()->assert($x);
+ assertType('array', $x);
+}
+
+function objectTypeAssert(mixed $x): void
+{
+ v::objectType()->assert($x);
+ assertType('object', $x);
+}
+
+// --- Instance (dynamic) ---
+
+function instanceAssert(mixed $x): void
+{
+ v::instance(DateTimeInterface::class)->assert($x);
+ assertType('DateTimeInterface', $x);
+}
+
+// --- Val validators ---
+
+function intValAssert(mixed $x): void
+{
+ v::intVal()->assert($x);
+ assertType('int|numeric-string', $x);
+}
+
+function numericValAssert(mixed $x): void
+{
+ v::numericVal()->assert($x);
+ assertType('float|int|numeric-string', $x);
+}
+
+function scalarValAssert(mixed $x): void
+{
+ v::scalarVal()->assert($x);
+ assertType('bool|float|int|string', $x);
+}
+
+// --- Composable prefixes ---
+
+function nullOrIntTypeAssert(mixed $x): void
+{
+ v::nullOrIntType()->assert($x);
+ assertType('int|null', $x);
+}
+
+function notIntTypeAssert(int|string $x): void
+{
+ v::notIntType()->assert($x);
+ assertType('string', $x);
+}
+
+// --- Chain intersection ---
+
+function chainIntersection(mixed $x): void
+{
+ v::intType()->positive()->assert($x);
+ assertType('int', $x);
+}
+
+// --- check() works too ---
+
+function checkNarrowing(mixed $x): void
+{
+ v::stringType()->check($x);
+ assertType('string', $x);
+}
+
+// --- isValid() type guard ---
+
+function isValidGuard(mixed $x): void
+{
+ if (!v::intType()->isValid($x)) {
+ return;
+ }
+
+ assertType('int', $x);
+}
+
+function isValidFalsey(int|string $x): void
+{
+ if (v::intType()->isValid($x)) {
+ return;
+ }
+
+ assertType('string', $x);
+}
+
+// --- P0: String format validators ---
+
+function emailAssert(mixed $x): void
+{
+ v::email()->assert($x);
+ assertType('string', $x);
+}
+
+function uuidAssert(mixed $x): void
+{
+ v::uuid()->assert($x);
+ assertType('string', $x);
+}
+
+function urlAssert(mixed $x): void
+{
+ v::url()->assert($x);
+ assertType('string', $x);
+}
+
+function jsonAssert(mixed $x): void
+{
+ v::json()->assert($x);
+ assertType('string', $x);
+}
+
+function alphaAssert(mixed $x): void
+{
+ v::alpha()->assert($x);
+ assertType('string', $x);
+}
+
+function digitAssert(mixed $x): void
+{
+ v::digit()->assert($x);
+ assertType('string', $x);
+}
+
+// --- P0: Filesystem validators ---
+
+function fileAssert(mixed $x): void
+{
+ v::file()->assert($x);
+ assertType('SplFileInfo|string', $x);
+}
+
+function directoryAssert(mixed $x): void
+{
+ v::directory()->assert($x);
+ assertType('SplFileInfo|string', $x);
+}
+
+// --- P0: Array validators ---
+
+function sortedAssert(mixed $x): void
+{
+ v::sorted('ASC')->assert($x);
+ assertType('array|string', $x);
+}
+
+function uniqueAssert(mixed $x): void
+{
+ v::unique()->assert($x);
+ assertType('array', $x);
+}
+
+// --- P0: Numeric validators ---
+
+function multipleAssert(mixed $x): void
+{
+ v::multiple(3)->assert($x);
+ assertType('int', $x);
+}
+
+// --- P0: DateTime ---
+
+function dateTimeAssert(mixed $x): void
+{
+ v::dateTime()->assert($x);
+ assertType('DateTimeInterface|string', $x);
+}
+
+// --- P0: Composable prefix + new narrowing ---
+
+function nullOrEmailAssert(mixed $x): void
+{
+ v::nullOrEmail()->assert($x);
+ assertType('string|null', $x);
+}
+
+// --- P0: Chain with string format ---
+
+function stringTypeEmailChain(mixed $x): void
+{
+ v::stringType()->email()->assert($x);
+ assertType('string', $x);
+}
+
+// --- Identical (value mode) ---
+
+function identicalIntAssert(mixed $x): void
+{
+ v::identical(42)->assert($x);
+ assertType('42', $x);
+}
+
+function identicalStringAssert(mixed $x): void
+{
+ v::identical('foo')->assert($x);
+ assertType("'foo'", $x);
+}
+
+// --- In (member mode) ---
+
+function inArrayAssert(mixed $x): void
+{
+ v::in(['active', 'inactive', 'pending'])->assert($x);
+ assertType("'active'|'inactive'|'pending'", $x);
+}
+
+function inIntArrayAssert(mixed $x): void
+{
+ v::in([1, 2, 3])->assert($x);
+ assertType('1|2|3', $x);
+}
+
+// --- AnyOf (children union) ---
+
+function anyOfAssert(mixed $x): void
+{
+ v::anyOf(v::intType(), v::stringType())->assert($x);
+ assertType('int|string', $x);
+}
+
+// --- AllOf (children intersect) ---
+
+function allOfAssert(mixed $x): void
+{
+ v::allOf(v::intType(), v::positive())->assert($x);
+ assertType('int', $x);
+}
+
+// --- Each (elements) ---
+
+function eachAssert(mixed $x): void
+{
+ v::each(v::intType())->assert($x);
+ assertType('array', $x);
+}
+
+function eachStringAssert(mixed $x): void
+{
+ v::each(v::email())->assert($x);
+ assertType('array', $x);
+}
+
+// --- When (childrenRange) ---
+
+function whenAssert(mixed $x): void
+{
+ v::when(v::intType(), v::intType(), v::stringType())->assert($x);
+ assertType('int|string', $x);
+}
+
+// --- NoneOf (children union + remove) ---
+
+function noneOfAssert(int|string|float $x): void
+{
+ v::noneOf(v::intType(), v::stringType())->assert($x);
+ assertType('float', $x);
+}
+
+// --- Additional type validators ---
+
+function callableTypeAssert(mixed $x): void
+{
+ v::callableType()->assert($x);
+ assertType('callable(): mixed', $x);
+}
+
+function resourceTypeAssert(mixed $x): void
+{
+ v::resourceType()->assert($x);
+ assertType('resource', $x);
+}
+
+function iterableTypeAssert(mixed $x): void
+{
+ v::iterableType()->assert($x);
+ assertType('iterable', $x);
+}
+
+// --- Bool variants ---
+
+function trueValAssert(mixed $x): void
+{
+ v::trueVal()->assert($x);
+ assertType('true', $x);
+}
+
+function falseValAssert(mixed $x): void
+{
+ v::falseVal()->assert($x);
+ assertType('false', $x);
+}
+
+function boolValAssert(mixed $x): void
+{
+ v::boolVal()->assert($x);
+ assertType('bool', $x);
+}
+
+// --- Val variants ---
+
+function floatValAssert(mixed $x): void
+{
+ v::floatVal()->assert($x);
+ assertType('float|int|numeric-string', $x);
+}
+
+function stringValAssert(mixed $x): void
+{
+ v::stringVal()->assert($x);
+ assertType('bool|float|int|string|Stringable', $x);
+}
+
+function arrayValAssert(mixed $x): void
+{
+ v::arrayVal()->assert($x);
+ assertType('array|ArrayAccess', $x);
+}
+
+function iterableValAssert(mixed $x): void
+{
+ v::iterableVal()->assert($x);
+ assertType('array|stdClass|Traversable', $x);
+}
+
+// --- Collection ---
+
+function countableAssert(mixed $x): void
+{
+ v::countable()->assert($x);
+ assertType('array|Countable', $x);
+}
+
+// --- Numeric ---
+
+function positiveAssert(mixed $x): void
+{
+ v::positive()->assert($x);
+ assertType('float|int|numeric-string', $x);
+}
+
+function negativeAssert(mixed $x): void
+{
+ v::negative()->assert($x);
+ assertType('float|int|numeric-string', $x);
+}
+
+function evenAssert(mixed $x): void
+{
+ v::even()->assert($x);
+ assertType('int', $x);
+}
+
+function oddAssert(mixed $x): void
+{
+ v::odd()->assert($x);
+ assertType('int', $x);
+}
+
+function factorAssert(mixed $x): void
+{
+ v::factor(10)->assert($x);
+ assertType('int', $x);
+}
+
+function finiteAssert(mixed $x): void
+{
+ v::finite()->assert($x);
+ assertType('float|int|numeric-string', $x);
+}
+
+function infiniteAssert(mixed $x): void
+{
+ v::infinite()->assert($x);
+ assertType('float|int|numeric-string', $x);
+}
+
+function numberAssert(mixed $x): void
+{
+ v::number()->assert($x);
+ assertType('float|int|numeric-string', $x);
+}
+
+// --- Date/Time ---
+
+function dateAssert(mixed $x): void
+{
+ v::date()->assert($x);
+ assertType('string', $x);
+}
+
+function timeAssert(mixed $x): void
+{
+ v::time()->assert($x);
+ assertType('string', $x);
+}
+
+function leapYearAssert(mixed $x): void
+{
+ v::leapYear()->assert($x);
+ assertType('int|string', $x);
+}
+
+// --- Composites ---
+
+function oneOfAssert(mixed $x): void
+{
+ v::oneOf(v::intType(), v::stringType())->assert($x);
+ assertType('int|string', $x);
+}
+
+function allAssert(mixed $x): void
+{
+ v::all(v::intType())->assert($x);
+ assertType('array', $x);
+}
+
+// --- Modifier: undefOr ---
+
+function undefOrIntTypeAssert(mixed $x): void
+{
+ v::undefOrIntType()->assert($x);
+ assertType('int|null', $x);
+}
diff --git a/tests/src/Transformers/StubTransformer.php b/tests/src/Transformers/StubTransformer.php
deleted file mode 100644
index caea9ec3c..000000000
--- a/tests/src/Transformers/StubTransformer.php
+++ /dev/null
@@ -1,23 +0,0 @@
-
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation\Test\Transformers;
-
-use Respect\Validation\Transformers\Transformer;
-use Respect\Validation\Transformers\ValidatorSpec;
-
-final class StubTransformer implements Transformer
-{
- public function transform(ValidatorSpec $validatorSpec): ValidatorSpec
- {
- return $validatorSpec;
- }
-}
diff --git a/src-dev/CodeGen/CodeGenerator.php b/tests/src/Validators/NonPublic.php
similarity index 57%
rename from src-dev/CodeGen/CodeGenerator.php
rename to tests/src/Validators/NonPublic.php
index 69007f32d..e2ea2fc25 100644
--- a/src-dev/CodeGen/CodeGenerator.php
+++ b/tests/src/Validators/NonPublic.php
@@ -8,10 +8,11 @@
declare(strict_types=1);
-namespace Respect\Dev\CodeGen;
+namespace Respect\Validation\Test\Validators;
-interface CodeGenerator
+final class NonPublic
{
- /** @return array filename => content */
- public function generate(): array;
+ private function __construct()
+ {
+ }
}
diff --git a/tests/unit/ContainerRegistryTest.php b/tests/unit/ContainerRegistryTest.php
index f0d8c6674..1dd9feac3 100644
--- a/tests/unit/ContainerRegistryTest.php
+++ b/tests/unit/ContainerRegistryTest.php
@@ -50,4 +50,21 @@ public function itAllowsOverwritingTheContainer(): void
self::assertSame($newContainer, ContainerRegistry::getContainer());
}
+
+ #[Test]
+ public function extraNamespacesResolveCustomValidators(): void
+ {
+ $mainContainer = ContainerRegistry::getContainer();
+ ContainerRegistry::setContainer(ContainerRegistry::createContainer([
+ 'respect.validation.rule_factory.namespaces' => ['Respect\\Validation\\Test\\Validators'],
+ ]));
+
+ try {
+ // 'CustomRule' exists in Test\Validators but not in Validators
+ $builder = ValidatorBuilder::customRule(); // @phpstan-ignore staticMethod.notFound
+ self::assertCount(1, $builder->getValidators());
+ } finally {
+ ContainerRegistry::setContainer($mainContainer);
+ }
+ }
}
diff --git a/tests/unit/NamespacedRuleFactoryTest.php b/tests/unit/NamespacedRuleFactoryTest.php
deleted file mode 100644
index 3dc428190..000000000
--- a/tests/unit/NamespacedRuleFactoryTest.php
+++ /dev/null
@@ -1,110 +0,0 @@
-
- * SPDX-FileContributor: Augusto Pascutti
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation;
-
-use PHPUnit\Framework\Attributes\CoversClass;
-use PHPUnit\Framework\Attributes\Group;
-use PHPUnit\Framework\Attributes\Test;
-use Respect\Validation\Exceptions\ComponentException;
-use Respect\Validation\Exceptions\InvalidClassException;
-use Respect\Validation\Test\TestCase;
-use Respect\Validation\Test\Transformers\StubTransformer;
-use Respect\Validation\Test\Validators\Invalid;
-use Respect\Validation\Test\Validators\MyAbstractClass;
-use Respect\Validation\Test\Validators\Stub;
-use Respect\Validation\Test\Validators\Valid;
-
-use function assert;
-use function sprintf;
-
-#[Group('core')]
-#[CoversClass(NamespacedValidatorFactory::class)]
-final class NamespacedRuleFactoryTest extends TestCase
-{
- private const string TEST_RULES_NAMESPACE = 'Respect\\Validation\\Test\\Validators';
-
- #[Test]
- public function shouldCreateRuleByNameBasedOnNamespace(): void
- {
- $factory = new NamespacedValidatorFactory(new StubTransformer(), [self::TEST_RULES_NAMESPACE]);
-
- self::assertInstanceOf(Valid::class, $factory->create('valid'));
- }
-
- #[Test]
- public function shouldLookUpToAllNamespacesUntilRuleIsFound(): void
- {
- $factory = (new NamespacedValidatorFactory(new StubTransformer(), [self::TEST_RULES_NAMESPACE]))
- ->withNamespace(__NAMESPACE__);
-
- self::assertInstanceOf(Valid::class, $factory->create('valid'));
- }
-
- #[Test]
- public function shouldDefineConstructorArgumentsWhenCreatingRule(): void
- {
- $constructorArguments = [true, false, true, false];
-
- $factory = new NamespacedValidatorFactory(new StubTransformer(), [self::TEST_RULES_NAMESPACE]);
- $validator = $factory->create('stub', $constructorArguments);
- assert($validator instanceof Stub);
-
- self::assertSame($constructorArguments, $validator->validations);
- }
-
- #[Test]
- public function shouldThrowsAnExceptionOnConstructorReflectionFailure(): void
- {
- $constructorArguments = ['a', 'b'];
-
- $factory = new NamespacedValidatorFactory(new StubTransformer(), [self::TEST_RULES_NAMESPACE]);
-
- $this->expectException(InvalidClassException::class);
- $this->expectExceptionMessage('"noConstructor" could not be instantiated with arguments `["a", "b"]`');
-
- $factory->create('noConstructor', $constructorArguments);
- }
-
- #[Test]
- public function shouldThrowsAnExceptionWhenRuleIsInvalid(): void
- {
- $factory = new NamespacedValidatorFactory(new StubTransformer(), [self::TEST_RULES_NAMESPACE]);
-
- $this->expectException(InvalidClassException::class);
- $this->expectExceptionMessage(sprintf('"%s" must be an instance of "%s"', Invalid::class, Validator::class));
-
- $factory->create('invalid');
- }
-
- #[Test]
- public function shouldThrowsAnExceptionWhenRuleIsNotInstantiable(): void
- {
- $factory = new NamespacedValidatorFactory(new StubTransformer(), [self::TEST_RULES_NAMESPACE]);
-
- $this->expectException(InvalidClassException::class);
- $this->expectExceptionMessage(sprintf('"%s" must be instantiable', MyAbstractClass::class));
-
- $factory->create('myAbstractClass');
- }
-
- #[Test]
- public function shouldThrowsAnExceptionWhenRuleIsNotFound(): void
- {
- $factory = new NamespacedValidatorFactory(new StubTransformer(), [self::TEST_RULES_NAMESPACE]);
-
- $this->expectException(ComponentException::class);
- $this->expectExceptionMessage('"nonExistingRule" is not a valid rule name');
-
- $factory->create('nonExistingRule');
- }
-}
diff --git a/tests/unit/Transformers/PrefixTest.php b/tests/unit/Transformers/PrefixTest.php
deleted file mode 100644
index 9fedb4d58..000000000
--- a/tests/unit/Transformers/PrefixTest.php
+++ /dev/null
@@ -1,102 +0,0 @@
-
- * SPDX-FileContributor: Henrique Moody
- */
-
-declare(strict_types=1);
-
-namespace Respect\Validation\Transformers;
-
-use PHPUnit\Framework\Attributes\CoversClass;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\Group;
-use PHPUnit\Framework\Attributes\Test;
-use Respect\Validation\Test\TestCase;
-
-#[Group('core')]
-#[CoversClass(Prefix::class)]
-final class PrefixTest extends TestCase
-{
- #[Test]
- #[DataProvider('providerForTransformedValidatorSpec')]
- public function itShouldTransformValidatorSpec(ValidatorSpec $original, ValidatorSpec $expected): void
- {
- $transformer = new Prefix();
- $transformed = $transformer->transform($original);
-
- self::assertEquals($expected, $transformed);
- }
-
- #[Test]
- #[DataProvider('providerForUntransformedRuleNames')]
- public function itShouldPreventTransformingCanonicalRule(string $ruleName): void
- {
- $validatorSpec = new ValidatorSpec($ruleName);
-
- $transformer = new Prefix();
- self::assertSame($validatorSpec, $transformer->transform($validatorSpec));
- }
-
- /** @return array */
- public static function providerForTransformedValidatorSpec(): array
- {
- return [
- 'key' => [
- new ValidatorSpec('keyNextRule', ['keyName', 123]),
- new ValidatorSpec('NextRule', [123], new ValidatorSpec('key', ['keyName'])),
- ],
- 'length' => [
- new ValidatorSpec('lengthNextRule', [5]),
- new ValidatorSpec('NextRule', [5], new ValidatorSpec('length')),
- ],
- 'max' => [
- new ValidatorSpec('maxNextRule', [1, 10]),
- new ValidatorSpec('NextRule', [1, 10], new ValidatorSpec('max')),
- ],
- 'min' => [
- new ValidatorSpec('minNextRule', [1, 10]),
- new ValidatorSpec('NextRule', [1, 10], new ValidatorSpec('min')),
- ],
- 'not' => [
- new ValidatorSpec('notNextRule', [1, 10]),
- new ValidatorSpec('NextRule', [1, 10], new ValidatorSpec('not')),
- ],
- 'nullOr' => [
- new ValidatorSpec('nullOrNextRule', [1, 10]),
- new ValidatorSpec('NextRule', [1, 10], new ValidatorSpec('nullOr')),
- ],
- 'property' => [
- new ValidatorSpec('propertyNextRule', ['propertyName', 567]),
- new ValidatorSpec('NextRule', [567], new ValidatorSpec('property', ['propertyName'])),
- ],
- 'undefOr' => [
- new ValidatorSpec('undefOrNextRule', [1, 10]),
- new ValidatorSpec('NextRule', [1, 10], new ValidatorSpec('undefOr')),
- ],
- ];
- }
-
- /** @return array */
- public static function providerForUntransformedRuleNames(): array
- {
- return [
- 'equals' => ['equals'],
- 'key' => ['key'],
- 'keyExists' => ['keyExists'],
- 'keyOptional' => ['keyOptional'],
- 'keySet' => ['keySet'],
- 'length' => ['length'],
- 'max' => ['max'],
- 'min' => ['min'],
- 'not' => ['not'],
- 'undef' => ['undef'],
- 'property' => ['property'],
- 'propertyExists' => ['propertyExists'],
- 'propertyOptional' => ['propertyOptional'],
- ];
- }
-}
diff --git a/tests/unit/ValidatorBuilderTest.php b/tests/unit/ValidatorBuilderTest.php
index 3f152a573..d17642362 100644
--- a/tests/unit/ValidatorBuilderTest.php
+++ b/tests/unit/ValidatorBuilderTest.php
@@ -17,6 +17,7 @@
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
use PHPUnit\Framework\Attributes\Test;
+use Respect\Fluent\Exceptions\CouldNotResolve;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Test\TestCase;
@@ -31,7 +32,7 @@ final class ValidatorBuilderTest extends TestCase
#[Test]
public function invalidRuleClassShouldThrowComponentException(): void
{
- $this->expectException(ComponentException::class);
+ $this->expectException(CouldNotResolve::class);
// @phpstan-ignore-next-line
ValidatorBuilder::iDoNotExistSoIShouldThrowException();
@@ -42,7 +43,6 @@ public function shouldReturnValidatorInstanceWhenTheNotRuleIsCalledWithArguments
{
$validator = ValidatorBuilder::init();
- // @phpstan-ignore-next-line
self::assertNotSame($validator, $validator->not($validator->falsy()));
}