diff --git a/tools/.phpstan/composer.json b/tools/.phpstan/composer.json
index 6448ba0..a4ed808 100644
--- a/tools/.phpstan/composer.json
+++ b/tools/.phpstan/composer.json
@@ -3,7 +3,7 @@
"phpstan/phpstan": "^2.2.7",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan-strict-rules": "^2.0.12",
- "tomasvotruba/type-coverage": "^2.2.2",
+ "tomasvotruba/type-coverage": "^2.3.0",
"ergebnis/phpstan-rules": "^2.13.1"
},
"config": {
diff --git a/tools/.phpstan/composer.lock b/tools/.phpstan/composer.lock
index dd527c9..44c207a 100644
--- a/tools/.phpstan/composer.lock
+++ b/tools/.phpstan/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f960036a066807c312a2d48672c1e045",
+ "content-hash": "3579343022b2a28c7195fe30b1752bfb",
"packages": [],
"packages-dev": [
{
@@ -246,29 +246,31 @@
},
{
"name": "tomasvotruba/type-coverage",
- "version": "2.2.2",
+ "version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/TomasVotruba/type-coverage.git",
- "reference": "25f298265c823e8fb0505169135855e1e6a91021"
+ "reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/25f298265c823e8fb0505169135855e1e6a91021",
- "reference": "25f298265c823e8fb0505169135855e1e6a91021",
+ "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
+ "reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
"shasum": ""
},
"require": {
"php": "^8.4",
- "phpstan/phpstan": "^2.2"
+ "phpstan/phpstan": "^2.2",
+ "webmozart/assert": "^1.11 || ^2.1"
},
"require-dev": {
"phpstan/extension-installer": "^1.4",
- "phpunit/phpunit": "^12.5",
+ "phpunit/phpunit": "^13.2",
"rector/jack": "^1.0",
- "rector/rector": "^2.4",
+ "rector/rector": "^2.5",
"shipmonk/composer-dependency-analyser": "^1.8",
- "symplify/easy-coding-standard": "^13.1",
+ "symfony/dom-crawler": "^8.1",
+ "symplify/easy-coding-standard": "^13.2",
"tomasvotruba/unused-public": "^2.2",
"tracy/tracy": "^2.12"
},
@@ -276,12 +278,14 @@
"extra": {
"phpstan": {
"includes": [
- "config/extension.neon"
+ "config/extension.neon",
+ "packages/type-perfect/config/extension.neon"
]
}
},
"autoload": {
"psr-4": {
+ "Rector\\TypePerfect\\": "packages/type-perfect/src",
"TomasVotruba\\TypeCoverage\\": "src"
}
},
@@ -296,7 +300,7 @@
],
"support": {
"issues": "https://github.com/TomasVotruba/type-coverage/issues",
- "source": "https://github.com/TomasVotruba/type-coverage/tree/2.2.2"
+ "source": "https://github.com/TomasVotruba/type-coverage/tree/2.3.0"
},
"funding": [
{
@@ -308,7 +312,73 @@
"type": "github"
}
],
- "time": "2026-06-07T12:35:00+00:00"
+ "time": "2026-07-29T20:32:37+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "2.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-date": "*",
+ "ext-filter": "*",
+ "php": "^8.2"
+ },
+ "suggest": {
+ "ext-intl": "",
+ "ext-simplexml": "",
+ "ext-spl": ""
+ },
+ "type": "library",
+ "extra": {
+ "psalm": {
+ "pluginClass": "Webmozart\\Assert\\PsalmPlugin"
+ },
+ "branch-alias": {
+ "dev-master": "2.0-dev",
+ "dev-feature/2-0": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ },
+ {
+ "name": "Woody Gilk",
+ "email": "woody.gilk@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "support": {
+ "issues": "https://github.com/webmozarts/assert/issues",
+ "source": "https://github.com/webmozarts/assert/tree/2.4.1"
+ },
+ "time": "2026-06-15T15:31:57+00:00"
}
],
"aliases": [],
diff --git a/tools/.phpstan/vendor/composer/installed.json b/tools/.phpstan/vendor/composer/installed.json
index aeb0329..24df1c3 100644
--- a/tools/.phpstan/vendor/composer/installed.json
+++ b/tools/.phpstan/vendor/composer/installed.json
@@ -251,45 +251,49 @@
},
{
"name": "tomasvotruba/type-coverage",
- "version": "2.2.2",
- "version_normalized": "2.2.2.0",
+ "version": "2.3.0",
+ "version_normalized": "2.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/TomasVotruba/type-coverage.git",
- "reference": "25f298265c823e8fb0505169135855e1e6a91021"
+ "reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/25f298265c823e8fb0505169135855e1e6a91021",
- "reference": "25f298265c823e8fb0505169135855e1e6a91021",
+ "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
+ "reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
"shasum": ""
},
"require": {
"php": "^8.4",
- "phpstan/phpstan": "^2.2"
+ "phpstan/phpstan": "^2.2",
+ "webmozart/assert": "^1.11 || ^2.1"
},
"require-dev": {
"phpstan/extension-installer": "^1.4",
- "phpunit/phpunit": "^12.5",
+ "phpunit/phpunit": "^13.2",
"rector/jack": "^1.0",
- "rector/rector": "^2.4",
+ "rector/rector": "^2.5",
"shipmonk/composer-dependency-analyser": "^1.8",
- "symplify/easy-coding-standard": "^13.1",
+ "symfony/dom-crawler": "^8.1",
+ "symplify/easy-coding-standard": "^13.2",
"tomasvotruba/unused-public": "^2.2",
"tracy/tracy": "^2.12"
},
- "time": "2026-06-07T12:35:00+00:00",
+ "time": "2026-07-29T20:32:37+00:00",
"type": "phpstan-extension",
"extra": {
"phpstan": {
"includes": [
- "config/extension.neon"
+ "config/extension.neon",
+ "packages/type-perfect/config/extension.neon"
]
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
+ "Rector\\TypePerfect\\": "packages/type-perfect/src",
"TomasVotruba\\TypeCoverage\\": "src"
}
},
@@ -304,7 +308,7 @@
],
"support": {
"issues": "https://github.com/TomasVotruba/type-coverage/issues",
- "source": "https://github.com/TomasVotruba/type-coverage/tree/2.2.2"
+ "source": "https://github.com/TomasVotruba/type-coverage/tree/2.3.0"
},
"funding": [
{
@@ -317,6 +321,75 @@
}
],
"install-path": "../tomasvotruba/type-coverage"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "2.4.1",
+ "version_normalized": "2.4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-date": "*",
+ "ext-filter": "*",
+ "php": "^8.2"
+ },
+ "suggest": {
+ "ext-intl": "",
+ "ext-simplexml": "",
+ "ext-spl": ""
+ },
+ "time": "2026-06-15T15:31:57+00:00",
+ "type": "library",
+ "extra": {
+ "psalm": {
+ "pluginClass": "Webmozart\\Assert\\PsalmPlugin"
+ },
+ "branch-alias": {
+ "dev-master": "2.0-dev",
+ "dev-feature/2-0": "2.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ },
+ {
+ "name": "Woody Gilk",
+ "email": "woody.gilk@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "support": {
+ "issues": "https://github.com/webmozarts/assert/issues",
+ "source": "https://github.com/webmozarts/assert/tree/2.4.1"
+ },
+ "install-path": "../webmozart/assert"
}
],
"dev": true,
@@ -325,6 +398,7 @@
"phpstan/extension-installer",
"phpstan/phpstan",
"phpstan/phpstan-strict-rules",
- "tomasvotruba/type-coverage"
+ "tomasvotruba/type-coverage",
+ "webmozart/assert"
]
}
diff --git a/tools/.phpstan/vendor/composer/installed.php b/tools/.phpstan/vendor/composer/installed.php
index 1d9c8bc..d32e9cf 100644
--- a/tools/.phpstan/vendor/composer/installed.php
+++ b/tools/.phpstan/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => '7.0.x-dev',
'version' => '7.0.9999999.9999999-dev',
- 'reference' => '0107b5050a17e24a8d66433ce851e34c2144dc30',
+ 'reference' => '724ed522d74918b0d84a50cdee4d0d2f7a5ba67c',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => '7.0.x-dev',
'version' => '7.0.9999999.9999999-dev',
- 'reference' => '0107b5050a17e24a8d66433ce851e34c2144dc30',
+ 'reference' => '724ed522d74918b0d84a50cdee4d0d2f7a5ba67c',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -56,13 +56,22 @@
'dev_requirement' => true,
),
'tomasvotruba/type-coverage' => array(
- 'pretty_version' => '2.2.2',
- 'version' => '2.2.2.0',
- 'reference' => '25f298265c823e8fb0505169135855e1e6a91021',
+ 'pretty_version' => '2.3.0',
+ 'version' => '2.3.0.0',
+ 'reference' => '7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644',
'type' => 'phpstan-extension',
'install_path' => __DIR__ . '/../tomasvotruba/type-coverage',
'aliases' => array(),
'dev_requirement' => true,
),
+ 'webmozart/assert' => array(
+ 'pretty_version' => '2.4.1',
+ 'version' => '2.4.1.0',
+ 'reference' => '2ccb7c2e821038c03a3e6e1700c570c158c55f70',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../webmozart/assert',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
),
);
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/README.md b/tools/.phpstan/vendor/tomasvotruba/type-coverage/README.md
index cb7e71f..4abb940 100644
--- a/tools/.phpstan/vendor/tomasvotruba/type-coverage/README.md
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/README.md
@@ -138,3 +138,406 @@ vendor/bin/phpstan
Happy coding!
+
+
+
+---
+
+# Type Perfect
+
+[](https://packagist.org/packages/rector/type-perfect/stats)
+
+Next level type declaration check PHPStan rules.
+
+We use these sets to improve code quality of our clients' code beyond PHPStan features.
+
+* These rules make skipped object types explicit, param types narrow and help you to fill more accurate object type hints.
+* **They're easy to enable, even if your code does not pass level 0**
+* They're effortless to resolve and make your code instantly more solid and reliable.
+
+If you care about code quality and type safety, add these 10 rules to your CI.
+
+
+
+These rules ship with the package [installed above](#install), there is nothing else to require.
+
+*Migrating from `rector/type-perfect`? Remove it from your `composer.json`, the rules live here now.*
+
+
+
+Every rule is opt-in and disabled by default, so you can pick the ones that fit your project. See [Configure](#configure) below for the full list.
+
+The 3 checks below are the simplest ones to start with:
+
+```yaml
+parameters:
+ type_perfect:
+ no_isset_on_object: true
+ no_empty_on_object: true
+ no_array_access_on_object: true
+ no_param_type_removal: true
+```
+
+The first one makes sure we don't miss a chance to use `instanceof` to make further code know about exact object type:
+
+```php
+private ?SomeType $someType = null;
+
+if (! empty($this->someType)) {
+ // ...
+}
+
+if (! isset($this->someType)) {
+ // ...
+}
+
+// here we only know, that $this->someType is not empty/null
+```
+
+:no_good:
+
+↓
+
+
+```php
+if (! $this->someType instanceof SomeType) {
+ return;
+}
+
+// here we know $this->someType is exactly SomeType
+```
+
+:heavy_check_mark:
+
+
+
+Second rule (`no_array_access_on_object`) checks we use explicit object methods over magic array access:
+
+```php
+$article = new Article();
+
+$id = $article['id'];
+// we have no idea, what the type is
+```
+
+:no_good:
+
+↓
+
+```php
+$id = $article->getId();
+// we know the type is int
+```
+
+:heavy_check_mark:
+
+
+
+Last rule (`no_param_type_removal`) checks that all interface implementations follow the same method signature as the interface:
+
+```php
+interface SomeInterface
+{
+ public function doSomething(int $value): void;
+}
+
+final class SomeClass implements SomeInterface
+{
+ public function doSomething($value): void { // ... }
+}
+```
+
+:no_good:
+
+↓
+
+```php
+final class SomeClass implements SomeInterface
+{
+ public function doSomething(int $value): void { // ... }
+}
+```
+
+:heavy_check_mark:
+
+
+
+## Configure
+
+All rules are enabled by configuration and disabled by default. We take them from the simplest to more powerful, in the same order we apply them on legacy projects.
+
+You can enable them all at once:
+
+```yaml
+parameters:
+ type_perfect:
+ # the 3 checks above
+ no_isset_on_object: true
+ no_empty_on_object: true
+ no_array_access_on_object: true
+ no_param_type_removal: true
+
+ no_mixed_property: true
+ no_mixed_caller: true
+ null_over_false: true
+ narrow_param: true
+ narrow_return: true
+```
+
+Or one by one:
+
+
+
+## 1. Null over False
+
+```yaml
+parameters:
+ type_perfect:
+ null_over_false: true
+```
+
+Bool types are typically used for on/off, yes/no responses. But sometimes, the `false` is misused as *no-result* response, where `null` would be more accurate:
+
+```php
+public function getProduct()
+{
+ if (...) {
+ return $product;
+ }
+
+ return false;
+}
+```
+
+:no_good:
+
+↓
+
+We should use `null` instead, as it enabled strict type declaration in form of `?Product` since PHP 7.1:
+
+```php
+public function getProduct(): ?Product
+{
+ if (...) {
+ return $product;
+ }
+
+ return null;
+}
+```
+
+:heavy_check_mark:
+
+
+
+## 2. No mixed Property
+
+```yaml
+parameters:
+ type_perfect:
+ no_mixed_property: true
+```
+
+This rule focuses on PHPStan blind spot while fetching a property. If we have a property with unknown type, PHPStan is not be able to analyse it. It silently ignores it.
+
+```php
+private $someType;
+
+public function run()
+{
+ $this->someType->vale;
+}
+```
+
+It doesn't see there is a typo in `vale` property name. It should be `value`
+
+:no_good:
+
+↓
+
+
+```php
+private SomeType $someType;
+
+public function run()
+{
+ $this->someType->value;
+}
+```
+
+This rule makes sure all property fetches know their type they're called on.
+
+:heavy_check_mark:
+
+
+
+## 3. No mixed Caller
+
+```yaml
+parameters:
+ type_perfect:
+ no_mixed_caller: true
+```
+
+Same as above, only for method calls:
+
+```php
+private $someType;
+
+public function run()
+{
+ $this->someType->someMetho(1, 2);
+}
+```
+
+It doesn't see there is a typo in `someMetho` name, and that the 2nd parameter must be `string`.
+
+:no_good:
+
+↓
+
+
+```php
+private SomeType $someType;
+
+public function run()
+{
+ $this->someType->someMethod(1, 'active');
+}
+```
+
+This group makes sure methods call know their type they're called on.
+
+:heavy_check_mark:
+
+
+
+## 4. Narrow Param Types
+
+The more narrow param type we have, the reliable the code is. `string` beats `mixed`, `int` beats `scalar` and `ExactObject` beats `stdClass`.
+
+```yaml
+parameters:
+ type_perfect:
+ narrow_param: true
+```
+
+In case of `private`, but also `public` method calls, our project often knows exact types that are passed in it:
+
+```php
+// in one file
+$product->addPrice(100.52);
+
+// another file
+$product->addPrice(52.05);
+```
+
+But out of from fear and "just to be safe", we keep the `addPrice()` param type empty, `mixed` or in a docblock.
+
+:no_good:
+
+↓
+
+If, in 100 % cases the `float` type is passed, PHPStan knows it can be added and improve further analysis:
+
+```diff
+
+-/**
+- * @param float $price
+- */
+-public function addPrice($price)
++public function addPrice(float $price)
+{
+ $this->price = $price;
+}
+```
+
+That's where this group comes in. It checks all the passed types, and tells us know how to narrow the param type declaration.
+
+:heavy_check_mark:
+
+
+
+## 5. Narrow Return Types
+
+Last but not least, the more narrow return type, the more reliable the code.
+
+```yaml
+parameters:
+ type_perfect:
+ narrow_return: true
+```
+
+Where does it help? Let's say we have 2 types of talks, that do have different behavior:
+
+```php
+final class ConferenceTalk extends Talk
+{
+ public function bookHotel()
+ {
+ // ...
+ }
+}
+
+final class MeetupTalk extends Talk
+{
+ public function bookTrain()
+ {
+ // ...
+ }
+}
+```
+
+Then we have a factory (repository, or services) that returns generic `Talk` type:
+
+```php
+final class TalkFactory
+{
+ public function createConferenceTalk(): Talk
+ {
+ return new ConferenceTalk();
+ }
+
+ public function createMeetupTalk(): Talk
+ {
+ return new MeetupTalk();
+ }
+}
+```
+
+In this case we've just lost strict type and have to verify the type on runtime:
+
+```php
+$talk instanceof ConferenceTalk
+```
+
+:no_good:
+
+↓
+
+That's where this group comes in. In case we return the exact type, we should use exact type in return type declaration to keep the code as reliable as possible:
+
+```diff
+ final class TalkFactory
+ {
+- public function createConferenceTalk(): Talk
++ public function createConferenceTalk(): ConferenceTalk
+ {
+ return new ConferenceTalk();
+ }
+
+- public function createMeetupTalk(): Talk
++ public function createMeetupTalk(): MeetupTalk
+ {
+ return new MeetupTalk();
+ }
+}
+```
+
+:heavy_check_mark:
+
+Add sets one by one, fix what you find helpful and ignore the rest.
+
+
+
+Happy coding!
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer-dependency-analyser.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer-dependency-analyser.php
index dfa20ef..553c5bf 100644
--- a/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer-dependency-analyser.php
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer-dependency-analyser.php
@@ -7,4 +7,9 @@ use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
return new Configuration()
// PhpParser classes are provided by phpstan/phpstan, no need to require nikic/php-parser directly
- ->ignoreErrorsOnPackage('nikic/php-parser', [ErrorType::SHADOW_DEPENDENCY]);
+ ->ignoreErrorsOnPackage('nikic/php-parser', [ErrorType::SHADOW_DEPENDENCY])
+ // type-perfect test fixtures are static analysis inputs, not real code, and reference 3rd-party classes on purpose
+ ->ignoreErrorsOnPath(
+ __DIR__ . '/packages/type-perfect/tests',
+ [ErrorType::UNKNOWN_CLASS, ErrorType::SHADOW_DEPENDENCY]
+ );
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json
index d3493db..e706ef4 100644
--- a/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json
@@ -6,26 +6,30 @@
"keywords": ["static analysis", "phpstan-extension"],
"require": {
"php": "^8.4",
- "phpstan/phpstan": "^2.2"
+ "phpstan/phpstan": "^2.2",
+ "webmozart/assert": "^1.11 || ^2.1"
},
"require-dev": {
"phpstan/extension-installer": "^1.4",
- "phpunit/phpunit": "^12.5",
- "symplify/easy-coding-standard": "^13.1",
- "rector/rector": "^2.4",
+ "phpunit/phpunit": "^13.2",
+ "symplify/easy-coding-standard": "^13.2",
+ "rector/rector": "^2.5",
"tracy/tracy": "^2.12",
"tomasvotruba/unused-public": "^2.2",
"rector/jack": "^1.0",
- "shipmonk/composer-dependency-analyser": "^1.8"
+ "shipmonk/composer-dependency-analyser": "^1.8",
+ "symfony/dom-crawler": "^8.1"
},
"autoload": {
"psr-4": {
- "TomasVotruba\\TypeCoverage\\": "src"
+ "TomasVotruba\\TypeCoverage\\": "src",
+ "Rector\\TypePerfect\\": "packages/type-perfect/src"
}
},
"autoload-dev": {
"psr-4": {
- "TomasVotruba\\TypeCoverage\\Tests\\": "tests"
+ "TomasVotruba\\TypeCoverage\\Tests\\": "tests",
+ "Rector\\TypePerfect\\Tests\\": "packages/type-perfect/tests"
}
},
"scripts": {
@@ -43,7 +47,8 @@
"extra": {
"phpstan": {
"includes": [
- "config/extension.neon"
+ "config/extension.neon",
+ "packages/type-perfect/config/extension.neon"
]
}
}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/LICENSE b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/LICENSE
new file mode 100644
index 0000000..2dbf78e
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/LICENSE
@@ -0,0 +1,25 @@
+The MIT License
+---------------
+
+Copyright (c) 2020 Tomas Votruba (https://tomasvotruba.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/config/extension.neon b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/config/extension.neon
new file mode 100644
index 0000000..e590d9b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/config/extension.neon
@@ -0,0 +1,94 @@
+parametersSchema:
+ # see https://doc.nette.org/en/schema for configuration
+ type_perfect: structure([
+ narrow_param: bool()
+ narrow_return: bool()
+ no_mixed: bool()
+ null_over_false: bool()
+
+ # replace for no_mixed
+ no_mixed_property: bool()
+ no_mixed_caller: bool()
+
+ no_param_type_removal: bool()
+ no_array_access_on_object: bool()
+ no_isset_on_object: bool()
+ no_empty_on_object: bool()
+ ])
+
+# defaults
+parameters:
+ type_perfect:
+ narrow_param: false
+ narrow_return: false
+ no_mixed: false
+ null_over_false: false
+ # priority override of no_mixed
+ no_mixed_property: false
+ no_mixed_caller: false
+
+ no_param_type_removal: false
+ no_array_access_on_object: false
+ no_isset_on_object: false
+ no_empty_on_object: false
+
+rules:
+ # turn on by the group name
+
+ # no_param_type_removal
+ - Rector\TypePerfect\Rules\NoParamTypeRemovalRule
+
+ # no_array_access_on_object
+ - Rector\TypePerfect\Rules\NoArrayAccessOnObjectRule
+
+ # no_isset_on_object
+ - Rector\TypePerfect\Rules\NoIssetOnObjectRule
+
+ # no_empty_on_object
+ - Rector\TypePerfect\Rules\NoEmptyOnObjectRule
+
+ # narrow_param
+ - Rector\TypePerfect\Rules\NarrowPublicClassMethodParamTypeRule
+ - Rector\TypePerfect\Rules\NarrowPrivateClassMethodParamTypeRule
+
+ # narrow_return
+ - Rector\TypePerfect\Rules\NarrowReturnObjectTypeRule
+
+ # no_mixed
+ - Rector\TypePerfect\Rules\NoMixedPropertyFetcherRule
+ - Rector\TypePerfect\Rules\NoMixedMethodCallerRule
+
+ # null_over_false
+ - Rector\TypePerfect\Rules\ReturnNullOverFalseRule
+
+services:
+ -
+ factory: Rector\TypePerfect\Configuration
+ arguments:
+ - %type_perfect%
+
+ - Rector\TypePerfect\NodeFinder\ClassMethodNodeFinder
+ - Rector\TypePerfect\NodeFinder\MethodCallNodeFinder
+ - Rector\TypePerfect\NodeFinder\ReturnNodeFinder
+ - Rector\TypePerfect\PhpDoc\ApiDocStmtAnalyzer
+ - Rector\TypePerfect\Printer\NodeComparator
+ - Rector\TypePerfect\Reflection\ReflectionParser
+ - Rector\TypePerfect\Reflection\MethodNodeAnalyser
+ - Rector\TypePerfect\Matcher\Collector\PublicClassMethodMatcher
+ - Rector\TypePerfect\Matcher\ClassMethodCallReferenceResolver
+ - Rector\TypePerfect\Printer\CollectorMetadataPrinter
+ - Rector\TypePerfect\Guard\EmptyIssetGuard
+
+ # for NarrowPublicClassMethodParamTypeByCallerTypeRule
+ -
+ class: Rector\TypePerfect\Collector\ClassMethod\PublicClassMethodParamTypesCollector
+ tags: [phpstan.collector]
+
+ -
+ class: Rector\TypePerfect\Collector\MethodCall\MethodCallArgTypesCollector
+ tags: [phpstan.collector]
+
+ -
+ class: Rector\TypePerfect\Collector\MethodCallableNode\MethodCallableCollector
+ tags:
+ - phpstan.collector
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/ClassMethod/PublicClassMethodParamTypesCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/ClassMethod/PublicClassMethodParamTypesCollector.php
new file mode 100644
index 0000000..8f7390b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/ClassMethod/PublicClassMethodParamTypesCollector.php
@@ -0,0 +1,81 @@
+
+ */
+final readonly class PublicClassMethodParamTypesCollector implements Collector
+{
+ public function __construct(
+ private ApiDocStmtAnalyzer $apiDocStmtAnalyzer,
+ private PublicClassMethodMatcher $publicClassMethodMatcher,
+ private CollectorMetadataPrinter $collectorMetadataPrinter,
+ private Configuration $configuration
+ ) {
+ }
+
+ public function getNodeType(): string
+ {
+ return ClassMethod::class;
+ }
+
+ /**
+ * @param ClassMethod $node
+ * @return array{class-string, string, string, int}|null
+ */
+ public function processNode(Node $node, Scope $scope): ?array
+ {
+ if (! $this->configuration->isNarrowParamEnabled()) {
+ return null;
+ }
+
+ if ($node->params === []) {
+ return null;
+ }
+
+ if ($this->publicClassMethodMatcher->shouldSkipClassMethod($node)) {
+ return null;
+ }
+
+ // only if the class has no parents/implementers, to avoid class method required by contracts
+ $classReflection = $scope->getClassReflection();
+ if (! $classReflection instanceof ClassReflection) {
+ return null;
+ }
+
+ if ($this->apiDocStmtAnalyzer->hasApiDoc($node, $classReflection)) {
+ return null;
+ }
+
+ if ($this->publicClassMethodMatcher->shouldSkipClassReflection($classReflection)) {
+ return null;
+ }
+
+ $methodName = $node->name->toString();
+
+ // is this method required by parent contract? skip it
+ if ($this->publicClassMethodMatcher->isUsedByParentClassOrInterface($classReflection, $methodName)) {
+ return null;
+ }
+
+ $printedParamTypesString = $this->collectorMetadataPrinter->printParamTypesToString(
+ $node,
+ $classReflection,
+ $scope
+ );
+ return [$classReflection->getName(), $methodName, $printedParamTypesString, $node->getLine()];
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/MethodCall/MethodCallArgTypesCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/MethodCall/MethodCallArgTypesCollector.php
new file mode 100644
index 0000000..341d6bc
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/MethodCall/MethodCallArgTypesCollector.php
@@ -0,0 +1,77 @@
+
+ */
+final readonly class MethodCallArgTypesCollector implements Collector
+{
+ public function __construct(
+ private ClassMethodCallReferenceResolver $classMethodCallReferenceResolver,
+ private CollectorMetadataPrinter $collectorMetadataPrinter,
+ private Configuration $configuration
+ ) {
+ }
+
+ public function getNodeType(): string
+ {
+ return MethodCall::class;
+ }
+
+ /**
+ * @param MethodCall $node
+ * @return array{string, string}|null
+ */
+ public function processNode(Node $node, Scope $scope): ?array
+ {
+ if (! $this->configuration->isNarrowParamEnabled()) {
+ return null;
+ }
+
+ if ($node->isFirstClassCallable() || $node->getArgs() === [] || ! $node->name instanceof Identifier) {
+ return null;
+ }
+
+ $methodCalledOnType = $scope->getType($node->var);
+ $methodReflection = $scope->getMethodReflection($methodCalledOnType, $node->name->name);
+ if (! $methodReflection instanceof ExtendedMethodReflection) {
+ return null;
+ }
+
+ $classMethodCallReference = $this->classMethodCallReferenceResolver->resolve($node, $scope, true);
+ if (! $classMethodCallReference instanceof MethodCallReference) {
+ return null;
+ }
+
+ $classMethodReference = $this->createClassMethodReference($classMethodCallReference);
+
+ $stringArgTypesString = $this->collectorMetadataPrinter->printArgTypesAsString(
+ $node,
+ $methodReflection,
+ $scope
+ );
+ return [$classMethodReference, $stringArgTypesString];
+ }
+
+ private function createClassMethodReference(MethodCallReference $classMethodCallReference): string
+ {
+ $className = $classMethodCallReference->getClass();
+ $methodName = $classMethodCallReference->getMethod();
+
+ return $className . '::' . $methodName;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/MethodCallableNode/MethodCallableCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/MethodCallableNode/MethodCallableCollector.php
new file mode 100644
index 0000000..0d52c65
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Collector/MethodCallableNode/MethodCallableCollector.php
@@ -0,0 +1,63 @@
+|null>
+ *
+ * PHPStan has special node for first class callables of MethodCall
+ *
+ * @see https://github.com/phpstan/phpstan-src/blob/511c1e435fb43b8eb0ac310e6aa3230147963790/src/Analyser/NodeScopeResolver.php#L1936
+ */
+final readonly class MethodCallableCollector implements Collector
+{
+ public function __construct(
+ private ClassMethodCallReferenceResolver $classMethodCallReferenceResolver,
+ private Configuration $configuration
+ ) {
+ }
+
+ public function getNodeType(): string
+ {
+ return MethodCallableNode::class;
+ }
+
+ /**
+ * @param MethodCallableNode $node
+ * @return array{string}|null
+ */
+ public function processNode(Node $node, Scope $scope): ?array
+ {
+ if (! $this->configuration->isNarrowParamEnabled()) {
+ return null;
+ }
+
+ $classMethodCallReference = $this->classMethodCallReferenceResolver->resolve($node, $scope, true);
+ if (! $classMethodCallReference instanceof MethodCallReference) {
+ return null;
+ }
+
+ $classMethodReference = $this->createClassMethodReference($classMethodCallReference);
+
+ // special case that should skip everything
+ return [$classMethodReference];
+ }
+
+ private function createClassMethodReference(MethodCallReference $classMethodCallReference): string
+ {
+ $className = $classMethodCallReference->getClass();
+ $methodName = $classMethodCallReference->getMethod();
+
+ return $className . '::' . $methodName;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Configuration.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Configuration.php
new file mode 100644
index 0000000..c944604
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Configuration.php
@@ -0,0 +1,80 @@
+ $parameters
+ */
+ public function __construct(
+ private array $parameters
+ ) {
+ // enabed by default in tests
+ if (defined('PHPUNIT_COMPOSER_INSTALL')) {
+ $this->parameters['narrow_param'] = true;
+ $this->parameters['narrow_return'] = true;
+ $this->parameters['no_mixed'] = true;
+ $this->parameters['null_over_false'] = true;
+ $this->parameters['no_param_type_removal'] = true;
+ $this->parameters['no_array_access_on_object'] = true;
+ $this->parameters['no_isset_on_object'] = true;
+ $this->parameters['no_empty_on_object'] = true;
+ }
+ }
+
+ public function isNarrowParamEnabled(): bool
+ {
+ return $this->parameters['narrow_param'] ?? false;
+ }
+
+ public function isNarrowReturnEnabled(): bool
+ {
+ return $this->parameters['narrow_return'] ?? false;
+ }
+
+ public function isNoMixedPropertyEnabled(): bool
+ {
+ if ($this->parameters['no_mixed_property']) {
+ return true;
+ }
+
+ return $this->parameters['no_mixed'] ?? false;
+ }
+
+ public function isNoMixedCallerEnabled(): bool
+ {
+ if ($this->parameters['no_mixed_caller']) {
+ return true;
+ }
+
+ return $this->parameters['no_mixed'] ?? false;
+ }
+
+ public function isNoFalsyReturnEnabled(): bool
+ {
+ return $this->parameters['null_over_false'] ?? false;
+ }
+
+ public function isNoParamTypeRemovalEnabled(): bool
+ {
+ return $this->parameters['no_param_type_removal'] ?? false;
+ }
+
+ public function isNoArrayAccessOnObjectEnabled(): bool
+ {
+ return $this->parameters['no_array_access_on_object'] ?? false;
+ }
+
+ public function isNoIssetOnObjectEnabled(): bool
+ {
+ return $this->parameters['no_isset_on_object'] ?? false;
+ }
+
+ public function isNoEmptyOnObjectEnabled(): bool
+ {
+ return $this->parameters['no_empty_on_object'] ?? false;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Enum/Types/ResolvedTypes.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Enum/Types/ResolvedTypes.php
new file mode 100644
index 0000000..2680c43
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Enum/Types/ResolvedTypes.php
@@ -0,0 +1,10 @@
+name instanceof Expr) {
+ return true;
+ }
+
+ if (! $scope->hasVariableType($expr->name)->yes()) {
+ return true;
+ }
+
+ $varType = $scope->getNativeType($expr);
+ $varType = TypeCombinator::removeNull($varType);
+ return $varType->getObjectClassNames() === [];
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Matcher/ClassMethodCallReferenceResolver.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Matcher/ClassMethodCallReferenceResolver.php
new file mode 100644
index 0000000..efbdfe8
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Matcher/ClassMethodCallReferenceResolver.php
@@ -0,0 +1,55 @@
+getName();
+ $variable = $methodCallOrMethodCallable->getVar();
+ } else {
+ $methodName = $methodCallOrMethodCallable->name;
+ $variable = $methodCallOrMethodCallable->var;
+ }
+
+ if ($methodName instanceof Expr) {
+ return null;
+ }
+
+ $callerType = $scope->getType($variable);
+
+ // remove optional nullable type
+ if (TypeCombinator::containsNull($callerType)) {
+ $callerType = TypeCombinator::removeNull($callerType);
+ }
+
+ if (! $allowThisType && $callerType instanceof ThisType) {
+ return null;
+ }
+
+ if (count($callerType->getObjectClassNames()) !== 1) {
+ return null;
+ }
+
+ // move to the class where method is defined, e.g. parent class defines the method, so it should be checked there
+ $className = $callerType->getObjectClassNames()[0];
+ $methodNameString = $methodName->toString();
+
+ return new MethodCallReference($className, $methodNameString);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Matcher/Collector/PublicClassMethodMatcher.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Matcher/Collector/PublicClassMethodMatcher.php
new file mode 100644
index 0000000..b95cc40
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Matcher/Collector/PublicClassMethodMatcher.php
@@ -0,0 +1,67 @@
+isClass()) {
+ return true;
+ }
+
+ return array_any(self::SKIPPED_TYPES, fn (string $skippedType): bool => $classReflection->is($skippedType));
+ }
+
+ public function isUsedByParentClassOrInterface(ClassReflection $classReflection, string $methodName): bool
+ {
+ // is this method required by parent contract? skip it
+ foreach ($classReflection->getInterfaces() as $parentInterfaceReflection) {
+ if ($parentInterfaceReflection->hasMethod($methodName)) {
+ return true;
+ }
+ }
+
+ return array_any($classReflection->getParents(), fn (ClassReflection $parentClassReflection): bool => $parentClassReflection->hasMethod($methodName));
+ }
+
+ public function shouldSkipClassMethod(ClassMethod $classMethod): bool
+ {
+ if ($classMethod->isMagic()) {
+ return true;
+ }
+
+ if ($classMethod->isStatic()) {
+ return true;
+ }
+
+ // skip attributes
+ if ($classMethod->attrGroups !== []) {
+ return true;
+ }
+
+ if (! $classMethod->isPublic()) {
+ return true;
+ }
+
+ $doc = $classMethod->getDocComment();
+
+ // skip symfony action
+ return $doc instanceof Doc && str_contains($doc->getText(), '@Route');
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/ClassMethodNodeFinder.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/ClassMethodNodeFinder.php
new file mode 100644
index 0000000..6f45c4c
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/ClassMethodNodeFinder.php
@@ -0,0 +1,51 @@
+getClassReflection();
+ if (! $classReflection instanceof ClassReflection) {
+ return null;
+ }
+
+ $classLike = $this->reflectionParser->parseClassReflection($classReflection);
+ if (! $classLike instanceof Class_) {
+ return null;
+ }
+
+ if (! $methodCall->name instanceof Identifier) {
+ return null;
+ }
+
+ $methodCallName = $methodCall->name->toString();
+
+ $classMethod = $classLike->getMethod($methodCallName);
+ if (! $classMethod instanceof ClassMethod) {
+ return null;
+ }
+
+ if (! $classMethod->isPrivate()) {
+ return null;
+ }
+
+ return $classMethod;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/MethodCallNodeFinder.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/MethodCallNodeFinder.php
new file mode 100644
index 0000000..040c1c5
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/MethodCallNodeFinder.php
@@ -0,0 +1,59 @@
+nodeFinder = new NodeFinder();
+ }
+
+ /**
+ * @return MethodCall[]
+ */
+ public function findUsages(MethodCall $methodCall, Scope $scope): array
+ {
+ $classReflection = $scope->getClassReflection();
+ if (! $classReflection instanceof ClassReflection) {
+ return [];
+ }
+
+ $classLike = $this->reflectionParser->parseClassReflection($classReflection);
+ if (! $classLike instanceof Class_) {
+ return [];
+ }
+
+ $methodCalls = $this->nodeFinder->find($classLike, function (Node $node) use ($methodCall): bool {
+ if (! $node instanceof MethodCall) {
+ return false;
+ }
+
+ if (! $this->nodeComparator->areNodesEqual($node->var, $methodCall->var)) {
+ return false;
+ }
+
+ return $this->nodeComparator->areNodesEqual($node->name, $methodCall->name);
+ });
+
+ Assert::allIsInstanceOf($methodCalls, MethodCall::class);
+
+ return $methodCalls;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/ReturnNodeFinder.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/ReturnNodeFinder.php
new file mode 100644
index 0000000..471a01f
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeFinder/ReturnNodeFinder.php
@@ -0,0 +1,69 @@
+findReturnsWithValues($classMethod);
+ if (count($returns) !== 1) {
+ return null;
+ }
+
+ $onlyReturn = $returns[0];
+ return $onlyReturn->expr;
+ }
+
+ /**
+ * @return Return_[]
+ */
+ public function findReturnsWithValues(ClassMethod $classMethod): array
+ {
+ $returns = [];
+
+ $this->traverseNodesWithCallable((array) $classMethod->stmts, static function (
+ Node $node
+ ) use (&$returns) {
+ // skip different scope
+ if ($node instanceof FunctionLike) {
+ return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
+ }
+
+ if (! $node instanceof Return_) {
+ return null;
+ }
+
+ if (! $node->expr instanceof Expr) {
+ return null;
+ }
+
+ $returns[] = $node;
+ });
+
+ return $returns;
+ }
+
+ /**
+ * @param callable(Node $node): (int|Node|null) $callable
+ * @param Node[] $nodes
+ */
+ private function traverseNodesWithCallable(array $nodes, callable $callable): void
+ {
+ $nodeTraverser = new NodeTraverser();
+
+ $callableNodeVisitor = new CallableNodeVisitor($callable);
+ $nodeTraverser->addVisitor($callableNodeVisitor);
+ $nodeTraverser->traverse($nodes);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeVisitor/CallableNodeVisitor.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeVisitor/CallableNodeVisitor.php
new file mode 100644
index 0000000..e0bb569
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/NodeVisitor/CallableNodeVisitor.php
@@ -0,0 +1,42 @@
+callable = $callable;
+ }
+
+ public function enterNode(Node $node): int|Node|null
+ {
+ $originalNode = $node;
+ $callable = $this->callable;
+
+ /** @var int|Node|null $newNode */
+ $newNode = $callable($node);
+
+ if ($originalNode instanceof Stmt && $newNode instanceof Expr) {
+ return new Expression($newNode);
+ }
+
+ return $newNode;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/PhpDoc/ApiDocStmtAnalyzer.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/PhpDoc/ApiDocStmtAnalyzer.php
new file mode 100644
index 0000000..c6b2307
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/PhpDoc/ApiDocStmtAnalyzer.php
@@ -0,0 +1,37 @@
+hasClassReflectionApiDoc($classReflection)) {
+ return true;
+ }
+
+ $docComment = $classMethod->getDocComment();
+ if (! $docComment instanceof Doc) {
+ return false;
+ }
+
+ return str_contains($docComment->getText(), '@api');
+ }
+
+ private function hasClassReflectionApiDoc(ClassReflection $classReflection): bool
+ {
+ if (! $classReflection->getResolvedPhpDoc() instanceof ResolvedPhpDocBlock) {
+ return false;
+ }
+
+ $resolvedPhpDocBlock = $classReflection->getResolvedPhpDoc();
+ return str_contains($resolvedPhpDocBlock->getPhpDocString(), '@api');
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Printer/CollectorMetadataPrinter.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Printer/CollectorMetadataPrinter.php
new file mode 100644
index 0000000..26a1e73
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Printer/CollectorMetadataPrinter.php
@@ -0,0 +1,248 @@
+standard = new Standard();
+ }
+
+ public function printArgTypesAsString(
+ MethodCall $methodCall,
+ ExtendedMethodReflection $extendedMethodReflection,
+ Scope $scope
+ ): string {
+ $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
+ $scope,
+ $methodCall->getArgs(),
+ $extendedMethodReflection->getVariants(),
+ $extendedMethodReflection->getNamedArgumentsVariants()
+ );
+ $parameters = $parametersAcceptor->getParameters();
+
+ $stringArgTypes = [];
+ foreach ($methodCall->getArgs() as $i => $arg) {
+ $argType = $scope->getType($arg->value);
+
+ // we have no idea, nothing we can do
+ if ($argType instanceof MixedType) {
+ return ResolvedTypes::UNKNOWN_TYPES;
+ }
+
+ if ($argType instanceof IntersectionType) {
+ return ResolvedTypes::UNKNOWN_TYPES;
+ }
+
+ if ($argType instanceof GenericObjectType) {
+ return ResolvedTypes::UNKNOWN_TYPES;
+ }
+
+ if ($argType instanceof PHPStanUnionType) {
+ return ResolvedTypes::UNKNOWN_TYPES;
+ }
+
+ if ($argType instanceof ThisType) {
+ $argType = new ObjectType($argType->getClassName());
+ }
+
+ if (array_key_exists($i, $parameters)) {
+ $defaultValueType = $parameters[$i]->getDefaultValue();
+ if ($defaultValueType instanceof Type) {
+ $argType = TypeCombinator::union($argType, $defaultValueType);
+ }
+ }
+
+ $printedArgType = $this->printTypeToString($argType);
+ $printedArgType = $this->normalizeDateTime($printedArgType);
+
+ $stringArgTypes[] = $printedArgType;
+ }
+
+ return implode('|', $stringArgTypes);
+ }
+
+ public function printParamTypesToString(
+ ClassMethod $classMethod,
+ ClassReflection $classReflection,
+ Scope $scope
+ ): string {
+ $className = $classReflection->getName();
+
+ $parametersReflection = [];
+ if ($classReflection->hasMethod($classMethod->name->name)) {
+ $methodReflection = $classReflection->getMethod($classMethod->name->name, $scope);
+ $variants = $methodReflection->getVariants();
+ if (count($variants) === 1) {
+ $parametersReflection = $variants[0]->getParameters();
+ }
+ }
+
+ $printedParamTypes = [];
+ foreach ($classMethod->params as $i => $param) {
+ if ($param->type === null) {
+ $printedParamTypes[] = '';
+ continue;
+ }
+
+ $phpdocType = null;
+ if (array_key_exists($i, $parametersReflection)) {
+ $paramphpdocType = $parametersReflection[$i]->getPhpDocType();
+ if (! $paramphpdocType instanceof MixedType) {
+ $phpdocType = $paramphpdocType;
+ }
+ }
+
+ if ($phpdocType instanceof Type) {
+ $printedParamType = $this->printTypeToString($phpdocType);
+ } else {
+ $paramType = $this->transformSelfToClassName($param->type, $className);
+
+ if ($paramType instanceof NullableType) {
+ // unite to phpstan type
+ $paramType = new UnionType([$paramType->type, new Identifier('null')]);
+ }
+
+ if ($paramType instanceof UnionType || $paramType instanceof NodeIntersectionType) {
+ $paramType = $this->resolveSortedTypes($paramType, $className);
+ }
+
+ $printedParamType = $this->standard->prettyPrint([$paramType]);
+ $printedParamType = str_replace('\Closure', 'callable', $printedParamType);
+ $printedParamType = ltrim($printedParamType, '\\');
+ $printedParamType = str_replace('|\\', '|', $printedParamType);
+ }
+
+ // to avoid DateTime vs DateTimeImmutable vs DateTimeInterface conflicts
+ $printedParamType = $this->normalizeDateTime($printedParamType);
+
+ $printedParamTypes[] = $printedParamType;
+ }
+
+ return implode('|', $printedParamTypes);
+ }
+
+ private function transformSelfToClassName(Node $node, ?string $className): Node
+ {
+ if (! $node instanceof Name || $className === null) {
+ return $node;
+ }
+
+ if ($node->toString() !== 'self') {
+ return $node;
+ }
+
+ return new FullyQualified($className);
+ }
+
+ private function resolveSortedTypes(
+ UnionType|NodeIntersectionType $paramType,
+ ?string $className
+ ): UnionType|NodeIntersectionType {
+ $typeNames = [];
+
+ foreach ($paramType->types as $type) {
+ if ($type instanceof NodeIntersectionType) {
+ foreach ($type->types as $intersectionType) {
+ /** @var Identifier|Name $intersectionType */
+ $intersectionType = $this->transformSelfToClassName($intersectionType, $className);
+ $typeNames[] = (string) $intersectionType;
+ }
+
+ continue;
+ }
+
+ /** @var Identifier|Name $type */
+ $type = $this->transformSelfToClassName($type, $className);
+ $typeNames[] = (string) $type;
+ }
+
+ sort($typeNames);
+
+ $types = [];
+ foreach ($typeNames as $typeName) {
+ $types[] = new Identifier($typeName);
+ }
+
+ if ($paramType instanceof NodeIntersectionType) {
+ return new NodeIntersectionType($types);
+ }
+
+ return new UnionType($types);
+ }
+
+ private function printTypeToString(Type $type): string
+ {
+ if ($type->isClassString()->yes()) {
+ return 'string';
+ }
+
+ if ($type->isArray()->yes()) {
+ return 'array';
+ }
+
+ if ($type->isBoolean()->yes()) {
+ return 'bool';
+ }
+
+ if ($type instanceof IntegerRangeType) {
+ return 'int';
+ }
+
+ if ($type instanceof ClosureType) {
+ return 'callable';
+ }
+
+ if (count($type->getEnumCases()) === 1) {
+ return $type->getEnumCases()[0]
+ ->getClassName();
+ }
+
+ return $type->describe(VerbosityLevel::typeOnly());
+ }
+
+ private function normalizeDateTime(string $printedType): string
+ {
+ if ($printedType === 'DateTimeImmutable') {
+ return 'DateTimeInterface';
+ }
+
+ if ($printedType === 'DateTime') {
+ return 'DateTimeInterface';
+ }
+
+ return $printedType;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Printer/NodeComparator.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Printer/NodeComparator.php
new file mode 100644
index 0000000..1d1efae
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Printer/NodeComparator.php
@@ -0,0 +1,25 @@
+setAttribute('comments', null);
+ $secondNode->setAttribute('comments', null);
+
+ return $this->printer->prettyPrint([$firstNode]) === $this->printer->prettyPrint([$secondNode]);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Reflection/MethodNodeAnalyser.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Reflection/MethodNodeAnalyser.php
new file mode 100644
index 0000000..6e99e48
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Reflection/MethodNodeAnalyser.php
@@ -0,0 +1,46 @@
+matchFirstParentClassMethod($scope, $methodName) instanceof MethodReflection;
+ }
+
+ public function matchFirstParentClassMethod(Scope $scope, string $methodName): ?MethodReflection
+ {
+ $classReflection = $scope->getClassReflection();
+ if (! $classReflection instanceof ClassReflection) {
+ return null;
+ }
+
+ // the classes have highter priority, e.g. priority in class covariance
+ foreach ($classReflection->getParents() as $parentClassReflection) {
+ if ($parentClassReflection->hasNativeMethod($methodName)) {
+ return $parentClassReflection->getNativeMethod($methodName);
+ }
+ }
+
+ foreach ($classReflection->getAncestors() as $ancestorClassReflection) {
+ if ($classReflection === $ancestorClassReflection) {
+ continue;
+ }
+
+ if (! $ancestorClassReflection->hasNativeMethod($methodName)) {
+ continue;
+ }
+
+ return $ancestorClassReflection->getNativeMethod($methodName);
+ }
+
+ return null;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Reflection/ReflectionParser.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Reflection/ReflectionParser.php
new file mode 100644
index 0000000..ab3fc54
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Reflection/ReflectionParser.php
@@ -0,0 +1,84 @@
+
+ */
+ private array $classesByFilename = [];
+
+ private readonly Parser $parser;
+
+ public function __construct()
+ {
+ $parserFactory = new ParserFactory();
+ $this->parser = $parserFactory->createForNewestSupportedVersion();
+ }
+
+ public function parseClassReflection(ClassReflection $classReflection): ?ClassLike
+ {
+ $fileName = $classReflection->getFileName();
+ if ($fileName === null) {
+ return null;
+ }
+
+ return $this->parseFilenameToClass($fileName);
+ }
+
+ private function parseFilenameToClass(string $fileName): ClassLike|null
+ {
+ if (isset($this->classesByFilename[$fileName])) {
+ return $this->classesByFilename[$fileName];
+ }
+
+ try {
+ /** @var string $fileContents */
+ $fileContents = file_get_contents($fileName);
+
+ $stmts = $this->parser->parse($fileContents);
+ if (! is_array($stmts)) {
+ return null;
+ }
+
+ // complete namespacedName variables
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->traverse($stmts);
+ } catch (Throwable) {
+ // not reachable
+ return null;
+ }
+
+ $classLike = $this->findFirstClassLike($stmts);
+ if (! $classLike instanceof ClassLike) {
+ return null;
+ }
+
+ $this->classesByFilename[$fileName] = $classLike;
+
+ return $classLike;
+ }
+
+ /**
+ * @param Node[] $nodes
+ */
+ private function findFirstClassLike(array $nodes): ?ClassLike
+ {
+ $nodeFinder = new NodeFinder();
+ return $nodeFinder->findFirstInstanceOf($nodes, ClassLike::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowPrivateClassMethodParamTypeRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowPrivateClassMethodParamTypeRule.php
new file mode 100644
index 0000000..000ee38
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowPrivateClassMethodParamTypeRule.php
@@ -0,0 +1,180 @@
+
+ */
+final readonly class NarrowPrivateClassMethodParamTypeRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Parameter %d should use "%s" type as the only type passed to this method';
+
+ public function __construct(
+ private Configuration $configuration,
+ private MethodCallNodeFinder $methodCallNodeFinder,
+ private ClassMethodNodeFinder $classMethodNodeFinder
+ ) {
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return MethodCall::class;
+ }
+
+ /**
+ * @param MethodCall $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNarrowParamEnabled() || $node->isFirstClassCallable()) {
+ return [];
+ }
+
+ $args = $node->getArgs();
+ if ($args === []) {
+ return [];
+ }
+
+ if (! $node->var instanceof Variable) {
+ return [];
+ }
+
+ if (! is_string($node->var->name)) {
+ return [];
+ }
+
+ if ($node->var->name !== 'this') {
+ return [];
+ }
+
+ return $this->validateArgVsParamTypes($args, $node, $scope);
+ }
+
+ /**
+ * @param Arg[] $args
+ * @return RuleError[]
+ */
+ private function validateArgVsParamTypes(array $args, MethodCall $methodCall, Scope $scope): array
+ {
+ $methodCallUses = $this->methodCallNodeFinder->findUsages($methodCall, $scope);
+ if (count($methodCallUses) > 1) {
+ return [];
+ }
+
+ $classMethod = $this->classMethodNodeFinder->findByMethodCall($methodCall, $scope);
+ if (! $classMethod instanceof ClassMethod) {
+ return [];
+ }
+
+ /** @var Param[] $params */
+ $params = $classMethod->getParams();
+
+ $errorMessages = [];
+
+ foreach ($args as $position => $arg) {
+ $param = $params[$position] ?? [];
+ if (! $param instanceof Param) {
+ continue;
+ }
+
+ if ($param->variadic) {
+ return [];
+ }
+
+ $paramRuleError = $this->validateParam($param, $position, $arg->value, $scope);
+ if (! $paramRuleError instanceof RuleError) {
+ continue;
+ }
+
+ // @todo test double failed type
+ $errorMessages[] = $paramRuleError;
+ }
+
+ return $errorMessages;
+ }
+
+ private function validateParam(Param $param, int $position, Expr $expr, Scope $scope): ?RuleError
+ {
+ $type = $param->type;
+
+ // @todo some static type mapper from php-parser to PHPStan?
+ if (! $type instanceof FullyQualified) {
+ return null;
+ }
+
+ $argType = $scope->getType($expr);
+ if ($argType instanceof MixedType) {
+ return null;
+ }
+
+ if ($argType instanceof TemplateType) {
+ return null;
+ }
+
+ // not solveable yet, work with PHP 8 code only
+ if ($argType instanceof UnionType) {
+ return null;
+ }
+
+ if ($argType instanceof IntersectionType || $argType instanceof GenericObjectType) {
+ return null;
+ }
+
+ $objectType = new ObjectType($type->toString());
+
+ if ($objectType->equals($argType)) {
+ return null;
+ }
+
+ $classReflection = $objectType->getClassReflection();
+ if ($classReflection instanceof ClassReflection && $classReflection->isAbstract()) {
+ return null;
+ }
+
+ // handle weird type substration cases
+ $paramTypeAsString = $objectType->describe(VerbosityLevel::typeOnly());
+ $argTypeAsString = $argType->describe(VerbosityLevel::typeOnly());
+
+ if ($paramTypeAsString === $argTypeAsString) {
+ return null;
+ }
+
+ $errorMessage = sprintf(self::ERROR_MESSAGE, $position + 1, $argTypeAsString);
+
+ return RuleErrorBuilder::message($errorMessage)
+ ->identifier('typePerfect.narrowPrivateClassMethodParamType')
+ ->line($param->getLine())
+ ->build();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowPublicClassMethodParamTypeRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowPublicClassMethodParamTypeRule.php
new file mode 100644
index 0000000..4e3072d
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowPublicClassMethodParamTypeRule.php
@@ -0,0 +1,165 @@
+
+ */
+final readonly class NarrowPublicClassMethodParamTypeRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Parameters should have "%s" types as the only types passed to this method';
+
+ public function __construct(
+ private Configuration $configuration
+ ) {
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return CollectedDataNode::class;
+ }
+
+ /**
+ * @param CollectedDataNode $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNarrowParamEnabled()) {
+ return [];
+ }
+
+ $publicClassMethodCollector = $node->get(PublicClassMethodParamTypesCollector::class);
+ $classMethodReferenceToArgTypes = $this->resolveClassMethodReferenceToArgTypes($node);
+
+ $ruleErrors = [];
+
+ foreach ($publicClassMethodCollector as $filePath => $declarations) {
+ foreach ($declarations as [$className, $methodName, $paramTypesString, $line]) {
+ $uniqueCollectedArgTypesString = $this->resolveUniqueArgTypesString(
+ $classMethodReferenceToArgTypes,
+ $className,
+ $methodName
+ );
+
+ if ($uniqueCollectedArgTypesString === null) {
+ continue;
+ }
+
+ if ($paramTypesString === $uniqueCollectedArgTypesString) {
+ continue;
+ }
+
+ $ruleErrors[] = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $uniqueCollectedArgTypesString))
+ ->identifier('typePerfect.narrowPublicClassMethodParamType')
+ ->file($filePath)
+ ->line($line)
+ ->build();
+ }
+ }
+
+ return $ruleErrors;
+ }
+
+ /**
+ * @return array
+ */
+ private function resolveClassMethodReferenceToArgTypes(CollectedDataNode $collectedDataNode): array
+ {
+ $methodCallArgTypesByFilePath = $collectedDataNode->get(MethodCallArgTypesCollector::class);
+
+ // these should be skipped completely, as we have no idea what is being passed there
+ $methodCallablesByFilePath = $collectedDataNode->get(MethodCallableCollector::class);
+
+ $methodFirstClassCallables = $this->flattenCollectedData($methodCallablesByFilePath);
+
+ // group call references and types
+ $classMethodReferenceToTypes = [];
+
+ foreach ($methodCallArgTypesByFilePath as $methodCallArgTypes) {
+ foreach ($methodCallArgTypes as [$classMethodReference, $argTypesString]) {
+ // skip it
+ if (in_array($classMethodReference, $methodFirstClassCallables, true)) {
+ continue;
+ }
+
+ $classMethodReferenceToTypes[$classMethodReference][] = $argTypesString;
+ }
+ }
+
+ // resolve unique values
+ foreach ($classMethodReferenceToTypes as $key => $value) {
+ $classMethodReferenceToTypes[$key] = array_unique($value);
+ }
+
+ return $classMethodReferenceToTypes;
+ }
+
+ /**
+ * @param array $classMethodReferenceToArgTypes
+ */
+ private function resolveUniqueArgTypesString(
+ array $classMethodReferenceToArgTypes,
+ string $className,
+ string $methodName
+ ): ?string {
+ $currentClassMethodReference = $className . '::' . $methodName;
+
+ $collectedArgTypes = $classMethodReferenceToArgTypes[$currentClassMethodReference] ?? null;
+ if ($collectedArgTypes === null) {
+ return null;
+ }
+
+ // we need exactly one type
+ if (count($collectedArgTypes) !== 1) {
+ return null;
+ }
+
+ // one of the arg types could not be resolved, we're not sure
+ if (in_array(ResolvedTypes::UNKNOWN_TYPES, $collectedArgTypes, true)) {
+ return null;
+ }
+
+ return $collectedArgTypes[0];
+ }
+
+ /**
+ * @param array|null)>> $methodCallablesByFilePath
+ * @return string[]
+ */
+ private function flattenCollectedData(array $methodCallablesByFilePath): array
+ {
+ $methodFirstClassCallables = [];
+ foreach ($methodCallablesByFilePath as $methodCallables) {
+ foreach ($methodCallables as $methodCallable) {
+ // the collector returns null for nodes it cannot resolve
+ if (! isset($methodCallable[0])) {
+ continue;
+ }
+
+ $methodFirstClassCallables[] = $methodCallable[0];
+ }
+ }
+
+ return $methodFirstClassCallables;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowReturnObjectTypeRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowReturnObjectTypeRule.php
new file mode 100644
index 0000000..6e5171b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NarrowReturnObjectTypeRule.php
@@ -0,0 +1,140 @@
+
+ */
+final readonly class NarrowReturnObjectTypeRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Provide more specific return type "%s" over abstract one';
+
+ public function __construct(
+ private ReturnNodeFinder $returnNodeFinder,
+ private MethodNodeAnalyser $methodNodeAnalyser,
+ private Configuration $configuration
+ ) {
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return ClassMethod::class;
+ }
+
+ /**
+ * @param ClassMethod $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNarrowReturnEnabled()) {
+ return [];
+ }
+
+ if (! $node->returnType instanceof FullyQualified) {
+ return [];
+ }
+
+ if ($this->shouldSkipScope($scope)) {
+ return [];
+ }
+
+ $returnObjectType = new ObjectType($node->returnType->toString());
+ if ($this->shouldSkipReturnObjectType($returnObjectType)) {
+ return [];
+ }
+
+ $methodName = $node->name->toString();
+ if ($this->methodNodeAnalyser->hasParentVendorLock($scope, $methodName)) {
+ return [];
+ }
+
+ $returnExpr = $this->returnNodeFinder->findOnlyReturnsExpr($node);
+ if (! $returnExpr instanceof Expr) {
+ return [];
+ }
+
+ $returnExprType = $scope->getType($returnExpr);
+ if ($this->shouldSkipReturnExprType($returnExprType)) {
+ return [];
+ }
+
+ if ($returnObjectType->equals($returnExprType)) {
+ return [];
+ }
+
+ // is subtype?
+ if (! $returnObjectType->isSuperTypeOf($returnExprType)->yes()) {
+ return [];
+ }
+
+ if (count($returnExprType->getObjectClassNames()) !== 1) {
+ return [];
+ }
+
+ $errorMessage = sprintf(self::ERROR_MESSAGE, $returnExprType->getObjectClassNames()[0]);
+
+ return [
+ RuleErrorBuilder::message($errorMessage)
+ ->identifier('typePerfect.narrowReturnObjectType')
+ ->build(),
+ ];
+ }
+
+ private function shouldSkipReturnObjectType(ObjectType $objectType): bool
+ {
+ $classReflection = $objectType->getClassReflection();
+ if (! $classReflection instanceof ClassReflection) {
+ return true;
+ }
+
+ // cannot be more precise if final class
+ return $classReflection->isFinal();
+ }
+
+ private function shouldSkipScope(Scope $scope): bool
+ {
+ $classReflection = $scope->getClassReflection();
+ if (! $classReflection instanceof ClassReflection) {
+ return true;
+ }
+
+ return ! $classReflection->isClass();
+ }
+
+ private function shouldSkipReturnExprType(Type $type): bool
+ {
+ if (count($type->getObjectClassNames()) !== 1) {
+ return true;
+ }
+
+ if (count($type->getObjectClassReflections()) !== 1) {
+ return true;
+ }
+
+ return $type->getObjectClassReflections()[0]
+ ->isAnonymous();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoArrayAccessOnObjectRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoArrayAccessOnObjectRule.php
new file mode 100644
index 0000000..17a4c49
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoArrayAccessOnObjectRule.php
@@ -0,0 +1,81 @@
+
+ */
+final readonly class NoArrayAccessOnObjectRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Use explicit methods over array access on object';
+
+ /**
+ * @var string[]
+ */
+ private const array ALLOWED_CLASSES = [
+ 'SplFixedArray',
+ 'SimpleXMLElement',
+ 'Iterator',
+ 'WeakMap',
+ 'Aws\ResultInterface',
+ 'Symfony\Component\DomCrawler\AbstractUriElement',
+ 'Symfony\Component\Form\FormInterface',
+ 'Symfony\Component\OptionsResolver\Options',
+ ];
+
+ public function __construct(
+ private Configuration $configuration,
+ ) {
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return ArrayDimFetch::class;
+ }
+
+ /**
+ * @param ArrayDimFetch $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNoArrayAccessOnObjectEnabled()) {
+ return [];
+ }
+
+ $varType = $scope->getType($node->var);
+ if (! $varType instanceof ObjectType) {
+ return [];
+ }
+
+ if ($this->isAllowedObjectType($varType)) {
+ return [];
+ }
+
+ return [
+ RuleErrorBuilder::message(self::ERROR_MESSAGE)
+ ->identifier('typePerfect.noArrayAccessOnObject')
+ ->build(),
+ ];
+ }
+
+ private function isAllowedObjectType(ObjectType $objectType): bool
+ {
+ return array_any(self::ALLOWED_CLASSES, fn (string $allowedClass): bool => $objectType->isInstanceOf($allowedClass)->yes());
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoEmptyOnObjectRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoEmptyOnObjectRule.php
new file mode 100644
index 0000000..9ed82f2
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoEmptyOnObjectRule.php
@@ -0,0 +1,54 @@
+
+ */
+final readonly class NoEmptyOnObjectRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Use instanceof instead of empty() on object';
+
+ public function __construct(
+ private EmptyIssetGuard $emptyIssetGuard,
+ private Configuration $configuration,
+ ) {
+ }
+
+ public function getNodeType(): string
+ {
+ return Empty_::class;
+ }
+
+ /**
+ * @param Empty_ $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNoEmptyOnObjectEnabled()) {
+ return [];
+ }
+
+ if ($this->emptyIssetGuard->isLegal($node->expr, $scope)) {
+ return [];
+ }
+
+ return [
+ RuleErrorBuilder::message(self::ERROR_MESSAGE)
+ ->identifier('typePerfect.noEmptyOnObject')
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoIssetOnObjectRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoIssetOnObjectRule.php
new file mode 100644
index 0000000..f6e3d33
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoIssetOnObjectRule.php
@@ -0,0 +1,60 @@
+
+ */
+final readonly class NoIssetOnObjectRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Use instanceof instead of isset() on object';
+
+ public function __construct(
+ private EmptyIssetGuard $emptyIssetGuard,
+ private Configuration $configuration,
+ ) {
+ }
+
+ public function getNodeType(): string
+ {
+ return Isset_::class;
+ }
+
+ /**
+ * @param Isset_ $node
+ *
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNoIssetOnObjectEnabled()) {
+ return [];
+ }
+
+ foreach ($node->vars as $var) {
+ if ($this->emptyIssetGuard->isLegal($var, $scope)) {
+ continue;
+ }
+
+ return [
+ RuleErrorBuilder::message(self::ERROR_MESSAGE)
+ ->identifier('typePerfect.noIssetOnObject')
+ ->build(),
+ ];
+ }
+
+ return [];
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoMixedMethodCallerRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoMixedMethodCallerRule.php
new file mode 100644
index 0000000..836d01f
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoMixedMethodCallerRule.php
@@ -0,0 +1,88 @@
+
+ */
+final readonly class NoMixedMethodCallerRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Mixed variable in a `%s->...()` can skip important errors. Make sure the type is known';
+
+ public function __construct(
+ private Printer $printer,
+ private Configuration $configuration,
+ ) {
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return MethodCall::class;
+ }
+
+ /**
+ * @param MethodCall $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNoMixedCallerEnabled()) {
+ return [];
+ }
+
+ $callerType = $scope->getType($node->var);
+
+ if (! $callerType instanceof MixedType) {
+ return [];
+ }
+
+ if ($callerType instanceof ErrorType) {
+ return [];
+ }
+
+ // if error, skip as well for false positive
+ if ($this->isPreviousTypeErrorType($node, $scope)) {
+ return [];
+ }
+
+ $printedMethodCall = $this->printer->prettyPrintExpr($node->var);
+
+ return [
+ RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $printedMethodCall))
+ ->identifier('typePerfect.noMixedMethodCaller')
+ ->build(),
+ ];
+ }
+
+ private function isPreviousTypeErrorType(MethodCall $methodCall, Scope $scope): bool
+ {
+ $currentMethodCall = $methodCall;
+ while ($currentMethodCall->var instanceof MethodCall) {
+ $previousType = $scope->getType($currentMethodCall->var);
+ if ($previousType instanceof ErrorType) {
+ return true;
+ }
+
+ $currentMethodCall = $currentMethodCall->var;
+ }
+
+ return false;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoMixedPropertyFetcherRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoMixedPropertyFetcherRule.php
new file mode 100644
index 0000000..0e9c9e6
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoMixedPropertyFetcherRule.php
@@ -0,0 +1,62 @@
+
+ */
+final readonly class NoMixedPropertyFetcherRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Mixed property fetch in a "%s->..." can skip important errors. Make sure the type is known';
+
+ public function __construct(
+ private Printer $printer,
+ private Configuration $configuration,
+ ) {
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return PropertyFetch::class;
+ }
+
+ /**
+ * @param PropertyFetch $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNoMixedPropertyEnabled()) {
+ return [];
+ }
+
+ $callerType = $scope->getType($node->var);
+ if (! $callerType instanceof MixedType) {
+ return [];
+ }
+
+ $printedVar = $this->printer->prettyPrintExpr($node->var);
+
+ return [
+ RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $printedVar))
+ ->identifier('typePerfect.noMixedPropertyFetcher')
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoParamTypeRemovalRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoParamTypeRemovalRule.php
new file mode 100644
index 0000000..b2592e5
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/NoParamTypeRemovalRule.php
@@ -0,0 +1,100 @@
+
+ */
+final readonly class NoParamTypeRemovalRule implements Rule
+{
+ public const string ERROR_MESSAGE = 'Removing parent param type is forbidden';
+
+ public function __construct(
+ private MethodNodeAnalyser $methodNodeAnalyser,
+ private Configuration $configuration,
+ ) {
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return ClassMethod::class;
+ }
+
+ /**
+ * @param ClassMethod $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNoParamTypeRemovalEnabled()) {
+ return [];
+ }
+
+ if ($node->params === []) {
+ return [];
+ }
+
+ $classMethodName = (string) $node->name;
+ if ($classMethodName === '__construct') {
+ return [];
+ }
+
+ $parentClassMethodReflection = $this->methodNodeAnalyser->matchFirstParentClassMethod($scope, $classMethodName);
+ if (! $parentClassMethodReflection instanceof PhpMethodReflection) {
+ return [];
+ }
+
+ foreach ($node->params as $paramPosition => $param) {
+ if ($param->type !== null) {
+ continue;
+ }
+
+ $parentParamType = $this->resolveParentParamType($parentClassMethodReflection, $paramPosition);
+ if ($parentParamType instanceof MixedType) {
+ continue;
+ }
+
+ // removed param type!
+ return [
+ RuleErrorBuilder::message(self::ERROR_MESSAGE)
+ ->identifier('typePerfect.noParamTypeRemoval')
+ ->build(),
+ ];
+ }
+
+ return [];
+ }
+
+ private function resolveParentParamType(PhpMethodReflection $phpMethodReflection, int $paramPosition): Type
+ {
+ foreach ($phpMethodReflection->getVariants() as $extendedParametersAcceptor) {
+ foreach ($extendedParametersAcceptor->getParameters() as $parentParamPosition => $parameterReflectionWithPhpDoc) {
+ if ($paramPosition !== $parentParamPosition) {
+ continue;
+ }
+
+ return $parameterReflectionWithPhpDoc->getNativeType();
+ }
+ }
+
+ return new MixedType();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/ReturnNullOverFalseRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/ReturnNullOverFalseRule.php
new file mode 100644
index 0000000..cb6e019
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Rules/ReturnNullOverFalseRule.php
@@ -0,0 +1,101 @@
+
+ */
+final readonly class ReturnNullOverFalseRule implements Rule
+{
+ /**
+ * @api
+ */
+ public const string ERROR_MESSAGE = 'Returning false in non return bool class method. Use null with type|null instead or add bool return type';
+
+ private NodeFinder $nodeFinder;
+
+ public function __construct(
+ private Configuration $configuration,
+ ) {
+ $this->nodeFinder = new NodeFinder();
+ }
+
+ /**
+ * @return class-string
+ */
+ public function getNodeType(): string
+ {
+ return ClassMethod::class;
+ }
+
+ /**
+ * @param ClassMethod $node
+ * @return RuleError[]
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (! $this->configuration->isNoFalsyReturnEnabled()) {
+ return [];
+ }
+
+ if ($node->stmts === null) {
+ return [];
+ }
+
+ if ($node->returnType instanceof Node) {
+ return [];
+ }
+
+ /** @var Return_[] $returns */
+ $returns = $this->nodeFinder->findInstanceOf($node->stmts, Return_::class);
+
+ $hasFalseType = false;
+ $hasTrueType = false;
+
+ foreach ($returns as $return) {
+ if (! $return->expr instanceof Expr) {
+ continue;
+ }
+
+ $exprType = $scope->getType($return->expr);
+ if (! $exprType instanceof ConstantBooleanType) {
+ if ($exprType->isBoolean()->yes()) {
+ return [];
+ }
+
+ continue;
+ }
+
+ if ($exprType->getValue()) {
+ $hasTrueType = true;
+ continue;
+ }
+
+ $hasFalseType = true;
+ }
+
+ if (! $hasTrueType && $hasFalseType) {
+ return [
+ RuleErrorBuilder::message(self::ERROR_MESSAGE)
+ ->identifier('typePerfect.nullOverFalse')
+ ->build(),
+ ];
+ }
+
+ return [];
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/ValueObject/MethodCallReference.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/ValueObject/MethodCallReference.php
new file mode 100644
index 0000000..9224546
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/ValueObject/MethodCallReference.php
@@ -0,0 +1,24 @@
+class;
+ }
+
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/DifferentClassSameMethodCallName.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/DifferentClassSameMethodCallName.php
new file mode 100644
index 0000000..31c2587
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/DifferentClassSameMethodCallName.php
@@ -0,0 +1,31 @@
+run($anotherClassWithRun);
+ }
+
+ /**
+ * @param SomeStaticCall|MethodCall $node
+ */
+ private function run(Node $node)
+ {
+ if ($node->name instanceof MethodCall) {
+ $this->run($node->name);
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/DoubleShot.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/DoubleShot.php
new file mode 100644
index 0000000..a9a8d49
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/DoubleShot.php
@@ -0,0 +1,18 @@
+isCheck($arg, $param);
+ }
+ private function isCheck(\PhpParser\Node $arg, \PhpParser\Node $param)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/Fixture.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/Fixture.php
new file mode 100644
index 0000000..68bd53c
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/Fixture.php
@@ -0,0 +1,22 @@
+isCheck($node);
+ }
+ }
+
+ private function isCheck(Node $node)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipAbstractBase.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipAbstractBase.php
new file mode 100644
index 0000000..ecafcfb
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipAbstractBase.php
@@ -0,0 +1,16 @@
+isCheck($arg);
+ }
+ private function isCheck(ConceptBase $arg)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipAlreadyCorrectType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipAlreadyCorrectType.php
new file mode 100644
index 0000000..f74f3dd
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipAlreadyCorrectType.php
@@ -0,0 +1,22 @@
+isCheck($node);
+ }
+ }
+
+ private function isCheck(MethodCall $node)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipCorrectUnionType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipCorrectUnionType.php
new file mode 100644
index 0000000..9480df8
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipCorrectUnionType.php
@@ -0,0 +1,32 @@
+isIncludeOnceOrRequireOnce($node)) {
+ return [];
+ }
+
+ return [];
+ }
+
+ /**
+ * @param Assign|Return_ $node
+ */
+ private function isIncludeOnceOrRequireOnce(\PhpParser\Node $node)
+ {
+ return true;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipDuplicatedCallOfSameMethodWithComment.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipDuplicatedCallOfSameMethodWithComment.php
new file mode 100644
index 0000000..632fc7d
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipDuplicatedCallOfSameMethodWithComment.php
@@ -0,0 +1,26 @@
+printFile(new FileWithoutNamespace([]));
+ }
+
+ private function printFile(\PhpParser\Node $node)
+ {
+ }
+
+ public function secondMethod()
+ {
+ // some comment
+ $this->printFile(new FileNode([]));
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipGenericType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipGenericType.php
new file mode 100644
index 0000000..e62b509
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipGenericType.php
@@ -0,0 +1,31 @@
+getsAGeneric($node);
+ }
+
+ /**
+ * @param T $node
+ * @return void
+ */
+ private function getsAGeneric(Node $node)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMayOverrideArg.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMayOverrideArg.php
new file mode 100644
index 0000000..b2658cd
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMayOverrideArg.php
@@ -0,0 +1,27 @@
+mayOverrideNode();
+
+ $this->isCheck($node);
+
+ }
+ }
+
+ private function isCheck(Node $node)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMixed.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMixed.php
new file mode 100644
index 0000000..66a104e
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMixed.php
@@ -0,0 +1,18 @@
+isCheck($arg);
+ }
+ private function isCheck(\PhpParser\Node $arg)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMultipleUsed.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMultipleUsed.php
new file mode 100644
index 0000000..1919ec9
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipMultipleUsed.php
@@ -0,0 +1,27 @@
+isCheck($node);
+ }
+
+ if ($node instanceof PropertyFetch) {
+ $this->isCheck($node);
+ }
+ }
+
+ private function isCheck(Node $node)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNoArgs.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNoArgs.php
new file mode 100644
index 0000000..9009443
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNoArgs.php
@@ -0,0 +1,15 @@
+execute();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNotFromThis.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNotFromThis.php
new file mode 100644
index 0000000..6543695
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNotFromThis.php
@@ -0,0 +1,14 @@
+format('Y-m-d');
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNotPrivate.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNotPrivate.php
new file mode 100644
index 0000000..a16c0ad
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipNotPrivate.php
@@ -0,0 +1,22 @@
+isCheckNotPrivate($node);
+ }
+ }
+
+ public function isCheckNotPrivate(Node $node)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipOptedOut.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipOptedOut.php
new file mode 100644
index 0000000..a74a81b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipOptedOut.php
@@ -0,0 +1,23 @@
+isCheck($type);
+ }
+
+ private function isCheck(\PHPStan\Type\Type $type)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipParentNotIf.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipParentNotIf.php
new file mode 100644
index 0000000..04a51b7
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipParentNotIf.php
@@ -0,0 +1,15 @@
+execute($node);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipRecursive.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipRecursive.php
new file mode 100644
index 0000000..7ebbdc6
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipRecursive.php
@@ -0,0 +1,30 @@
+run($node);
+ }
+
+ /**
+ * @param SomeStaticCall|MethodCall $node
+ */
+ private function run(Node $node): void
+ {
+ if ($node->name instanceof MethodCall) {
+ $this->run($node->name);
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipSameGeneric.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipSameGeneric.php
new file mode 100644
index 0000000..23b8b23
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipSameGeneric.php
@@ -0,0 +1,34 @@
+getPricesForArticle();
+ $this->getAmounts($prices);
+ }
+
+ /**
+ * @param MyIterator $prices
+ */
+ private function getAmounts(MyIterator $prices): void
+ {
+ }
+
+ /**
+ * @return MyIterator
+ */
+ private function getPricesForArticle(): MyIterator
+ {
+ /** @var MyIterator */
+ return new MyIterator();
+ }
+
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipVariadics.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipVariadics.php
new file mode 100644
index 0000000..db8c922
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Fixture/SkipVariadics.php
@@ -0,0 +1,21 @@
+takesVariadic(...$arr);
+ }
+
+ private function takesVariadic(Node ...$node)
+ {
+
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/NarrowPrivateClassMethodParamTypeRuleTest.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/NarrowPrivateClassMethodParamTypeRuleTest.php
new file mode 100644
index 0000000..b742874
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/NarrowPrivateClassMethodParamTypeRuleTest.php
@@ -0,0 +1,74 @@
+ $expectedErrorsWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorsWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorsWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/SkipDuplicatedCallOfSameMethodWithComment.php', []];
+
+ yield [__DIR__ . '/Fixture/SkipCorrectUnionType.php', []];
+ yield [__DIR__ . '/Fixture/SkipRecursive.php', []];
+ yield [__DIR__ . '/Fixture/SkipMixed.php', []];
+ yield [__DIR__ . '/Fixture/SkipOptedOut.php', []];
+ yield [__DIR__ . '/Fixture/SkipNotFromThis.php', []];
+
+ yield [__DIR__ . '/Fixture/SkipParentNotIf.php', []];
+ yield [__DIR__ . '/Fixture/SkipNoArgs.php', []];
+ yield [__DIR__ . '/Fixture/SkipAlreadyCorrectType.php', []];
+ yield [__DIR__ . '/Fixture/SkipMayOverrideArg.php', []];
+ yield [__DIR__ . '/Fixture/SkipMultipleUsed.php', []];
+ yield [__DIR__ . '/Fixture/SkipNotPrivate.php', []];
+
+ $errorMessage = sprintf(NarrowPrivateClassMethodParamTypeRule::ERROR_MESSAGE, 1, MethodCall::class);
+ yield [__DIR__ . '/Fixture/Fixture.php', [[$errorMessage, 19]]];
+ yield [__DIR__ . '/Fixture/DifferentClassSameMethodCallName.php', [[$errorMessage, 25]]];
+
+ $argErrorMessage = sprintf(NarrowPrivateClassMethodParamTypeRule::ERROR_MESSAGE, 1, Arg::class);
+ $paramErrorMessage = sprintf(NarrowPrivateClassMethodParamTypeRule::ERROR_MESSAGE, 2, Param::class);
+ yield [__DIR__ . '/Fixture/DoubleShot.php', [[$argErrorMessage, 15], [$paramErrorMessage, 15]]];
+ yield [__DIR__ . '/Fixture/SkipGenericType.php', []];
+ yield [__DIR__ . '/Fixture/SkipAbstractBase.php', []];
+ yield [__DIR__ . '/Fixture/SkipVariadics.php', []];
+ yield [__DIR__ . '/Fixture/SkipSameGeneric.php', []];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NarrowPrivateClassMethodParamTypeRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Source/AnotherClassWithRun.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Source/AnotherClassWithRun.php
new file mode 100644
index 0000000..0b3ba33
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPrivateClassMethodParamTypeRule/Source/AnotherClassWithRun.php
@@ -0,0 +1,15 @@
+test(false);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/FirstClassCallables/CallVariadics.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/FirstClassCallables/CallVariadics.php
new file mode 100644
index 0000000..4d01cdd
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/FirstClassCallables/CallVariadics.php
@@ -0,0 +1,15 @@
+callMe(...);
+
+ $someCalledMethod->callMe(1000);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/FirstClassCallables/SomeCalledMethod.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/FirstClassCallables/SomeCalledMethod.php
new file mode 100644
index 0000000..3b1da8c
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/FirstClassCallables/SomeCalledMethod.php
@@ -0,0 +1,12 @@
+ $g */
+ public function doFoo(GenericA $g):void
+ {
+ }
+
+ /** @param GenericA $g */
+ function doBar($g):void {
+ $this->doFoo($g);
+ }
+}
+
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/Generics/SkipSameGeneric.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/Generics/SkipSameGeneric.php
new file mode 100644
index 0000000..160755e
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/Generics/SkipSameGeneric.php
@@ -0,0 +1,19 @@
+ $_werbemasse
+ */
+ public function setWerbemasse($_werbemasse): void
+ {
+ $this->_werbemasse = $_werbemasse;
+ }
+}
+
+
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/HandleDefaultValue.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/HandleDefaultValue.php
new file mode 100644
index 0000000..c040d87
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/HandleDefaultValue.php
@@ -0,0 +1,30 @@
+takesUnionWithDifferentDefaultValue(1);
+
+ $o = new HandleDefaultValue();
+ $o->takesUnionWithSameTypeDefaultValue(1);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/PublicDoubleShot.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/PublicDoubleShot.php
new file mode 100644
index 0000000..b156afb
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/PublicDoubleShot.php
@@ -0,0 +1,12 @@
+map(function () {});
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipClassStringPassed.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipClassStringPassed.php
new file mode 100644
index 0000000..3efc716
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipClassStringPassed.php
@@ -0,0 +1,12 @@
+map(function () {});
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipDateTimeMix.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipDateTimeMix.php
new file mode 100644
index 0000000..c64230a
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipDateTimeMix.php
@@ -0,0 +1,23 @@
+markDate(
+ new \DateTimeImmutable('15:30')
+ );
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipDefault.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipDefault.php
new file mode 100644
index 0000000..9575234
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipDefault.php
@@ -0,0 +1,21 @@
+dontSetParamValue();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipEnum.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipEnum.php
new file mode 100644
index 0000000..1d695da
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipEnum.php
@@ -0,0 +1,19 @@
+pick(PickReasonEnum::BarcodeIssue);
+ }
+}
\ No newline at end of file
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipEqualUnionType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipEqualUnionType.php
new file mode 100644
index 0000000..7c6cb87
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipEqualUnionType.php
@@ -0,0 +1,44 @@
+class
+ : $obj->var;
+ }
+
+ public function runTernaryFlipped(StaticCall|MethodCall $obj)
+ {
+ return $obj instanceof MethodCall
+ ? $obj->var
+ : $obj->class;
+ }
+
+ /**
+ * @param Node[]|Node $node
+ */
+ public function runArrayTyped(array | Node $node)
+ {
+ }
+
+ /**
+ * @param array|Node $node
+ */
+ public function runArrayTyped2(array | Node $node)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipExpectedClassType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipExpectedClassType.php
new file mode 100644
index 0000000..1c72233
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipExpectedClassType.php
@@ -0,0 +1,14 @@
+callMe(1000);
+ }
+
+ private function callMe($number)
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipNullableCompare.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipNullableCompare.php
new file mode 100644
index 0000000..70f5b1a
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipNullableCompare.php
@@ -0,0 +1,14 @@
+map($resource, 'name');
+
+
+ $resource = $this->getFileResource();
+ $this->map($resource, 'surname');
+ }
+
+ /**
+ * @return resource
+ */
+ private function getFileResource()
+ {
+ return fopen('file.txt', 'r');
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipSelf.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipSelf.php
new file mode 100644
index 0000000..f979c8e
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipSelf.php
@@ -0,0 +1,17 @@
+takesSelf(new SkipSelf());
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipThisPassedExactType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipThisPassedExactType.php
new file mode 100644
index 0000000..c7670e1
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/SkipThisPassedExactType.php
@@ -0,0 +1,14 @@
+run(new DateTime('now'));
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/ThisPassedFromInterface.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/ThisPassedFromInterface.php
new file mode 100644
index 0000000..df4f8eb
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Fixture/ThisPassedFromInterface.php
@@ -0,0 +1,14 @@
+ $expectedErrorsWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(array $filePaths, array $expectedErrorsWithLines): void
+ {
+ $this->analyse($filePaths, $expectedErrorsWithLines);
+ }
+
+ /**
+ * @return Iterator>, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [[__DIR__ . '/Fixture/Generics/SkipPassedGenerics.php'], []];
+
+ yield [[__DIR__ . '/Fixture/SkipDefault.php'], []];
+ yield [[__DIR__ . '/Fixture/SkipResource.php'], []];
+ yield [[__DIR__ . '/Fixture/SkipDateTimeMix.php'], []];
+ yield [[__DIR__ . '/Fixture/SkipNonPublicClassMethod.php'], []];
+
+ // skip first class callables as anything can be passed there
+ yield [[
+ __DIR__ . '/Fixture/FirstClassCallables/CallVariadics.php',
+ __DIR__ . '/Fixture/FirstClassCallables/SomeCalledMethod.php',
+ ], []];
+
+ // skip expected scalar type
+ yield [[
+ __DIR__ . '/Fixture/SkipProperlyFilledParamType.php',
+ __DIR__ . '/Source/ExpectedType/FirstTypedCaller.php',
+ __DIR__ . '/Source/ExpectedType/SecondTypedCaller.php',
+ ], []];
+
+ // skip expected object type
+ yield [[
+ __DIR__ . '/Fixture/SkipExpectedClassType.php',
+ __DIR__ . '/Source/ExpectedClassType/FirstClassTypedCaller.php',
+ __DIR__ . '/Source/ExpectedClassType/SecondClassTypedCaller.php',
+ ], []];
+
+ // skip class-string
+ yield [[
+ __DIR__ . '/Fixture/SkipClassStringPassed.php',
+ __DIR__ . '/Source/ExpectedClassString/FirstTypedCaller.php',
+ __DIR__ . '/Source/ExpectedClassString/SecondTypedCaller.php',
+ ], []];
+
+ // skip everything in case of values is mixed
+ yield [[
+ __DIR__ . '/Fixture/SkipMixedAndString.php',
+ __DIR__ . '/Source/MixedAndString/FirstCaller.php',
+ __DIR__ . '/Source/MixedAndString/SecondCaller.php',
+ ], []];
+
+ // skip int + string values
+ yield [[
+ __DIR__ . '/Fixture/SkipMixedAndString.php',
+ __DIR__ . '/Source/MixedAndString/FirstCaller.php',
+ __DIR__ . '/Source/MixedAndString/ThirdCaller.php',
+ ], []];
+
+ // skip nullable compare
+ yield [[
+ __DIR__ . '/Fixture/SkipNullableCompare.php',
+ __DIR__ . '/Source/NullableParam/FirstNullable.php',
+ __DIR__ . '/Source/NullableParam/SecondNullable.php',
+ ], []];
+
+ // skip api
+ yield [[
+ __DIR__ . '/Fixture/SkipApiMarked.php',
+ __DIR__ . '/Source/ExpectedNodeApi/CallWithProperty.php',
+ ], []];
+
+ // skip equal union type
+ yield [[
+ __DIR__ . '/Fixture/SkipEqualUnionType.php',
+ __DIR__ . '/Source/ExpectedUnion/CallUnionType.php',
+ ], []];
+
+ // skip equal union type flipped
+ yield [[
+ __DIR__ . '/Fixture/SkipEqualUnionType.php',
+ __DIR__ . '/Source/ExpectedUnion/CallUnionTypeFlipped.php',
+ ], []];
+
+ // skip equal union type ternary if else
+ yield [[
+ __DIR__ . '/Fixture/SkipEqualUnionType.php',
+ __DIR__ . '/Source/ExpectedUnion/CallUnionTypeTernaryIfElse.php',
+ ], []];
+
+ // skip equal union type ternary if else flipped
+ yield [[
+ __DIR__ . '/Fixture/SkipEqualUnionType.php',
+ __DIR__ . '/Source/ExpectedUnion/CallUnionTypeTernaryIfElseFlipped.php',
+ ], []];
+
+ // skip equal union type array typed
+ yield [[
+ __DIR__ . '/Fixture/SkipEqualUnionType.php',
+ __DIR__ . '/Source/ExpectedUnion/CallUnionArrayType.php',
+ ], []];
+
+ // skip this passed exact type
+ yield [[
+ __DIR__ . '/Fixture/SkipThisPassedExactType.php',
+ __DIR__ . '/Source/ExpectedThisType/CallByThis.php',
+ ], []];
+
+ // skip used internally for second type
+ yield [[
+ __DIR__ . '/Fixture/SkipUsedInternallyForSecondType.php',
+ __DIR__ . '/Source/ExpectedType/OnlyFirstTypeCalledOutside.php',
+ ], []];
+
+ $argErrorMessage = sprintf(NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE, 'int');
+ yield [[
+ __DIR__ . '/Fixture/PublicDoubleShot.php',
+ __DIR__ . '/Source/FirstCaller.php',
+ __DIR__ . '/Source/SecondCaller.php',
+ ], [[$argErrorMessage, 9]]];
+
+ // this passed from interface
+ $argErrorMessage = sprintf(
+ NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE,
+ CallByThisFromInterface::class
+ );
+ yield [[
+ __DIR__ . '/Fixture/ThisPassedFromInterface.php',
+ __DIR__ . '/Source/ExpectedThisType/CallByThisFromInterface.php',
+ ], [[$argErrorMessage, 11]]];
+
+ yield [[__DIR__ . '/Fixture/SkipSelf.php'], []];
+
+ yield [[__DIR__ . '/Fixture/SkipClosure.php'], []];
+
+ yield [[__DIR__ . '/Fixture/SkipCallable.php'], []];
+
+ yield [[__DIR__ . '/Fixture/SkipEnum.php'], []];
+
+ $argErrorMessage = sprintf(NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE, 'int');
+
+ yield [[__DIR__ . '/Fixture/HandleDefaultValue.php'], [[$argErrorMessage, 15]]];
+
+ $argErrorMessage = sprintf(NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE, 'bool');
+ yield [[__DIR__ . '/Fixture/ExplicitlyNullableParams.php'], [[$argErrorMessage, 9]]];
+
+ // skip generics
+ yield [[
+ __DIR__ . '/Fixture/Generics/SkipSameGeneric.php',
+ __DIR__ . '/Source/SkipSameGeneric/MyGroup.php',
+ __DIR__ . '/Source/SkipSameGeneric/MyIterator.php',
+ __DIR__ . '/Source/SkipSameGeneric/MyService.php',
+ ], []];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NarrowPublicClassMethodParamTypeRule::class);
+ }
+
+ /**
+ * Warning, just spent hour looking for why the test does not run :D This should be implicit part of the parent
+ * class.
+ *
+ * @return Collector[]
+ */
+ #[Override]
+ protected function getCollectors(): array
+ {
+ return self::getContainer()->getServicesByTag('phpstan.collector');
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassString/FirstTypedCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassString/FirstTypedCaller.php
new file mode 100644
index 0000000..35c8ce0
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassString/FirstTypedCaller.php
@@ -0,0 +1,17 @@
+resolve($classReflection->getName());
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassString/SecondTypedCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassString/SecondTypedCaller.php
new file mode 100644
index 0000000..4d4815a
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassString/SecondTypedCaller.php
@@ -0,0 +1,17 @@
+resolve($classReflection->getName());
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassType/FirstClassTypedCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassType/FirstClassTypedCaller.php
new file mode 100644
index 0000000..8ad53ce
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassType/FirstClassTypedCaller.php
@@ -0,0 +1,17 @@
+callMeWithClassType($knownType);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassType/SecondClassTypedCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassType/SecondClassTypedCaller.php
new file mode 100644
index 0000000..222edca
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedClassType/SecondClassTypedCaller.php
@@ -0,0 +1,17 @@
+callMeWithClassType($knownType);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedNodeApi/CallWithProperty.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedNodeApi/CallWithProperty.php
new file mode 100644
index 0000000..44abed4
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedNodeApi/CallWithProperty.php
@@ -0,0 +1,17 @@
+callNode($property);
+ }
+
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedThisType/CallByThis.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedThisType/CallByThis.php
new file mode 100644
index 0000000..e78ad81
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedThisType/CallByThis.php
@@ -0,0 +1,15 @@
+run($this);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedThisType/CallByThisFromInterface.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedThisType/CallByThisFromInterface.php
new file mode 100644
index 0000000..eb90be0
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedThisType/CallByThisFromInterface.php
@@ -0,0 +1,16 @@
+run($this);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/FirstTypedCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/FirstTypedCaller.php
new file mode 100644
index 0000000..9eacb9d
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/FirstTypedCaller.php
@@ -0,0 +1,15 @@
+callMeTwice(100);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/OnlyFirstTypeCalledOutside.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/OnlyFirstTypeCalledOutside.php
new file mode 100644
index 0000000..a3e116a
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/OnlyFirstTypeCalledOutside.php
@@ -0,0 +1,16 @@
+run(new stdClass());
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/SecondTypedCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/SecondTypedCaller.php
new file mode 100644
index 0000000..cc0e2df
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedType/SecondTypedCaller.php
@@ -0,0 +1,15 @@
+callMeTwice(100);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionArrayType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionArrayType.php
new file mode 100644
index 0000000..813720b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionArrayType.php
@@ -0,0 +1,31 @@
+runArrayTyped($node);
+ }
+
+ public function run2(SkipEqualUnionType $skipEqualUnionType, Node $node): void
+ {
+ /** @var Node[]|Node $node */
+ $node = rand(0, 1)
+ ? $node
+ : [$node];
+
+ $skipEqualUnionType->runArrayTyped2($node);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionType.php
new file mode 100644
index 0000000..b3b11a0
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionType.php
@@ -0,0 +1,22 @@
+run($value);
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeFlipped.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeFlipped.php
new file mode 100644
index 0000000..a65c98b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeFlipped.php
@@ -0,0 +1,22 @@
+run($value);
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeTernaryIfElse.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeTernaryIfElse.php
new file mode 100644
index 0000000..5643966
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeTernaryIfElse.php
@@ -0,0 +1,22 @@
+runTernary($value);
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeTernaryIfElseFlipped.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeTernaryIfElseFlipped.php
new file mode 100644
index 0000000..2ac613f
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/ExpectedUnion/CallUnionTypeTernaryIfElseFlipped.php
@@ -0,0 +1,22 @@
+runTernaryFlipped($value);
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/FirstCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/FirstCaller.php
new file mode 100644
index 0000000..81565e0
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/FirstCaller.php
@@ -0,0 +1,15 @@
+callMeTwice(100);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/FirstCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/FirstCaller.php
new file mode 100644
index 0000000..abf52f5
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/FirstCaller.php
@@ -0,0 +1,15 @@
+resolve('string');
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/SecondCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/SecondCaller.php
new file mode 100644
index 0000000..c087c8d
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/SecondCaller.php
@@ -0,0 +1,15 @@
+resolve($mixedValue);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/ThirdCaller.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/ThirdCaller.php
new file mode 100644
index 0000000..532bda8
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/MixedAndString/ThirdCaller.php
@@ -0,0 +1,15 @@
+resolve(1000);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/NullableParam/FirstNullable.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/NullableParam/FirstNullable.php
new file mode 100644
index 0000000..b666861
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/NullableParam/FirstNullable.php
@@ -0,0 +1,16 @@
+callNode($node);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/NullableParam/SecondNullable.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/NullableParam/SecondNullable.php
new file mode 100644
index 0000000..2ace432
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/NullableParam/SecondNullable.php
@@ -0,0 +1,16 @@
+callNode($node);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/PassMeAsType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/PassMeAsType.php
new file mode 100644
index 0000000..ee1a0d0
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/PassMeAsType.php
@@ -0,0 +1,10 @@
+callMeTwice(100);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/SkipSameGeneric/MyGroup.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/SkipSameGeneric/MyGroup.php
new file mode 100644
index 0000000..e0e02d7
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/SkipSameGeneric/MyGroup.php
@@ -0,0 +1,10 @@
+articleModel->setWerbemasse($this->fetchCustomizationPositionsByArtidAndSprache());
+ }
+
+ /**
+ * @return MyIterator
+ */
+ public function fetchCustomizationPositionsByArtidAndSprache(int $artid, int $spracheid): MyIterator
+ {
+ }
+
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/SomeInterface.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/SomeInterface.php
new file mode 100644
index 0000000..ee3221b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowPublicClassMethodParamTypeRule/Source/SomeInterface.php
@@ -0,0 +1,8 @@
+ $expectedErrorMessagesWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorMessagesWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/SkipSpecificReturnType.php', []];
+ yield [__DIR__ . '/Fixture/SkipSomeContract.php', []];
+
+ $errorMessage = sprintf(NarrowReturnObjectTypeRule::ERROR_MESSAGE, SpecificControl::class);
+ yield [__DIR__ . '/Fixture/SomeAbstractReturnType.php', [[$errorMessage, 12]]];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NarrowReturnObjectTypeRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowReturnObjectTypeRule/Source/AbstractControl.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowReturnObjectTypeRule/Source/AbstractControl.php
new file mode 100644
index 0000000..8b600f0
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NarrowReturnObjectTypeRule/Source/AbstractControl.php
@@ -0,0 +1,9 @@
+children() as $name => $childElement) {
+ return isset($childElement['some']);
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoArrayAccessOnObjectRule/NoArrayAccessOnObjectRuleTest.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoArrayAccessOnObjectRule/NoArrayAccessOnObjectRuleTest.php
new file mode 100644
index 0000000..332a987
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoArrayAccessOnObjectRule/NoArrayAccessOnObjectRuleTest.php
@@ -0,0 +1,55 @@
+ $expectedErrorMessagesWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorMessagesWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/ArrayAccessOnObject.php', [[NoArrayAccessOnObjectRule::ERROR_MESSAGE, 14]]];
+ yield [__DIR__ . '/Fixture/ArrayAccessOnNestedObject.php', [[NoArrayAccessOnObjectRule::ERROR_MESSAGE, 14]]];
+
+ yield [__DIR__ . '/Fixture/SkipIterator.php', []];
+ yield [__DIR__ . '/Fixture/SkipOnArray.php', []];
+ yield [__DIR__ . '/Fixture/SkipSplFixedArray.php', []];
+ yield [__DIR__ . '/Fixture/SkipUriElement.php', []];
+ yield [__DIR__ . '/Fixture/SkipWeakMap.php', []];
+ yield [__DIR__ . '/Fixture/SkipXml.php', []];
+ yield [__DIR__ . '/Fixture/SkipXmlElementForeach.php', []];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NoArrayAccessOnObjectRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoArrayAccessOnObjectRule/Source/ChildOfSomeClassWithArrayAccess.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoArrayAccessOnObjectRule/Source/ChildOfSomeClassWithArrayAccess.php
new file mode 100644
index 0000000..a04d76d
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoArrayAccessOnObjectRule/Source/ChildOfSomeClassWithArrayAccess.php
@@ -0,0 +1,9 @@
+args[9])) {
+ return $methodCall->args[9];
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoEmptyOnObjectRule/Fixture/SkipPhpDocType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoEmptyOnObjectRule/Fixture/SkipPhpDocType.php
new file mode 100644
index 0000000..ec18890
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoEmptyOnObjectRule/Fixture/SkipPhpDocType.php
@@ -0,0 +1,12 @@
+foo = 'bar';
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoEmptyOnObjectRule/NoEmptyOnObjectRuleTest.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoEmptyOnObjectRule/NoEmptyOnObjectRuleTest.php
new file mode 100644
index 0000000..e23380b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoEmptyOnObjectRule/NoEmptyOnObjectRuleTest.php
@@ -0,0 +1,51 @@
+ $expectedErrorMessagesWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorMessagesWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/EmptyOnObject.php', [[NoEmptyOnObjectRule::ERROR_MESSAGE, 19]]];
+
+ yield [__DIR__ . '/Fixture/SkipEmptyOnArray.php', []];
+ yield [__DIR__ . '/Fixture/SkipEmptyOnArrayNestedOnObject.php', []];
+ yield [__DIR__ . '/Fixture/SkipPossibleUndefinedVariable.php', []];
+ yield [__DIR__ . '/Fixture/SkipPhpDocType.php', []];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NoEmptyOnObjectRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/IssetOnObject.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/IssetOnObject.php
new file mode 100644
index 0000000..8509aee
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/IssetOnObject.php
@@ -0,0 +1,23 @@
+args[9])) {
+ return $methodCall->args[9];
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/SkipIssetOnPropertyFetch.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/SkipIssetOnPropertyFetch.php
new file mode 100644
index 0000000..66f1bd5
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/SkipIssetOnPropertyFetch.php
@@ -0,0 +1,27 @@
+initializeFoo();
+
+ $this->foo->sayHello();
+ }
+
+ private function initializeFoo(): void
+ {
+ if (!isset($this->foo))
+ {
+ $this->foo = new Foo();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/SkipPhpDocType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/SkipPhpDocType.php
new file mode 100644
index 0000000..a35ede9
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Fixture/SkipPhpDocType.php
@@ -0,0 +1,12 @@
+foo = 'bar';
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/NoIssetOnObjectRuleTest.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/NoIssetOnObjectRuleTest.php
new file mode 100644
index 0000000..a6633e8
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/NoIssetOnObjectRuleTest.php
@@ -0,0 +1,52 @@
+ $expectedErrorMessagesWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorMessagesWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/IssetOnObject.php', [[NoIssetOnObjectRule::ERROR_MESSAGE, 19]]];
+
+ yield [__DIR__ . '/Fixture/SkipIssetOnArray.php', []];
+ yield [__DIR__ . '/Fixture/SkipIssetOnArrayNestedOnObject.php', []];
+ yield [__DIR__ . '/Fixture/SkipPossibleUndefinedVariable.php', []];
+ yield [__DIR__ . '/Fixture/SkipIssetOnPropertyFetch.php', []];
+ yield [__DIR__ . '/Fixture/SkipPhpDocType.php', []];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NoIssetOnObjectRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Source/Foo.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Source/Foo.php
new file mode 100644
index 0000000..d69a712
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoIssetOnObjectRule/Source/Foo.php
@@ -0,0 +1,13 @@
+$magic();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipKnownCallerType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipKnownCallerType.php
new file mode 100644
index 0000000..297d252
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipKnownCallerType.php
@@ -0,0 +1,15 @@
+call();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipMockObject.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipMockObject.php
new file mode 100644
index 0000000..58168a7
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipMockObject.php
@@ -0,0 +1,19 @@
+createMock(MagicMethodName::class);
+
+ $mock
+ ->method('run')
+ ->willReturn(true);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipPHPUnitMock.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipPHPUnitMock.php
new file mode 100644
index 0000000..ecf6e76
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipPHPUnitMock.php
@@ -0,0 +1,21 @@
+createMock(SomeFinalClass::class);
+
+ $someClassMock->expects($this->once())
+ ->method('some')
+ ->with($this->any())
+ ->willReturn(1000);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/UnknownCallerType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/UnknownCallerType.php
new file mode 100644
index 0000000..5a01e19
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Fixture/UnknownCallerType.php
@@ -0,0 +1,13 @@
+call();
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/NoMixedMethodCallerRuleTest.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/NoMixedMethodCallerRuleTest.php
new file mode 100644
index 0000000..f85d37b
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/NoMixedMethodCallerRuleTest.php
@@ -0,0 +1,54 @@
+ $expectedErrorsWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorsWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorsWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/SkipKnownCallerType.php', []];
+ yield [__DIR__ . '/Fixture/SkipMockObject.php', []];
+ yield [__DIR__ . '/Fixture/SkipPHPUnitMock.php', []];
+
+ $errorMessage = sprintf(NoMixedMethodCallerRule::ERROR_MESSAGE, '$someType');
+ yield [__DIR__ . '/Fixture/MagicMethodName.php', [[$errorMessage, 11]]];
+
+ $errorMessage = sprintf(NoMixedMethodCallerRule::ERROR_MESSAGE, '$mixedType');
+ yield [__DIR__ . '/Fixture/UnknownCallerType.php', [[$errorMessage, 11]]];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NoMixedMethodCallerRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Source/KnownType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Source/KnownType.php
new file mode 100644
index 0000000..75536c7
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedMethodCallerRule/Source/KnownType.php
@@ -0,0 +1,12 @@
+{$name};
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/SkipDynamicNameWithKnownType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/SkipDynamicNameWithKnownType.php
new file mode 100644
index 0000000..c3812a1
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/SkipDynamicNameWithKnownType.php
@@ -0,0 +1,15 @@
+{$name};
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/SkipKnownFetcherType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/SkipKnownFetcherType.php
new file mode 100644
index 0000000..48ec8ac
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/SkipKnownFetcherType.php
@@ -0,0 +1,15 @@
+name;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/UnknownPropertyFetcher.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/UnknownPropertyFetcher.php
new file mode 100644
index 0000000..ef906f0
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Fixture/UnknownPropertyFetcher.php
@@ -0,0 +1,13 @@
+name;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/NoMixedPropertyFetcherRuleTest.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/NoMixedPropertyFetcherRuleTest.php
new file mode 100644
index 0000000..a9df18e
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/NoMixedPropertyFetcherRuleTest.php
@@ -0,0 +1,53 @@
+ $expectedErrorsWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorsWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorsWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/SkipDynamicNameWithKnownType.php', []];
+ yield [__DIR__ . '/Fixture/SkipKnownFetcherType.php', []];
+
+ $message = sprintf(NoMixedPropertyFetcherRule::ERROR_MESSAGE, '$unknownType');
+ yield [__DIR__ . '/Fixture/DynamicName.php', [[$message, 11]]];
+
+ $message = sprintf(NoMixedPropertyFetcherRule::ERROR_MESSAGE, '$unknownType');
+ yield [__DIR__ . '/Fixture/UnknownPropertyFetcher.php', [[$message, 11]]];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NoMixedPropertyFetcherRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Source/KnownType.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Source/KnownType.php
new file mode 100644
index 0000000..2655c86
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoMixedPropertyFetcherRule/Source/KnownType.php
@@ -0,0 +1,10 @@
+ $expectedErrorMessagesWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorMessagesWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/SkipConstruct.php', []];
+ yield [__DIR__ . '/Fixture/SkipPhpDocType.php', []];
+ yield [__DIR__ . '/Fixture/SkipPresentType.php', []];
+ yield [__DIR__ . '/Fixture/SkipNoType.php', []];
+
+ yield [__DIR__ . '/Fixture/SkipIndirectRemoval.php', []];
+
+ yield [__DIR__ . '/Fixture/RemoveParentType.php', [[NoParamTypeRemovalRule::ERROR_MESSAGE, 11]]];
+
+ yield [__DIR__ . '/Fixture/SkipNoParent.php', []];
+ yield [__DIR__ . '/Fixture/SkipNotHasParentMethod.php', []];
+ yield [__DIR__ . '/Fixture/SkipHasSameParameterWithParentMethod.php', []];
+ yield [__DIR__ . '/Fixture/SkipHasSameParameterWithInterfaceMethod.php', []];
+
+ yield [
+ __DIR__ . '/Fixture/HasDifferentParameterWithParentMethod.php',
+ [[NoParamTypeRemovalRule::ERROR_MESSAGE, 9]],
+ ];
+ yield [
+ __DIR__ . '/Fixture/HasDifferentParameterWithInterfaceMethod.php',
+ [[NoParamTypeRemovalRule::ERROR_MESSAGE, 9], [NoParamTypeRemovalRule::ERROR_MESSAGE, 13]],
+ ];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(NoParamTypeRemovalRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoParamTypeRemovalRule/Source/NoTypeInterface.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoParamTypeRemovalRule/Source/NoTypeInterface.php
new file mode 100644
index 0000000..333fbd2
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/NoParamTypeRemovalRule/Source/NoTypeInterface.php
@@ -0,0 +1,10 @@
+loadFromDb($name)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function loadFromDb(string $name): ?array
+ {
+ if (mt_rand(1, 0)) {
+ return null;
+ }
+
+ return ['test' => 'foo'];
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/ReturnNullOverFalseRule/Fixture/CheckResultFromOtherMethod2.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/ReturnNullOverFalseRule/Fixture/CheckResultFromOtherMethod2.php
new file mode 100644
index 0000000..65fe076
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/ReturnNullOverFalseRule/Fixture/CheckResultFromOtherMethod2.php
@@ -0,0 +1,31 @@
+isPropertyVisible($name)) {
+ return false;
+ }
+
+ return isset($this->$name);
+ }
+
+ private function isPropertyVisible(string $name): bool
+ {
+ if (mt_rand(1, 0)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/ReturnNullOverFalseRule/Fixture/ReturnFalseOnly.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/ReturnNullOverFalseRule/Fixture/ReturnFalseOnly.php
new file mode 100644
index 0000000..0b91cf9
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/Rules/ReturnNullOverFalseRule/Fixture/ReturnFalseOnly.php
@@ -0,0 +1,17 @@
+ $expectedErrorMessagesWithLines
+ */
+ #[DataProvider('provideData')]
+ public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
+ {
+ $this->analyse([$filePath], $expectedErrorMessagesWithLines);
+ }
+
+ /**
+ * @return Iterator, mixed>>
+ */
+ public static function provideData(): Iterator
+ {
+ yield [__DIR__ . '/Fixture/ReturnFalseOnly.php', [[ReturnNullOverFalseRule::ERROR_MESSAGE, 9]]];
+ yield [__DIR__ . '/Fixture/CheckResultFromOtherMethod.php', []];
+ yield [__DIR__ . '/Fixture/CheckResultFromOtherMethod2.php', []];
+
+ yield [__DIR__ . '/Fixture/SkipReturnBool.php', []];
+ }
+
+ /**
+ * @return string[]
+ */
+ #[Override]
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [__DIR__ . '/../../../config/extension.neon'];
+ }
+
+ protected function getRule(): Rule
+ {
+ return self::getContainer()->getByType(ReturnNullOverFalseRule::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/SomeClass.php.inc b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/SomeClass.php.inc
new file mode 100644
index 0000000..17ce850
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/SomeClass.php.inc
@@ -0,0 +1,13 @@
+name;
+ }
+}
diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/config/included_services.neon b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/config/included_services.neon
new file mode 100644
index 0000000..6291a43
--- /dev/null
+++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/tests/config/included_services.neon
@@ -0,0 +1,2 @@
+includes:
+ - ../../config/services.neon
diff --git a/tools/.phpstan/vendor/webmozart/assert/CHANGELOG.md b/tools/.phpstan/vendor/webmozart/assert/CHANGELOG.md
new file mode 100644
index 0000000..d62c758
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/CHANGELOG.md
@@ -0,0 +1,315 @@
+Changelog
+=========
+
+## 2.4.1
+
+### Fixed
+
+- Corrected `uuid` assertion to prevent braces and prefixes being _inside_ the value.
+
+## 2.4.0
+
+### Changed
+
+- Updated docblocks for many methods, improving psalm support and type hints.
+
+## 2.3.0
+
+### Changed
+
+- Clarified documentation and testing of `uniqueValues`.
+
+### Added
+
+- All assertions now support `string|callable` for the message.
+
+## 2.2.0
+
+### Added
+
+- Added `isNotInstanceOfAny` assertion.
+
+## 2.1.6
+
+### Fixed
+
+- Corrected docblocks for `list*` methods.
+
+## 2.1.5
+
+### Fixed
+
+- Fixed regression of `instanceOf` messages
+
+## 2.1.4
+
+### Fixed
+
+- Use custom message for more internal calls.
+
+## 2.1.3
+
+### Fixed
+
+- Corrected `isList` type documentation.
+- Corrected `isAOf`, `isInstanceOf`, etc type documentation.
+
+## 2.1.2
+
+### Fixed
+
+- Changed `all*` assertion values back to `mixed`.
+
+## 2.1.1
+
+### Fixed
+
+- Optimized `stringNotEmpty` by internally using `notSame`.
+
+## 2.1.0
+
+### Fixed
+
+- Corrected `@param` declaration for `isMap`.
+- Pass custom message to internal assertion calls.
+
+## 2.0.0
+
+### Changed
+
+- **BREAKING** Minimum PHP version is now 8.2 (was 7.2).
+- **BREAKING** Remove deprecated `isTraversable`, use `isIterable` or `isInstanceOf` instead.
+- **BREAKING** Added `declare(strict_types=1)` to all classes.
+- Updated CI/CD test matrix to test PHP 8.2, 8.3, 8.4, and 8.5.
+- Updated development tools (PHPUnit, Psalm, PHP-CS-Fixer) to supported versions.
+- Added explicit return types and parameter types to all methods in both source code and tests.
+
+### Added
+
+- All assertion methods now return the checked value.
+- Added `notInArray` and `notOneOf`.
+- Added `isInitialized`, to check if a class property is initialized.
+- Added `negativeInteger` and `notNegativeInteger`
+- Added `isStatic` and `notStatic`
+
+### Fixed
+
+- Corrected validation of emails with unicode characters.
+
+## 1.12.1
+
+### Fixed
+
+- Exclude tools from export.
+
+## 1.12.0
+
+### Fixed
+
+- Corrected messages and typos in various assertions.
+- Document `void` return type.
+- Prevent UUIDs with trailing newlines from validating.
+- Assert values are strings before ctype checks.
+
+## 1.11.0
+
+### Added
+
+* Added explicit (non-magic) `allNullOr*` methods, with `@psalm-assert` annotations, for better Psalm support.
+
+### Changed
+
+* Trait methods will now check the assertion themselves, instead of using `__callStatic`
+* `isList` will now deal correctly with (modified) lists that contain `NaN`
+* `reportInvalidArgument` now has a return type of `never`.
+
+### Removed
+
+* Removed `symfony/polyfill-ctype` as a dependency, and require `ext-ctype` instead.
+ * You can still require the `symfony/polyfill-ctype` in your project if you need it, as it provides `ext-ctype`
+
+## 1.10.0
+
+### Added
+
+* On invalid assertion, we throw a `Webmozart\Assert\InvalidArgumentException`
+* Added `Assert::positiveInteger()`
+
+### Changed
+
+* Using a trait with real implementations of `all*()` and `nullOr*()` methods to improve psalm compatibility.
+
+### Removed
+
+* Support for PHP <7.2
+
+## 1.9.1
+
+## Fixed
+
+* provisional support for PHP 8.0
+
+## 1.9.0
+
+* added better Psalm support for `all*` & `nullOr*` methods
+* These methods are now understood by Psalm through a mixin. You may need a newer version of Psalm in order to use this
+* added `@psalm-pure` annotation to `Assert::notFalse()`
+* added more `@psalm-assert` annotations where appropriate
+
+## Changed
+
+* the `all*` & `nullOr*` methods are now declared on an interface, instead of `@method` annotations.
+This interface is linked to the `Assert` class with a `@mixin` annotation. Most IDEs have supported this
+for a long time, and you should not lose any autocompletion capabilities. PHPStan has supported this since
+version `0.12.20`. This package is marked incompatible (with a composer conflict) with phpstan version prior to that.
+If you do not use PHPStan than this does not matter.
+
+## 1.8.0
+
+### Added
+
+* added `Assert::notStartsWith()`
+* added `Assert::notEndsWith()`
+* added `Assert::inArray()`
+* added `@psalm-pure` annotations to pure assertions
+
+### Fixed
+
+* Exception messages of comparisons between `DateTime(Immutable)` objects now display their date & time.
+* Custom Exception messages for `Assert::count()` now use the values to render the exception message.
+
+## 1.7.0 (2020-02-14)
+
+### Added
+
+* added `Assert::notFalse()`
+* added `Assert::isAOf()`
+* added `Assert::isAnyOf()`
+* added `Assert::isNotA()`
+
+## 1.6.0 (2019-11-24)
+
+### Added
+
+* added `Assert::validArrayKey()`
+* added `Assert::isNonEmptyList()`
+* added `Assert::isNonEmptyMap()`
+* added `@throws InvalidArgumentException` annotations to all methods that throw.
+* added `@psalm-assert` for the list type to the `isList` assertion.
+
+### Fixed
+
+* `ResourceBundle` & `SimpleXMLElement` now pass the `isCountable` assertions.
+They are countable, without implementing the `Countable` interface.
+* The doc block of `range` now has the proper variables.
+* An empty array will now pass `isList` and `isMap`. As it is a valid form of both.
+If a non-empty variant is needed, use `isNonEmptyList` or `isNonEmptyMap`.
+
+### Changed
+
+* Removed some `@psalm-assert` annotations, that were 'side effect' assertions See:
+ * [#144](https://github.com/webmozart/assert/pull/144)
+ * [#145](https://github.com/webmozart/assert/issues/145)
+ * [#146](https://github.com/webmozart/assert/pull/146)
+ * [#150](https://github.com/webmozart/assert/pull/150)
+* If you use Psalm, the minimum version needed is `3.6.0`. Which is enforced through a composer conflict.
+If you don't use Psalm, then this has no impact.
+
+## 1.5.0 (2019-08-24)
+
+### Added
+
+* added `Assert::uniqueValues()`
+* added `Assert::unicodeLetters()`
+* added: `Assert::email()`
+* added support for [Psalm](https://github.com/vimeo/psalm), by adding `@psalm-assert` annotations where appropriate.
+
+### Fixed
+
+* `Assert::endsWith()` would not give the correct result when dealing with a multibyte suffix.
+* `Assert::length(), minLength, maxLength, lengthBetween` would not give the correct result when dealing with multibyte characters.
+
+**NOTE**: These 2 changes may break your assertions if you relied on the fact that multibyte characters didn't behave correctly.
+
+### Changed
+
+* The names of some variables have been updated to better reflect what they are.
+* All function calls are now in their FQN form, slightly increasing performance.
+* Tests are now properly ran against HHVM-3.30 and PHP nightly.
+
+### Deprecation
+
+* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()`
+ * This was already done in 1.3.0, but it was only done through a silenced `trigger_error`. It is now annotated as well.
+
+## 1.4.0 (2018-12-25)
+
+### Added
+
+* added `Assert::ip()`
+* added `Assert::ipv4()`
+* added `Assert::ipv6()`
+* added `Assert::notRegex()`
+* added `Assert::interfaceExists()`
+* added `Assert::isList()`
+* added `Assert::isMap()`
+* added polyfill for ctype
+
+### Fixed
+
+* Special case when comparing objects implementing `__toString()`
+
+## 1.3.0 (2018-01-29)
+
+### Added
+
+* added `Assert::minCount()`
+* added `Assert::maxCount()`
+* added `Assert::countBetween()`
+* added `Assert::isCountable()`
+* added `Assert::notWhitespaceOnly()`
+* added `Assert::natural()`
+* added `Assert::notContains()`
+* added `Assert::isArrayAccessible()`
+* added `Assert::isInstanceOfAny()`
+* added `Assert::isIterable()`
+
+### Fixed
+
+* `stringNotEmpty` will no longer report "0" is an empty string
+
+### Deprecation
+
+* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()`
+
+## 1.2.0 (2016-11-23)
+
+ * added `Assert::throws()`
+ * added `Assert::count()`
+ * added extension point `Assert::reportInvalidArgument()` for custom subclasses
+
+## 1.1.0 (2016-08-09)
+
+ * added `Assert::object()`
+ * added `Assert::propertyExists()`
+ * added `Assert::propertyNotExists()`
+ * added `Assert::methodExists()`
+ * added `Assert::methodNotExists()`
+ * added `Assert::uuid()`
+
+## 1.0.2 (2015-08-24)
+
+ * integrated Style CI
+ * add tests for minimum package dependencies on Travis CI
+
+## 1.0.1 (2015-05-12)
+
+ * added support for PHP 5.3.3
+
+## 1.0.0 (2015-05-12)
+
+ * first stable release
+
+## 1.0.0-beta (2015-03-19)
+
+ * first beta release
diff --git a/tools/.phpstan/vendor/webmozart/assert/LICENSE b/tools/.phpstan/vendor/webmozart/assert/LICENSE
new file mode 100644
index 0000000..9e2e307
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Bernhard Schussek
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/tools/.phpstan/vendor/webmozart/assert/README.md b/tools/.phpstan/vendor/webmozart/assert/README.md
new file mode 100644
index 0000000..fef4eda
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/README.md
@@ -0,0 +1,313 @@
+Webmozart Assert
+================
+
+[](https://packagist.org/packages/webmozart/assert)
+[](https://packagist.org/packages/webmozart/assert)
+
+This library contains efficient assertions to test the input and output of
+your methods. With these assertions, you can greatly reduce the amount of coding
+needed to write a safe implementation.
+
+All assertions in the [`Assert`] class throw an `Webmozart\Assert\InvalidArgumentException` if
+they fail.
+
+FAQ
+---
+
+**What's the difference to [beberlei/assert]?**
+
+This library is heavily inspired by Benjamin Eberlei's wonderful [assert package],
+but fixes a usability issue with error messages that can't be fixed there without
+breaking backwards compatibility.
+
+This package features usable error messages by default. However, you can also
+easily write custom error messages:
+
+```
+Assert::string($path, 'The path is expected to be a string. Got: %s');
+```
+
+In [beberlei/assert], the ordering of the `%s` placeholders is different for
+every assertion. This package, on the contrary, provides consistent placeholder
+ordering for all assertions:
+
+* `%s`: The tested value as string, e.g. `"/foo/bar"`.
+* `%2$s`, `%3$s`, ...: Additional assertion-specific values, e.g. the
+ minimum/maximum length, allowed values, etc.
+
+Check the source code of the assertions to find out details about the additional
+available placeholders.
+
+Installation
+------------
+
+Use [Composer] to install the package:
+
+```bash
+composer require webmozart/assert
+```
+
+Example
+-------
+
+```php
+use Webmozart\Assert\Assert;
+
+class Employee
+{
+ public function __construct($id)
+ {
+ Assert::integer($id, 'The employee ID must be an integer. Got: %s');
+ Assert::greaterThan($id, 0, 'The employee ID must be a positive integer. Got: %s');
+ }
+}
+```
+
+If you create an employee with an invalid ID, an exception is thrown:
+
+```php
+new Employee('foobar');
+// => Webmozart\Assert\InvalidArgumentException:
+// The employee ID must be an integer. Got: string
+
+new Employee(-10);
+// => Webmozart\Assert\InvalidArgumentException:
+// The employee ID must be a positive integer. Got: -10
+```
+
+Assertions
+----------
+
+The [`Assert`] class provides the following assertions:
+
+### Type Assertions
+
+Method | Description
+----------------------------------------------------------- | --------------------------------------------------
+`string($value, $message = '')` | Check that a value is a string
+`stringNotEmpty($value, $message = '')` | Check that a value is a non-empty string
+`integer($value, $message = '')` | Check that a value is an integer
+`integerish($value, $message = '')` | Check that a value casts to an integer
+`positiveInteger($value, $message = '')` | Check that a value is a positive (non-zero) integer
+`negativeInteger($value, $message = '')` | Check that a value is a negative integer
+`notNegativeInteger($value, $message = '')` | Check that a value is a non-negative integer
+`float($value, $message = '')` | Check that a value is a float
+`numeric($value, $message = '')` | Check that a value is numeric
+`natural($value, $message = '')` | Check that a value is a non-negative integer
+`boolean($value, $message = '')` | Check that a value is a boolean
+`scalar($value, $message = '')` | Check that a value is a scalar
+`object($value, $message = '')` | Check that a value is an object
+`objectish($value, $message = '')` | Check that a value is an object or a string of a class that exists
+`resource($value, $type = null, $message = '')` | Check that a value is a resource
+`isInitialized($value, $property, $message = '')` | Check that a value has an initialized property
+`isCallable($value, $message = '')` | Check that a value is a callable
+`isArray($value, $message = '')` | Check that a value is an array
+`isIterable($value, $message = '')` | Check that a value is an array or a `\Traversable`
+`isCountable($value, $message = '')` | Check that a value is an array or a `\Countable`
+`isInstanceOf($value, $class, $message = '')` | Check that a value is an `instanceof` a class
+`isInstanceOfAny($value, array $classes, $message = '')` | Check that a value is an `instanceof` at least one class on the array of classes
+`notInstanceOf($value, $class, $message = '')` | Check that a value is not an `instanceof` a class
+`isNotInstanceOfAny($value, array $classes, $message = '')` | Check that a value is not an `instanceof` at least one class on the array of classes
+`isAOf($value, $class, $message = '')` | Check that a value is of the class or has one of its parents
+`isAnyOf($value, array $classes, $message = '')` | Check that a value is of at least one of the classes or has one of its parents
+`isNotA($value, $class, $message = '')` | Check that a value is not of the class or has not one of its parents
+`isArrayAccessible($value, $message = '')` | Check that a value can be accessed as an array
+`uniqueValues($values, $message = '')` | Check that the given array contains unique values
+
+### Comparison Assertions
+
+Method | Description
+----------------------------------------------------- | ------------------------------------------------------------------
+`true($value, $message = '')` | Check that a value is `true`
+`false($value, $message = '')` | Check that a value is `false`
+`notFalse($value, $message = '')` | Check that a value is not `false`
+`null($value, $message = '')` | Check that a value is `null`
+`notNull($value, $message = '')` | Check that a value is not `null`
+`isEmpty($value, $message = '')` | Check that a value is `empty()`
+`notEmpty($value, $message = '')` | Check that a value is not `empty()`
+`eq($value, $value2, $message = '')` | Check that a value equals another (`==`)
+`notEq($value, $value2, $message = '')` | Check that a value does not equal another (`!=`)
+`same($value, $value2, $message = '')` | Check that a value is identical to another (`===`)
+`notSame($value, $value2, $message = '')` | Check that a value is not identical to another (`!==`)
+`greaterThan($value, $value2, $message = '')` | Check that a value is greater than another
+`greaterThanEq($value, $value2, $message = '')` | Check that a value is greater than or equal to another
+`lessThan($value, $value2, $message = '')` | Check that a value is less than another
+`lessThanEq($value, $value2, $message = '')` | Check that a value is less than or equal to another
+`range($value, $min, $max, $message = '')` | Check that a value is within a range
+`inArray($value, array $values, $message = '')` | Check that a value is one of a list of values
+`notInArray($value, array $values, $message = '')` | Check that a value is not one of a list of values
+`oneOf($value, array $values, $message = '')` | Check that a value is one of a list of values (alias of `inArray`)
+`notOneOf($value, array $values, $message = '')` | Check that a value is not one of a list of values (alias of `notInArray`)
+
+### String Assertions
+
+You should check that a value is a string with `Assert::string()` before making
+any of the following assertions.
+
+Method | Description
+--------------------------------------------------- | -----------------------------------------------------------------
+`contains($value, $subString, $message = '')` | Check that a string contains a substring
+`notContains($value, $subString, $message = '')` | Check that a string does not contain a substring
+`startsWith($value, $prefix, $message = '')` | Check that a string has a prefix
+`notStartsWith($value, $prefix, $message = '')` | Check that a string does not have a prefix
+`startsWithLetter($value, $message = '')` | Check that a string starts with a letter
+`endsWith($value, $suffix, $message = '')` | Check that a string has a suffix
+`notEndsWith($value, $suffix, $message = '')` | Check that a string does not have a suffix
+`regex($value, $pattern, $message = '')` | Check that a string matches a regular expression
+`notRegex($value, $pattern, $message = '')` | Check that a string does not match a regular expression
+`unicodeLetters($value, $message = '')` | Check that a string contains Unicode letters only
+`alpha($value, $message = '')` | Check that a string contains letters only
+`digits($value, $message = '')` | Check that a string contains digits only
+`alnum($value, $message = '')` | Check that a string contains letters and digits only
+`lower($value, $message = '')` | Check that a string contains lowercase characters only
+`upper($value, $message = '')` | Check that a string contains uppercase characters only
+`length($value, $length, $message = '')` | Check that a string has a certain number of characters
+`minLength($value, $min, $message = '')` | Check that a string has at least a certain number of characters
+`maxLength($value, $max, $message = '')` | Check that a string has at most a certain number of characters
+`lengthBetween($value, $min, $max, $message = '')` | Check that a string has a length in the given range
+`uuid($value, $message = '')` | Check that a string is a valid UUID
+`ip($value, $message = '')` | Check that a string is a valid IP (either IPv4 or IPv6)
+`ipv4($value, $message = '')` | Check that a string is a valid IPv4
+`ipv6($value, $message = '')` | Check that a string is a valid IPv6
+`email($value, $message = '')` | Check that a string is a valid e-mail address
+`notWhitespaceOnly($value, $message = '')` | Check that a string contains at least one non-whitespace character
+
+### File Assertions
+
+Method | Description
+----------------------------------- | --------------------------------------------------
+`fileExists($value, $message = '')` | Check that a value is an existing path
+`file($value, $message = '')` | Check that a value is an existing file
+`directory($value, $message = '')` | Check that a value is an existing directory
+`readable($value, $message = '')` | Check that a value is a readable path
+`writable($value, $message = '')` | Check that a value is a writable path
+
+### Object Assertions
+
+Method | Description
+----------------------------------------------------- | --------------------------------------------------
+`classExists($value, $message = '')` | Check that a value is an existing class name
+`subclassOf($value, $class, $message = '')` | Check that a class is a subclass of another
+`interfaceExists($value, $message = '')` | Check that a value is an existing interface name
+`implementsInterface($value, $class, $message = '')` | Check that a class implements an interface
+`propertyExists($value, $property, $message = '')` | Check that a property exists in a class/object
+`propertyNotExists($value, $property, $message = '')` | Check that a property does not exist in a class/object
+`methodExists($value, $method, $message = '')` | Check that a method exists in a class/object
+`methodNotExists($value, $method, $message = '')` | Check that a method does not exist in a class/object
+
+### Array Assertions
+
+Method | Description
+-------------------------------------------------- | ------------------------------------------------------------------
+`keyExists($array, $key, $message = '')` | Check that a key exists in an array
+`keyNotExists($array, $key, $message = '')` | Check that a key does not exist in an array
+`validArrayKey($key, $message = '')` | Check that a value is a valid array key (int or string)
+`count($array, $number, $message = '')` | Check that an array contains a specific number of elements
+`minCount($array, $min, $message = '')` | Check that an array contains at least a certain number of elements
+`maxCount($array, $max, $message = '')` | Check that an array contains at most a certain number of elements
+`countBetween($array, $min, $max, $message = '')` | Check that an array has a count in the given range
+`isList($array, $message = '')` | Check that an array is a non-associative list
+`isNonEmptyList($array, $message = '')` | Check that an array is a non-associative list, and not empty
+`isMap($array, $message = '')` | Check that an array is associative and has strings as keys
+`isNonEmptyMap($array, $message = '')` | Check that an array is associative and has strings as keys, and is not empty
+
+### Function Assertions
+
+Method | Description
+------------------------------------------| -----------------------------------------------------------------------------------------------------
+`throws($closure, $class, $message = '')` | Check that a function throws a certain exception. Subclasses of the exception class will be accepted.
+`isStatic($closure, $message = '')` | Check that a function is static.
+`notStatic($closure, $message = '')` | Check that a function is not static.
+
+### Collection Assertions
+
+All of the above assertions can be prefixed with `all*()` to test the contents
+of an array or a `\Traversable`:
+
+```php
+Assert::allIsInstanceOf($employees, 'Acme\Employee');
+```
+
+### Nullable Assertions
+
+All of the above assertions can be prefixed with `nullOr*()` to run the
+assertion only if the value is not `null`:
+
+```php
+Assert::nullOrString($middleName, 'The middle name must be a string or null. Got: %s');
+```
+
+### Lazy assertion messages
+
+Assertion messages can be provided as callables to avoid unnecessary computation when the assertion passes:
+
+```php
+Assert::string($value, fn() => expensiveMessage());
+```
+
+The callable will only be executed if the assertion fails.
+
+### Extending Assert
+
+The `Assert` class comes with a few methods, which can be overridden to change the class behaviour. You can also extend it to
+add your own assertions.
+
+
+#### Overriding methods
+
+Overriding the following methods in your assertion class allows you to change the behaviour of the assertions:
+
+* `public static function __callStatic($name, $arguments)`
+ * This method is used to 'create' the `nullOr` and `all` versions of the assertions.
+* `protected static function valueToString($value)`
+ * This method is used for error messages, to convert the value to a string value for displaying. You could use this for representing a value object with a `__toString` method for example.
+* `protected static function typeToString($value)`
+ * This method is used for error messages, to convert a value to a string representing its type.
+* `protected static function strlen($value)`
+ * This method is used to calculate string length for relevant methods, using the `mb_strlen` if available and useful.
+* `protected static function reportInvalidArgument($message)`
+ * This method is called when an assertion fails, with the specified error message. Here you can throw your own exception, or log something.
+
+## Static analysis support
+
+Where applicable, assertion functions are annotated to support Psalm's
+[Assertion syntax](https://psalm.dev/docs/annotating_code/assertion_syntax/).
+
+A native Psalm plugin can be enabled to also add type inference for return types (new in 2.x):
+
+```php
+vendor/bin/psalm-plugin enable webmozart/assert
+```
+
+A dedicated [PHPStan Plugin](https://github.com/phpstan/phpstan-webmozart-assert) is
+required for proper type support.
+
+Authors
+-------
+
+* [Bernhard Schussek] a.k.a. [@webmozart]
+* [The Community Contributors]
+
+Contribute
+----------
+
+Contributions to the package are always welcome!
+
+* Report any bugs or issues you find on the [issue tracker].
+* You can grab the source code at the package's [Git repository].
+
+License
+-------
+
+All contents of this package are licensed under the [MIT license].
+
+[beberlei/assert]: https://github.com/beberlei/assert
+[assert package]: https://github.com/beberlei/assert
+[Composer]: https://getcomposer.org
+[Bernhard Schussek]: https://webmozarts.com
+[The Community Contributors]: https://github.com/webmozart/assert/graphs/contributors
+[issue tracker]: https://github.com/webmozart/assert/issues
+[Git repository]: https://github.com/webmozart/assert
+[@webmozart]: https://twitter.com/webmozart
+[MIT license]: LICENSE
+[`Assert`]: src/Assert.php
diff --git a/tools/.phpstan/vendor/webmozart/assert/composer.json b/tools/.phpstan/vendor/webmozart/assert/composer.json
new file mode 100644
index 0000000..6c816f6
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/composer.json
@@ -0,0 +1,71 @@
+{
+ "name": "webmozart/assert",
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "license": "MIT",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ },
+ {
+ "name": "Woody Gilk",
+ "email": "woody.gilk@gmail.com"
+ }
+ ],
+ "require": {
+ "php": "^8.2",
+ "ext-ctype": "*",
+ "ext-date": "*",
+ "ext-filter": "*"
+ },
+ "suggest": {
+ "ext-intl": "",
+ "ext-simplexml": "",
+ "ext-spl": ""
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Webmozart\\Assert\\Bin\\": "bin/src",
+ "Webmozart\\Assert\\Tests\\": "tests/"
+ }
+ },
+ "extra": {
+ "psalm": {
+ "pluginClass": "Webmozart\\Assert\\PsalmPlugin"
+ },
+ "branch-alias": {
+ "dev-feature/2-0": "2.0-dev",
+ "dev-master": "2.0-dev"
+ }
+ },
+ "scripts": {
+ "install-tools": [
+ "composer --working-dir=tools/php-cs-fixer install",
+ "composer --working-dir=tools/phpunit install",
+ "composer --working-dir=tools/psalm install",
+ "composer --working-dir=tools/roave-bc-check install"
+ ],
+ "update-tools": [
+ "composer --working-dir=tools/php-cs-fixer update",
+ "composer --working-dir=tools/phpunit update",
+ "composer --working-dir=tools/psalm update",
+ "composer --working-dir=tools/roave-bc-check update"
+ ],
+ "generate-mixin": "php bin/generate.php",
+ "bc-check": "./tools/roave-bc-check/vendor/bin/roave-backward-compatibility-check",
+ "cs-check" : "./tools/php-cs-fixer/vendor/bin/php-cs-fixer check",
+ "cs-fix": "./tools/php-cs-fixer/vendor/bin/php-cs-fixer fix",
+ "static-analysis": "./tools/psalm/vendor/bin/psalm --threads=4 --root=$(pwd)",
+ "test": "./tools/phpunit/vendor/bin/phpunit"
+ }
+}
diff --git a/tools/.phpstan/vendor/webmozart/assert/src/Assert.php b/tools/.phpstan/vendor/webmozart/assert/src/Assert.php
new file mode 100644
index 0000000..31c861b
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/src/Assert.php
@@ -0,0 +1,2620 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Webmozart\Assert;
+
+use ArrayAccess;
+use Closure;
+use Countable;
+use DateTime;
+use DateTimeImmutable;
+use ReflectionFunction;
+use ReflectionProperty;
+use Throwable;
+use Traversable;
+
+/**
+ * Efficient assertions to validate the input/output of your methods.
+ *
+ * @since 1.0
+ *
+ * @author Bernhard Schussek
+ */
+class Assert
+{
+ use Mixin;
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert string $value
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function string(mixed $value, string|callable $message = ''): string
+ {
+ if (!\is_string($value)) {
+ $message = static::resolveMessage($message);
+
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a string. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert non-empty-string $value
+ * @param string|callable():string $message
+ *
+ * @return non-empty-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function stringNotEmpty(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+ static::notSame($value, '', $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert int $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function integer(mixed $value, string|callable $message = ''): int
+ {
+ if (!\is_int($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an integer. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert numeric $value
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function integerish(mixed $value, string|callable $message = ''): int|float|string
+ {
+ if (!\is_numeric($value) || $value != (int) $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an integerish value. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert positive-int $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return positive-int
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function positiveInteger(mixed $value, string|callable $message = ''): int
+ {
+ static::integer($value, $message);
+
+ if ($value < 1) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a positive integer. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ * @psalm-assert non-negative-int $value
+ * @param string|callable():string $message
+ *
+ * @return non-negative-int
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notNegativeInteger(mixed $value, string|callable $message = ''): int
+ {
+ static::integer($value, $message);
+
+ if ($value < 0) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a non negative integer. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ * @psalm-assert negative-int $value
+ * @param string|callable():string $message
+ *
+ * @return negative-int
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function negativeInteger(mixed $value, string|callable $message = ''): int
+ {
+ static::integer($value, $message);
+
+ if ($value >= 0) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a negative integer. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert float $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function float(mixed $value, string|callable $message = ''): float
+ {
+ if (!\is_float($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a float. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert numeric $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function numeric(mixed $value, string|callable $message = ''): int|float|string
+ {
+ if (!\is_numeric($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a numeric. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert positive-int|0 $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return positive-int|0
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function natural(mixed $value, string|callable $message = ''): int
+ {
+ if (!\is_int($value) || $value < 0) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a non-negative integer. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert bool $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function boolean(mixed $value, string|callable $message = ''): bool
+ {
+ if (!\is_bool($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a boolean. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert scalar $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function scalar(mixed $value, string|callable $message = ''): int|bool|float|string
+ {
+ if (!\is_scalar($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a scalar. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert object $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function object(mixed $value, string|callable $message = ''): object
+ {
+ if (!\is_object($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an object. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert object|class-string $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return object|class-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function objectish(mixed $value, string|callable $message = ''): object|string
+ {
+ if (!\is_object($value) && !\is_string($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an objectish value. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ if (\is_string($value) && !\class_exists($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected class to be defined. Got: %s',
+ $value
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert resource $value
+ *
+ * @param string|callable():string $message
+ *
+ * @see https://www.php.net/manual/en/function.get-resource-type.php
+ *
+ * @return resource
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function resource(mixed $value, ?string $type = null, string|callable $message = ''): mixed
+ {
+ if (!\is_resource($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a resource. Got: %s',
+ static::typeToString($value),
+ $type // User supplied message might include the second placeholder.
+ ));
+ }
+
+ if ($type && $type !== \get_resource_type($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a resource of type %2$s. Got: %s',
+ static::typeToString($value),
+ $type
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert object $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isInitialized(mixed $value, string $property, string|callable $message = ''): object
+ {
+ Assert::object($value);
+
+ $reflectionProperty = new ReflectionProperty($value, $property);
+
+ if (!$reflectionProperty->isInitialized($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected property %s to be initialized.',
+ $property,
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert callable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isCallable(mixed $value, string|callable $message = ''): callable
+ {
+ if (!\is_callable($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a callable. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert array $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isArray(mixed $value, string|callable $message = ''): array
+ {
+ if (!\is_array($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an array. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert array|ArrayAccess $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isArrayAccessible(mixed $value, string|callable $message = ''): array|ArrayAccess
+ {
+ if (!\is_array($value) && !($value instanceof ArrayAccess)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an array accessible. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert countable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isCountable(mixed $value, string|callable $message = ''): array|Countable
+ {
+ if (!\is_array($value) && !($value instanceof Countable)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a countable. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isIterable(mixed $value, string|callable $message = ''): iterable
+ {
+ if (!\is_array($value) && !($value instanceof Traversable)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an iterable. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T of object
+ *
+ * @psalm-assert T $value
+ *
+ * @param string|callable():string $message
+ * @param class-string $class
+ *
+ * @return T
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isInstanceOf(mixed $value, mixed $class, string|callable $message = ''): object
+ {
+ static::string($class, 'Expected class as a string. Got: %s');
+
+ if (!($value instanceof $class)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an instance of %2$s. Got: %s',
+ static::typeToString($value),
+ $class
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @template T of object
+ *
+ * @psalm-assert object $value
+ *
+ * @param string|callable():string $message
+ * @param class-string $class
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notInstanceOf(mixed $value, mixed $class, string|callable $message = ''): object
+ {
+ static::string($class, 'Expected class as a string. Got: %s');
+
+ if (!\is_object($value) || $value instanceof $class) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an instance other than %2$s. Got: %s',
+ static::typeToString($value),
+ $class
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @template T of object
+ *
+ * @psalm-assert T $value
+ *
+ * @param iterable> $classes
+ * @param string|callable():string $message
+ *
+ * @return T
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isInstanceOfAny(mixed $value, mixed $classes, string|callable $message = ''): object
+ {
+ static::isIterable($classes);
+
+ foreach ($classes as $class) {
+ static::string($class, 'Expected class as a string. Got: %s');
+
+ if ($value instanceof $class) {
+ return $value;
+ }
+ }
+
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an instance of any of %2$s. Got: %s',
+ static::typeToString($value),
+ \implode(', ', \array_map(static::valueToString(...), \iterator_to_array($classes)))
+ ));
+ }
+
+ /**
+ * @template T
+ *
+ * @psalm-assert object|class-string $value
+ *
+ * @param T $value
+ * @param string|callable():string $message
+ *
+ * @return T
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isNotInstanceOfAny(mixed $value, mixed $classes, string|callable $message = ''): mixed
+ {
+ static::isIterable($classes);
+
+ foreach ($classes as $class) {
+ static::string($class, 'Expected class as a string. Got: %s');
+
+ if ($value instanceof $class) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected not an instance of %2$s. Got: %s',
+ static::typeToString($value),
+ \implode(', ', \array_map(static::valueToString(...), \iterator_to_array($classes)))
+ ));
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T of object
+ *
+ * @psalm-assert T|class-string $value
+ *
+ * @param string|callable():string $message
+ * @param class-string $class
+ *
+ * @return T|class-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isAOf(mixed $value, mixed $class, string|callable $message = ''): object|string
+ {
+ static::string($class, 'Expected class as a string. Got: %s');
+
+ if (!\is_a($value, $class, \is_string($value))) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an instance of this class or to this class among its parents "%2$s". Got: %s',
+ static::valueToString($value),
+ $class
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T
+ *
+ * @psalm-assert object|class-string $value
+ *
+ * @param T $value
+ * @param string|callable():string $message
+ *
+ * @return object|class-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isNotA(mixed $value, mixed $class, string|callable $message = ''): object|string
+ {
+ static::objectish($value, $message);
+ static::string($class, 'Expected class as a string. Got: %s');
+
+ if (\is_a($value, $class, \is_string($value))) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an instance of this class or to this class among its parents other than "%2$s". Got: %s',
+ static::valueToString($value),
+ $class
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert T $value
+ *
+ * @template T as object
+ *
+ * @param array> $classes
+ * @param string|callable():string $message
+ *
+ * @return T
+ * @throws InvalidArgumentException
+ */
+ public static function isAnyOf(mixed $value, mixed $classes, string|callable $message = ''): object|string
+ {
+ static::objectish($value, $message);
+ static::isIterable($classes);
+
+ foreach ($classes as $class) {
+ static::string($class, 'Expected class as a string. Got: %s');
+
+ if (\is_a($value, $class, \is_string($value))) {
+ return $value;
+ }
+ }
+
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an instance of any of this classes or any of those classes among their parents "%2$s". Got: %s',
+ static::valueToString($value),
+ \implode(', ', \iterator_to_array($classes))
+ ));
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert empty $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return empty
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isEmpty(mixed $value, string|callable $message = ''): mixed
+ {
+ if (!empty($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an empty value. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert !empty $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notEmpty(mixed $value, string|callable $message = ''): mixed
+ {
+ if (empty($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a non-empty value. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function null(mixed $value, string|callable $message = ''): null
+ {
+ if (null !== $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected null. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert !null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @template T
+ * @param T|null $value
+ * @return T
+ * @throws InvalidArgumentException
+ */
+ public static function notNull(mixed $value, string|callable $message = ''): mixed
+ {
+ if (null === $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(
+ $message ?: 'Expected a value other than null.'
+ );
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert true $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function true(mixed $value, string|callable $message = ''): true
+ {
+ if (true !== $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to be true. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert false $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function false(mixed $value, string|callable $message = ''): false
+ {
+ if (false !== $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to be false. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert !false $value
+ *
+ * @param string|callable():string $message
+ *
+ * @template T
+ * @param T|false $value
+ * @return T
+ * @throws InvalidArgumentException
+ */
+ public static function notFalse(mixed $value, string|callable $message = ''): mixed
+ {
+ if (false === $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(
+ $message ?: 'Expected a value other than false.'
+ );
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ * @param string $value
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function ip(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ if (false === \filter_var($value, \FILTER_VALIDATE_IP)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to be an IP. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ * @param string $value
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function ipv4(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to be an IPv4. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ * @param string $value
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function ipv6(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to be an IPv6. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ * @param string $value
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function email(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ if (false === \filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to be a valid e-mail address. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Does non-strict comparisons on the items, so ['3', 3] will not pass the assertion.
+ * Note: objects with identical properties are also considered equal.
+ *
+ * @psalm-assert array $values
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function uniqueValues(mixed $values, string|callable $message = ''): array
+ {
+ static::isArray($values);
+
+ $allValues = \count($values);
+ $uniqueValues = \count(\array_unique($values));
+
+ if ($allValues !== $uniqueValues) {
+ $difference = $allValues - $uniqueValues;
+
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an array of unique values, but %s of them %s duplicated',
+ $difference,
+ 1 === $difference ? 'is' : 'are'
+ ));
+ }
+
+ return $values;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function eq(mixed $value, mixed $expect, string|callable $message = ''): mixed
+ {
+ if ($expect != $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value equal to %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($expect)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notEq(mixed $value, mixed $expect, string|callable $message = ''): mixed
+ {
+ if ($expect == $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a different value than %s.',
+ static::valueToString($expect)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function same(mixed $value, mixed $expect, string|callable $message = ''): mixed
+ {
+ if ($expect !== $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value identical to %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($expect)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notSame(mixed $value, mixed $expect, string|callable $message = ''): mixed
+ {
+ if ($expect === $value) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value not identical to %s.',
+ static::valueToString($expect)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function greaterThan(mixed $value, mixed $limit, string|callable $message = ''): mixed
+ {
+ if ($value <= $limit) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value greater than %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($limit)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function greaterThanEq(mixed $value, mixed $limit, string|callable $message = ''): mixed
+ {
+ if ($value < $limit) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($limit)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function lessThan(mixed $value, mixed $limit, string|callable $message = ''): mixed
+ {
+ if ($value >= $limit) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value less than %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($limit)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function lessThanEq(mixed $value, mixed $limit, string|callable $message = ''): mixed
+ {
+ if ($value > $limit) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value less than or equal to %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($limit)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Inclusive range, so Assert::(3, 3, 5) passes.
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function range(mixed $value, mixed $min, mixed $max, string|callable $message = ''): mixed
+ {
+ if ($value < $min || $value > $max) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value between %2$s and %3$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($min),
+ static::valueToString($max)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * A more human-readable alias of Assert::inArray().
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function oneOf(mixed $value, mixed $values, string|callable $message = ''): mixed
+ {
+ static::inArray($value, $values, $message);
+
+ return $value;
+ }
+
+ /**
+ * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion.
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function inArray(mixed $value, mixed $values, string|callable $message = ''): mixed
+ {
+ static::isArray($values);
+
+ if (!\in_array($value, $values, true)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected one of: %2$s. Got: %s',
+ static::valueToString($value),
+ \implode(', ', \array_map(static::valueToString(...), $values))
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * A more human-readable alias of Assert::notInArray().
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notOneOf(mixed $value, mixed $values, string|callable $message = ''): mixed
+ {
+ static::notInArray($value, $values, $message);
+
+ return $value;
+ }
+
+ /**
+ * Check that a value is not present
+ *
+ * Does strict comparison, so Assert::notInArray(3, [1, 2, 3]) will not pass
+ * the assertion, but Assert::notInArray(3, ['3']) will.
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notInArray(mixed $value, mixed $values, string|callable $message = ''): mixed
+ {
+ static::isArray($values);
+
+ if (\in_array($value, $values, true)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: '%2$s was not expected to contain a value. Got: %s',
+ static::valueToString($value),
+ \implode(', ', \array_map(static::valueToString(...), $values))
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function contains(mixed $value, mixed $subString, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($subString);
+
+ if (!\str_contains($value, $subString)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($subString)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notContains(mixed $value, mixed $subString, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($subString);
+
+ if (\str_contains($value, $subString)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: '%2$s was not expected to be contained in a value. Got: %s',
+ static::valueToString($value),
+ static::valueToString($subString)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notWhitespaceOnly(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (\preg_match('/^\s*$/', $value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a non-whitespace string. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function startsWith(mixed $value, mixed $prefix, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($prefix);
+
+ if (!\str_starts_with($value, $prefix)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to start with %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($prefix)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notStartsWith(mixed $value, mixed $prefix, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($prefix);
+
+ if (\str_starts_with($value, $prefix)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value not to start with %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($prefix)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function startsWithLetter(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ $valid = isset($value[0]);
+
+ if ($valid) {
+ $locale = \setlocale(LC_CTYPE, '0');
+ \setlocale(LC_CTYPE, 'C');
+ $valid = \ctype_alpha($value[0]);
+ \setlocale(LC_CTYPE, $locale);
+ }
+
+ if (!$valid) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to start with a letter. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function endsWith(mixed $value, mixed $suffix, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($suffix);
+
+ if (!\str_ends_with($value, $suffix)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to end with %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($suffix)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notEndsWith(mixed $value, mixed $suffix, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($suffix);
+
+ if (\str_ends_with($value, $suffix)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value not to end with %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($suffix)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function regex(mixed $value, mixed $pattern, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($pattern);
+
+ if (!\preg_match($pattern, $value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'The value %s does not match the expected pattern.',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notRegex(mixed $value, mixed $pattern, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($pattern);
+
+ if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'The value %s matches the pattern %s (at offset %d).',
+ static::valueToString($value),
+ static::valueToString($pattern),
+ $matches[0][1]
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function unicodeLetters(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ if (!\preg_match('/^\p{L}+$/u', $value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain only Unicode letters. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function alpha(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ $locale = \setlocale(LC_CTYPE, '0');
+ \setlocale(LC_CTYPE, 'C');
+ $valid = !\ctype_alpha($value);
+ \setlocale(LC_CTYPE, $locale);
+
+ if ($valid) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain only letters. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function digits(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ $locale = \setlocale(LC_CTYPE, '0');
+ \setlocale(LC_CTYPE, 'C');
+ $valid = !\ctype_digit($value);
+ \setlocale(LC_CTYPE, $locale);
+
+ if ($valid) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain digits only. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function alnum(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ $locale = \setlocale(LC_CTYPE, '0');
+ \setlocale(LC_CTYPE, 'C');
+ $valid = !\ctype_alnum($value);
+ \setlocale(LC_CTYPE, $locale);
+
+ if ($valid) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain letters and digits only. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert lowercase-string $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return lowercase-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function lower(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ $locale = \setlocale(LC_CTYPE, '0');
+ \setlocale(LC_CTYPE, 'C');
+ $valid = !\ctype_lower($value);
+ \setlocale(LC_CTYPE, $locale);
+
+ if ($valid) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain lowercase characters only. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert string $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function upper(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ $locale = \setlocale(LC_CTYPE, '0');
+ \setlocale(LC_CTYPE, 'C');
+ $valid = !\ctype_upper($value);
+ \setlocale(LC_CTYPE, $locale);
+
+ if ($valid) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain uppercase characters only. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function length(mixed $value, mixed $length, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::integerish($length);
+
+ if ($length !== static::strlen($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain %2$s characters. Got: %s',
+ static::valueToString($value),
+ $length
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Inclusive min.
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function minLength(mixed $value, mixed $min, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::integerish($min);
+
+ if (static::strlen($value) < $min) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
+ static::valueToString($value),
+ $min
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Inclusive max.
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function maxLength(mixed $value, mixed $max, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::integerish($max);
+
+ if (static::strlen($value) > $max) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
+ static::valueToString($value),
+ $max
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Inclusive, so Assert::lengthBetween('asd', 3, 5); passes the assertion.
+ *
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function lengthBetween(mixed $value, mixed $min, mixed $max, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::integerish($min);
+ static::integerish($max);
+
+ $length = static::strlen($value);
+
+ if ($length < $min || $length > $max) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
+ static::valueToString($value),
+ $min,
+ $max
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file.
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function fileExists(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (!\file_exists($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'The path %s does not exist.',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function file(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (!\is_file($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'The path %s is not a file.',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function directory(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (!\is_dir($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'The path %s is not a directory.',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function readable(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (!\is_readable($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'The path %s is not readable.',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function writable(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (!\is_writable($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'The path %s is not writable.',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-assert class-string $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return class-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function classExists(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (!\class_exists($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an existing class name. Got: %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template ExpectedType of object
+ *
+ * @psalm-assert class-string $value
+ *
+ * @param class-string $class
+ * @param string|callable():string $message
+ *
+ * @return class-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function subclassOf(mixed $value, mixed $class, string|callable $message = ''): string
+ {
+ static::string($value);
+ static::string($class);
+
+ if (!\is_subclass_of($value, $class)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected a sub-class of %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($class)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-assert class-string $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return class-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function interfaceExists(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value);
+
+ if (!\interface_exists($value)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an existing interface name. got %s',
+ static::valueToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template ExpectedType of object
+ *
+ * @psalm-assert class-string|ExpectedType $value
+ *
+ * @param class-string $interface
+ * @param string|callable():string $message
+ *
+ * @return class-string|ExpectedType
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function implementsInterface(mixed $value, mixed $interface, string|callable $message = ''): object|string
+ {
+ static::objectish($value);
+
+ $implements = \class_implements($value);
+
+ static::isArray($implements);
+
+ if (!\in_array($interface, $implements, true)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an implementation of %2$s. Got: %s',
+ static::valueToString($value),
+ static::valueToString($interface)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|object $classOrObject
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function propertyExists(mixed $classOrObject, mixed $property, string|callable $message = ''): object|string
+ {
+ static::objectish($classOrObject);
+
+ if (!\property_exists($classOrObject, $property)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected the property %s to exist.',
+ static::valueToString($property)
+ ));
+ }
+
+ return $classOrObject;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T as class-string|object
+ * @param T $classOrObject
+ * @param string|callable():string $message
+ *
+ * @return T
+ * @throws InvalidArgumentException
+ */
+ public static function propertyNotExists(mixed $classOrObject, mixed $property, string|callable $message = ''): mixed
+ {
+ if (!(\is_string($classOrObject) || \is_object($classOrObject)) || \property_exists($classOrObject, $property)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected the property %s to not exist.',
+ static::valueToString($property)
+ ));
+ }
+
+ return $classOrObject;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T as class-string|object
+ * @param T $classOrObject
+ * @param string|callable():string $message
+ *
+ * @return T
+ * @throws InvalidArgumentException
+ */
+ public static function methodExists(mixed $classOrObject, mixed $method, string|callable $message = ''): object|string
+ {
+ static::objectish($classOrObject);
+
+ if (!\method_exists($classOrObject, $method)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected the method %s to exist.',
+ static::valueToString($method)
+ ));
+ }
+
+ return $classOrObject;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T as class-string|object
+ * @param T $classOrObject
+ * @param string|callable():string $message
+ *
+ * @return T
+ * @throws InvalidArgumentException
+ */
+ public static function methodNotExists(mixed $classOrObject, mixed $method, string|callable $message = ''): mixed
+ {
+ static::objectish($classOrObject);
+
+ if (\method_exists($classOrObject, $method)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected the method %s to not exist.',
+ static::valueToString($method)
+ ));
+ }
+
+ return $classOrObject;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|int $key
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function keyExists(mixed $array, string|int $key, string|callable $message = ''): array
+ {
+ static::isArray($array, $message);
+
+ if (!(isset($array[$key]) || \array_key_exists($key, $array))) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected the key %s to exist.',
+ static::valueToString($key)
+ ));
+ }
+
+ return $array;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|int $key
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function keyNotExists(mixed $array, string|int $key, string|callable $message = ''): array
+ {
+ static::isArray($array, $message);
+
+ if (isset($array[$key]) || \array_key_exists($key, $array)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected the key %s to not exist.',
+ static::valueToString($key)
+ ));
+ }
+
+ return $array;
+ }
+
+ /**
+ * Checks if a value is a valid array key (int or string).
+ *
+ * @psalm-pure
+ *
+ * @psalm-assert array-key $value
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function validArrayKey(mixed $value, string|callable $message = ''): string|int
+ {
+ if (!(\is_int($value) || \is_string($value))) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected string or integer. Got: %s',
+ static::typeToString($value)
+ ));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function count(mixed $array, mixed $number, string|callable $message = ''): array|Countable
+ {
+ static::isCountable($array);
+ static::integerish($number);
+
+ static::eq(
+ \count($array),
+ $number,
+ fn () => static::resolveMessage($message) ?: \sprintf(
+ $message ?: 'Expected an array to contain %d elements. Got: %d.',
+ $number,
+ \count($array)
+ )
+ );
+
+ return $array;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function minCount(mixed $array, mixed $min, string|callable $message = ''): array|Countable
+ {
+ static::isCountable($array);
+ static::integerish($min);
+
+ if (\count($array) < $min) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an array to contain at least %2$d elements. Got: %d',
+ \count($array),
+ $min
+ ));
+ }
+
+ return $array;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function maxCount(mixed $array, mixed $max, string|callable $message = ''): array|Countable
+ {
+ static::isCountable($array);
+ static::integerish($max);
+
+ if (\count($array) > $max) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an array to contain at most %2$d elements. Got: %d',
+ \count($array),
+ $max
+ ));
+ }
+
+ return $array;
+ }
+
+ /**
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function countBetween(mixed $array, mixed $min, mixed $max, string|callable $message = ''): array|Countable
+ {
+ static::isCountable($array);
+ static::integerish($min);
+ static::integerish($max);
+
+ $count = \count($array);
+
+ if ($count < $min || $count > $max) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d',
+ $count,
+ $min,
+ $max
+ ));
+ }
+
+ return $array;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert list $array
+ *
+ * @param string|callable():string $message
+ *
+ * @return list
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isList(mixed $array, string|callable $message = ''): array
+ {
+ if (!\is_array($array) || !\array_is_list($array)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(
+ $message ?: 'Expected list - non-associative array.'
+ );
+ }
+
+ return $array;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert non-empty-list $array
+ *
+ * @param string|callable():string $message
+ *
+ * @return non-empty-list
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isNonEmptyList(mixed $array, string|callable $message = ''): array
+ {
+ static::isList($array, $message);
+ static::notEmpty($array, $message);
+
+ return $array;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T
+ *
+ * @psalm-assert array $array
+ *
+ * @param mixed|array $array
+ * @param string|callable():string $message
+ *
+ * @return array
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isMap(mixed $array, string|callable $message = ''): array
+ {
+ static::isArray($array, $message);
+
+ if (\count($array) > 0 && \array_is_list($array)) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(
+ $message ?: 'Expected map - associative array with string keys.'
+ );
+ }
+
+ return $array;
+ }
+
+ /**
+ * @param callable $callable
+ * @param string|callable():string $message
+ *
+ * @return Closure|callable-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isStatic(mixed $callable, string|callable $message = ''): Closure|string
+ {
+ static::isCallable($callable, $message);
+
+ $callable = static::callableToClosure($callable);
+
+ $reflection = new ReflectionFunction($callable);
+
+ if (!$reflection->isStatic()) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(
+ $message ?: 'Closure is not static.'
+ );
+ }
+
+ return $callable;
+ }
+
+ /**
+ * @param callable $callable
+ * @param string|callable():string $message
+ *
+ * @return Closure|callable-string
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function notStatic(mixed $callable, string|callable $message = ''): Closure|string
+ {
+ static::isCallable($callable, $message);
+
+ $callable = static::callableToClosure($callable);
+
+ $reflection = new ReflectionFunction($callable);
+
+ if ($reflection->isStatic()) {
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(
+ $message ?: 'Closure is not static.'
+ );
+ }
+
+ return $callable;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @template T
+ *
+ * @psalm-assert array $array
+ * @psalm-assert !empty $array
+ *
+ * @param array $array
+ * @param string|callable():string $message
+ *
+ * @return non-empty-array
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function isNonEmptyMap(mixed $array, string|callable $message = ''): array
+ {
+ static::isMap($array, $message);
+ static::notEmpty($array, $message);
+
+ return $array;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function uuid(mixed $value, string|callable $message = ''): string
+ {
+ static::string($value, $message);
+
+ $uuid = '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}';
+
+ // URN form as specified by RFC 9562, e.g. "urn:uuid:ff6f8cb0-...".
+ if (\str_starts_with($value, 'urn:uuid:') && \preg_match('/^urn:uuid:'.$uuid.'$/D', $value)) {
+ return $value;
+ }
+
+ // "uuid:" prefix, optionally combined with the curly-braced form.
+ if (\str_starts_with($value, 'uuid:') && \preg_match('/^uuid:(?:'.$uuid.'|\{'.$uuid.'\})$/D', $value)) {
+ return $value;
+ }
+
+ // Curly-braced form; the braces must be a matching pair.
+ if (\str_starts_with($value, '{') && \str_ends_with($value, '}') && \preg_match('/^\{'.$uuid.'\}$/D', $value)) {
+ return $value;
+ }
+
+ // Plain form, including the nil UUID with all 128 bits set to zero.
+ if (\preg_match('/^'.$uuid.'$/D', $value)) {
+ return $value;
+ }
+
+ $message = self::resolveMessage($message);
+ static::reportInvalidArgument(\sprintf(
+ $message ?: 'Value %s is not a valid UUID.',
+ static::valueToString($value)
+ ));
+ }
+
+ /**
+ * @template T as callable
+ *
+ * @param T $expression
+ * @param string|callable():string $message
+ * @param class-string $class
+ *
+ * @return T
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function throws(mixed $expression, string $class = Throwable::class, string|callable $message = ''): callable
+ {
+ static::string($class);
+ static::isCallable($expression);
+
+ $actual = 'none';
+
+ try {
+ $expression();
+ } catch (Throwable $e) {
+ $actual = \get_class($e);
+ if ($e instanceof $class) {
+ return $expression;
+ }
+ }
+
+ $message = self::resolveMessage($message);
+
+ static::reportInvalidArgument($message ?: \sprintf(
+ 'Expected to throw "%s", got "%s"',
+ $class,
+ $actual
+ ));
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @return Closure|callable-string
+ */
+ protected static function callableToClosure(callable $callable): Closure|string
+ {
+ if (\is_string($callable) && \function_exists($callable)) {
+ return $callable;
+ }
+
+ if ($callable instanceof Closure) {
+ return $callable;
+ }
+
+ return $callable(...);
+ }
+
+ /**
+ * @psalm-pure
+ */
+ protected static function valueToString(mixed $value): string
+ {
+ if (null === $value) {
+ return 'null';
+ }
+
+ if (true === $value) {
+ return 'true';
+ }
+
+ if (false === $value) {
+ return 'false';
+ }
+
+ if (\is_array($value)) {
+ return 'array';
+ }
+
+ if (\is_object($value)) {
+ if (\method_exists($value, '__toString')) {
+ return \get_class($value).': '.self::valueToString($value->__toString());
+ }
+
+ if ($value instanceof DateTime || $value instanceof DateTimeImmutable) {
+ return \get_class($value).': '.self::valueToString($value->format('c'));
+ }
+
+ if (\enum_exists(\get_class($value))) {
+ return \get_class($value).'::'.$value->name;
+ }
+
+ return \get_class($value);
+ }
+
+ if (\is_resource($value)) {
+ return 'resource';
+ }
+
+ if (\is_string($value)) {
+ return '"'.$value.'"';
+ }
+
+ return (string) $value;
+ }
+
+ /**
+ * @psalm-pure
+ */
+ protected static function typeToString(mixed $value): string
+ {
+ return \is_object($value) ? \get_class($value) : \gettype($value);
+ }
+
+ protected static function strlen(string $value): int
+ {
+ if (!\function_exists('mb_detect_encoding')) {
+ return \strlen($value);
+ }
+
+ if (false === $encoding = \mb_detect_encoding($value)) {
+ return \strlen($value);
+ }
+
+ return \mb_strlen($value, $encoding);
+ }
+
+ /**
+ * @psalm-pure this method is not supposed to perform side effects
+ *
+ * @throws InvalidArgumentException
+ */
+ protected static function reportInvalidArgument(string $message): never
+ {
+ throw new InvalidArgumentException($message);
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @param string|callable():string $message
+ */
+ protected static function resolveMessage(string|callable $message): string
+ {
+ return \is_callable($message) ? $message() : $message;
+ }
+
+ private function __construct()
+ {
+ }
+}
diff --git a/tools/.phpstan/vendor/webmozart/assert/src/HasAssert.php b/tools/.phpstan/vendor/webmozart/assert/src/HasAssert.php
new file mode 100644
index 0000000..11d144a
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/src/HasAssert.php
@@ -0,0 +1,179 @@
+ true,
+ 'nullorstring' => true,
+ 'allstring' => true,
+ 'allnullorstring' => true,
+ 'stringnotempty' => true,
+ 'nullorstringnotempty' => true,
+ 'allstringnotempty' => true,
+ 'allnullorstringnotempty' => true,
+ 'integer' => true,
+ 'nullorinteger' => true,
+ 'allinteger' => true,
+ 'allnullorinteger' => true,
+ 'integerish' => true,
+ 'nullorintegerish' => true,
+ 'allintegerish' => true,
+ 'allnullorintegerish' => true,
+ 'positiveinteger' => true,
+ 'nullorpositiveinteger' => true,
+ 'allpositiveinteger' => true,
+ 'allnullorpositiveinteger' => true,
+ 'notnegativeinteger' => true,
+ 'nullornotnegativeinteger' => true,
+ 'allnotnegativeinteger' => true,
+ 'allnullornotnegativeinteger' => true,
+ 'negativeinteger' => true,
+ 'nullornegativeinteger' => true,
+ 'allnegativeinteger' => true,
+ 'allnullornegativeinteger' => true,
+ 'float' => true,
+ 'nullorfloat' => true,
+ 'allfloat' => true,
+ 'allnullorfloat' => true,
+ 'numeric' => true,
+ 'nullornumeric' => true,
+ 'allnumeric' => true,
+ 'allnullornumeric' => true,
+ 'natural' => true,
+ 'nullornatural' => true,
+ 'allnatural' => true,
+ 'allnullornatural' => true,
+ 'boolean' => true,
+ 'nullorboolean' => true,
+ 'allboolean' => true,
+ 'allnullorboolean' => true,
+ 'scalar' => true,
+ 'nullorscalar' => true,
+ 'allscalar' => true,
+ 'allnullorscalar' => true,
+ 'object' => true,
+ 'nullorobject' => true,
+ 'allobject' => true,
+ 'allnullorobject' => true,
+ 'objectish' => true,
+ 'nullorobjectish' => true,
+ 'allobjectish' => true,
+ 'allnullorobjectish' => true,
+ 'resource' => true,
+ 'nullorresource' => true,
+ 'allresource' => true,
+ 'allnullorresource' => true,
+ 'iscallable' => true,
+ 'nulloriscallable' => true,
+ 'alliscallable' => true,
+ 'allnulloriscallable' => true,
+ 'isarray' => true,
+ 'nullorisarray' => true,
+ 'allisarray' => true,
+ 'allnullorisarray' => true,
+ 'isarrayaccessible' => true,
+ 'nullorisarrayaccessible' => true,
+ 'allisarrayaccessible' => true,
+ 'allnullorisarrayaccessible' => true,
+ 'iscountable' => true,
+ 'nulloriscountable' => true,
+ 'alliscountable' => true,
+ 'allnulloriscountable' => true,
+ 'isiterable' => true,
+ 'nullorisiterable' => true,
+ 'allisiterable' => true,
+ 'allnullorisiterable' => true,
+ 'isinstanceof' => true,
+ 'nullorisinstanceof' => true,
+ 'allisinstanceof' => true,
+ 'allnullorisinstanceof' => true,
+ 'notinstanceof' => true,
+ 'allnullornotinstanceof' => true,
+ 'isinstanceofany' => true,
+ 'nullorisinstanceofany' => true,
+ 'allisinstanceofany' => true,
+ 'allnullorisinstanceofany' => true,
+ 'isnotinstanceofany' => true,
+ 'nullorisnotinstanceofany' => true,
+ 'allisnotinstanceofany' => true,
+ 'allnullorisnotinstanceofany' => true,
+ 'isaof' => true,
+ 'nullorisaof' => true,
+ 'allisaof' => true,
+ 'allnullorisaof' => true,
+ 'isnota' => true,
+ 'allnullorisnota' => true,
+ 'isanyof' => true,
+ 'nullorisanyof' => true,
+ 'allisanyof' => true,
+ 'allnullorisanyof' => true,
+ 'isempty' => true,
+ 'nullorisempty' => true,
+ 'allisempty' => true,
+ 'allnullorisempty' => true,
+ 'notempty' => true,
+ 'allnullornotempty' => true,
+ 'null' => true,
+ 'allnull' => true,
+ 'notnull' => true,
+ 'true' => true,
+ 'nullortrue' => true,
+ 'alltrue' => true,
+ 'allnullortrue' => true,
+ 'false' => true,
+ 'nullorfalse' => true,
+ 'allfalse' => true,
+ 'allnullorfalse' => true,
+ 'notfalse' => true,
+ 'allnullornotfalse' => true,
+ 'uniquevalues' => true,
+ 'nulloruniquevalues' => true,
+ 'alluniquevalues' => true,
+ 'allnulloruniquevalues' => true,
+ 'lower' => true,
+ 'nullorlower' => true,
+ 'alllower' => true,
+ 'allnullorlower' => true,
+ 'upper' => true,
+ 'allnullorupper' => true,
+ 'classexists' => true,
+ 'nullorclassexists' => true,
+ 'allclassexists' => true,
+ 'allnullorclassexists' => true,
+ 'subclassof' => true,
+ 'nullorsubclassof' => true,
+ 'allsubclassof' => true,
+ 'allnullorsubclassof' => true,
+ 'interfaceexists' => true,
+ 'nullorinterfaceexists' => true,
+ 'allinterfaceexists' => true,
+ 'allnullorinterfaceexists' => true,
+ 'implementsinterface' => true,
+ 'nullorimplementsinterface' => true,
+ 'allimplementsinterface' => true,
+ 'allnullorimplementsinterface' => true,
+ 'validarraykey' => true,
+ 'nullorvalidarraykey' => true,
+ 'allvalidarraykey' => true,
+ 'allnullorvalidarraykey' => true,
+ 'islist' => true,
+ 'nullorislist' => true,
+ 'allislist' => true,
+ 'allnullorislist' => true,
+ 'isnonemptylist' => true,
+ 'nullorisnonemptylist' => true,
+ 'allisnonemptylist' => true,
+ 'allnullorisnonemptylist' => true,
+ 'ismap' => true,
+ 'nullorismap' => true,
+ 'allismap' => true,
+ 'allnullorismap' => true,
+ 'isnonemptymap' => true,
+ 'allnullorisnonemptymap' => true,
+ ];
+}
diff --git a/tools/.phpstan/vendor/webmozart/assert/src/InvalidArgumentException.php b/tools/.phpstan/vendor/webmozart/assert/src/InvalidArgumentException.php
new file mode 100644
index 0000000..fa2b223
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/src/InvalidArgumentException.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Webmozart\Assert;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}
diff --git a/tools/.phpstan/vendor/webmozart/assert/src/Mixin.php b/tools/.phpstan/vendor/webmozart/assert/src/Mixin.php
new file mode 100644
index 0000000..a714f16
--- /dev/null
+++ b/tools/.phpstan/vendor/webmozart/assert/src/Mixin.php
@@ -0,0 +1,5827 @@
+ $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allString(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::string($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrString(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::string($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert non-empty-string|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return non-empty-string|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrStringNotEmpty(mixed $value, callable|string $message = ''): ?string
+ {
+ null === $value || static::stringNotEmpty($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allStringNotEmpty(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::stringNotEmpty($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrStringNotEmpty(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::stringNotEmpty($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert int|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return int|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrInteger(mixed $value, callable|string $message = ''): ?int
+ {
+ null === $value || static::integer($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::integer($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::integer($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert numeric|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return numeric|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrIntegerish(mixed $value, callable|string $message = ''): string|int|float|null
+ {
+ null === $value || static::integerish($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allIntegerish(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::integerish($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrIntegerish(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::integerish($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert positive-int|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return positive-int|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrPositiveInteger(mixed $value, callable|string $message = ''): ?int
+ {
+ null === $value || static::positiveInteger($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allPositiveInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::positiveInteger($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrPositiveInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::positiveInteger($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert non-negative-int|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return non-negative-int|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrNotNegativeInteger(mixed $value, callable|string $message = ''): ?int
+ {
+ null === $value || static::notNegativeInteger($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNotNegativeInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::notNegativeInteger($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrNotNegativeInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::notNegativeInteger($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert negative-int|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return negative-int|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrNegativeInteger(mixed $value, callable|string $message = ''): ?int
+ {
+ null === $value || static::negativeInteger($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNegativeInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::negativeInteger($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrNegativeInteger(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::negativeInteger($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert float|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return float|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrFloat(mixed $value, callable|string $message = ''): ?float
+ {
+ null === $value || static::float($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allFloat(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::float($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrFloat(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::float($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert numeric|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return numeric|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrNumeric(mixed $value, callable|string $message = ''): string|int|float|null
+ {
+ null === $value || static::numeric($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNumeric(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::numeric($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrNumeric(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::numeric($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert positive-int|0|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return positive-int|0|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrNatural(mixed $value, callable|string $message = ''): ?int
+ {
+ null === $value || static::natural($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNatural(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::natural($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrNatural(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::natural($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert bool|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return bool|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrBoolean(mixed $value, callable|string $message = ''): ?bool
+ {
+ null === $value || static::boolean($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allBoolean(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::boolean($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrBoolean(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::boolean($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert scalar|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return scalar|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrScalar(mixed $value, callable|string $message = ''): string|int|float|bool|null
+ {
+ null === $value || static::scalar($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allScalar(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ static::scalar($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return iterable
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function allNullOrScalar(mixed $value, callable|string $message = ''): iterable
+ {
+ static::isIterable($value);
+
+ foreach ($value as $entry) {
+ null === $entry || static::scalar($entry, $message);
+ }
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert object|null $value
+ *
+ * @param string|callable():string $message
+ *
+ * @return object|null
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function nullOrObject(mixed $value, callable|string $message = ''): ?object
+ {
+ null === $value || static::object($value, $message);
+
+ return $value;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @psalm-assert iterable