diff --git a/.phive/phars.xml b/.phive/phars.xml
index 51665c5..e57059a 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -3,5 +3,4 @@
-
diff --git a/build.xml b/build.xml
index a01110c..9c730f7 100644
--- a/build.xml
+++ b/build.xml
@@ -17,10 +17,27 @@
-
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/phpstan.neon b/phpstan.neon
index e9a9e7e..e7f0517 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -3,3 +3,46 @@ parameters:
paths:
- src
- tests
+
+ checkTooWideReturnTypesInProtectedAndPublicMethods: true
+ reportAlwaysTrueInLastCondition: true
+ reportPossiblyNonexistentConstantArrayOffset: true
+ reportPossiblyNonexistentGeneralArrayOffset: true
+ treatPhpDocTypesAsCertain: false
+
+ strictRules:
+ allRules: false
+ booleansInConditions: true
+ closureUsesThis: true
+ disallowedBacktick: true
+ disallowedEmpty: true
+ disallowedImplicitArrayCreation: true
+ disallowedLooseComparison: true
+ disallowedShortTernary: true
+ illegalConstructorMethodCall: true
+ matchingInheritedMethodNames: true
+ noVariableVariables: true
+ numericOperandsInArithmeticOperators: true
+ overwriteVariablesWithLoop: true
+ requireParentConstructorCall: true
+ strictArrayFilter: true
+ strictFunctionCalls: true
+ switchConditionsMatchingType: true
+ uselessCast: true
+
+ ergebnis:
+ allRules: false
+ final:
+ enabled: true
+ privateInFinalClass:
+ enabled: true
+
+ type_coverage:
+ declare: 100
+ return: 100
+ param: 100
+ property: 100
+ constant: 100
+
+includes:
+ - phar://phpstan.phar/conf/bleedingEdge.neon
diff --git a/tools/.phpstan/composer.json b/tools/.phpstan/composer.json
new file mode 100644
index 0000000..8ef20ff
--- /dev/null
+++ b/tools/.phpstan/composer.json
@@ -0,0 +1,14 @@
+{
+ "require-dev": {
+ "phpstan/phpstan": "^2.1.8",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan-strict-rules": "^2.0.3",
+ "tomasvotruba/type-coverage": "^2.0.2",
+ "ergebnis/phpstan-rules": "^2.8.0"
+ },
+ "config": {
+ "allow-plugins": {
+ "phpstan/extension-installer": true
+ }
+ }
+}
diff --git a/tools/.phpstan/composer.lock b/tools/.phpstan/composer.lock
new file mode 100644
index 0000000..6c4d892
--- /dev/null
+++ b/tools/.phpstan/composer.lock
@@ -0,0 +1,388 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "7c032feec49c9dbbdaa0d52198cabb4c",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "ergebnis/phpstan-rules",
+ "version": "2.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/phpstan-rules.git",
+ "reference": "30e790621fbad05573ef9cd355279fff5122e080"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/30e790621fbad05573ef9cd355279fff5122e080",
+ "reference": "30e790621fbad05573ef9cd355279fff5122e080",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
+ "phpstan/phpstan": "^2.0.0"
+ },
+ "require-dev": {
+ "doctrine/orm": "^2.20.0 || ^3.3.0",
+ "ergebnis/composer-normalize": "^2.45.0",
+ "ergebnis/license": "^2.6.0",
+ "ergebnis/php-cs-fixer-config": "^6.43.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.18.0",
+ "nette/di": "^3.1.10",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan-deprecation-rules": "^2.0.1",
+ "phpstan/phpstan-phpunit": "^2.0.4",
+ "phpstan/phpstan-strict-rules": "^2.0.3",
+ "phpunit/phpunit": "^9.6.21",
+ "psr/container": "^2.0.2",
+ "symfony/finder": "^5.4.45",
+ "symfony/process": "^5.4.47"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\PHPStan\\Rules\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides rules for phpstan/phpstan.",
+ "homepage": "https://github.com/ergebnis/phpstan-rules",
+ "keywords": [
+ "PHPStan",
+ "phpstan-rules"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/phpstan-rules/issues",
+ "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/phpstan-rules"
+ },
+ "time": "2025-02-18T11:20:05+00:00"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v4.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
+ "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
+ "shasum": ""
+ },
+ "require": {
+ "php": "8.0 - 8.4"
+ },
+ "conflict": {
+ "nette/finder": "<3",
+ "nette/schema": "<1.2.2"
+ },
+ "require-dev": {
+ "jetbrains/phpstorm-attributes": "dev-master",
+ "nette/tester": "^2.5",
+ "phpstan/phpstan": "^1.0",
+ "tracy/tracy": "^2.9"
+ },
+ "suggest": {
+ "ext-gd": "to use Image",
+ "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+ "ext-json": "to use Nette\\Utils\\Json",
+ "ext-mbstring": "to use Strings::lower() etc...",
+ "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "array",
+ "core",
+ "datetime",
+ "images",
+ "json",
+ "nette",
+ "paginator",
+ "password",
+ "slugify",
+ "string",
+ "unicode",
+ "utf-8",
+ "utility",
+ "validation"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/utils/issues",
+ "source": "https://github.com/nette/utils/tree/v4.0.5"
+ },
+ "time": "2024-08-07T15:39:19+00:00"
+ },
+ {
+ "name": "phpstan/extension-installer",
+ "version": "1.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/extension-installer.git",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.0",
+ "php": "^7.2 || ^8.0",
+ "phpstan/phpstan": "^1.9.0 || ^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.0",
+ "php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PHPStan\\ExtensionInstaller\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\ExtensionInstaller\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Composer plugin for automatic installation of PHPStan extensions",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpstan/extension-installer/issues",
+ "source": "https://github.com/phpstan/extension-installer/tree/1.4.3"
+ },
+ "time": "2024-09-04T20:21:43+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.1.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f",
+ "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-09T09:30:48+00:00"
+ },
+ {
+ "name": "phpstan/phpstan-strict-rules",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-strict-rules.git",
+ "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba",
+ "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.0.4"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Extra strict and opinionated rules for PHPStan",
+ "support": {
+ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
+ "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3"
+ },
+ "time": "2025-01-21T10:52:14+00:00"
+ },
+ {
+ "name": "tomasvotruba/type-coverage",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/TomasVotruba/type-coverage.git",
+ "reference": "d033429580f2c18bda538fa44f2939236a990e0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c",
+ "reference": "d033429580f2c18bda538fa44f2939236a990e0c",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^3.2 || ^4.0",
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.0"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "config/extension.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "TomasVotruba\\TypeCoverage\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Measure type coverage of your project",
+ "keywords": [
+ "phpstan-extension",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/TomasVotruba/type-coverage/issues",
+ "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.me/rectorphp",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/tomasvotruba",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-07T00:10:26+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {},
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/tools/.phpstan/vendor/autoload.php b/tools/.phpstan/vendor/autoload.php
new file mode 100644
index 0000000..87b8902
--- /dev/null
+++ b/tools/.phpstan/vendor/autoload.php
@@ -0,0 +1,25 @@
+realpath = realpath($opened_path) ?: $opened_path;
+ $opened_path = $this->realpath;
+ $this->handle = fopen($this->realpath, $mode);
+ $this->position = 0;
+
+ return (bool) $this->handle;
+ }
+
+ public function stream_read($count)
+ {
+ $data = fread($this->handle, $count);
+
+ if ($this->position === 0) {
+ $data = preg_replace('{^#!.*\r?\n}', '', $data);
+ }
+
+ $this->position += strlen($data);
+
+ return $data;
+ }
+
+ public function stream_cast($castAs)
+ {
+ return $this->handle;
+ }
+
+ public function stream_close()
+ {
+ fclose($this->handle);
+ }
+
+ public function stream_lock($operation)
+ {
+ return $operation ? flock($this->handle, $operation) : true;
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ if (0 === fseek($this->handle, $offset, $whence)) {
+ $this->position = ftell($this->handle);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function stream_tell()
+ {
+ return $this->position;
+ }
+
+ public function stream_eof()
+ {
+ return feof($this->handle);
+ }
+
+ public function stream_stat()
+ {
+ return array();
+ }
+
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ return true;
+ }
+
+ public function url_stat($path, $flags)
+ {
+ $path = substr($path, 17);
+ if (file_exists($path)) {
+ return stat($path);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (
+ (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+ || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+ ) {
+ return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan');
+ }
+}
+
+return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan';
diff --git a/tools/.phpstan/vendor/bin/phpstan.phar b/tools/.phpstan/vendor/bin/phpstan.phar
new file mode 100755
index 0000000..fecf96f
--- /dev/null
+++ b/tools/.phpstan/vendor/bin/phpstan.phar
@@ -0,0 +1,119 @@
+#!/usr/bin/env php
+realpath = realpath($opened_path) ?: $opened_path;
+ $opened_path = $this->realpath;
+ $this->handle = fopen($this->realpath, $mode);
+ $this->position = 0;
+
+ return (bool) $this->handle;
+ }
+
+ public function stream_read($count)
+ {
+ $data = fread($this->handle, $count);
+
+ if ($this->position === 0) {
+ $data = preg_replace('{^#!.*\r?\n}', '', $data);
+ }
+
+ $this->position += strlen($data);
+
+ return $data;
+ }
+
+ public function stream_cast($castAs)
+ {
+ return $this->handle;
+ }
+
+ public function stream_close()
+ {
+ fclose($this->handle);
+ }
+
+ public function stream_lock($operation)
+ {
+ return $operation ? flock($this->handle, $operation) : true;
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ if (0 === fseek($this->handle, $offset, $whence)) {
+ $this->position = ftell($this->handle);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function stream_tell()
+ {
+ return $this->position;
+ }
+
+ public function stream_eof()
+ {
+ return feof($this->handle);
+ }
+
+ public function stream_stat()
+ {
+ return array();
+ }
+
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ return true;
+ }
+
+ public function url_stat($path, $flags)
+ {
+ $path = substr($path, 17);
+ if (file_exists($path)) {
+ return stat($path);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (
+ (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+ || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+ ) {
+ return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar');
+ }
+}
+
+return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar';
diff --git a/tools/.phpstan/vendor/composer/ClassLoader.php b/tools/.phpstan/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000..7824d8f
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/ClassLoader.php
@@ -0,0 +1,579 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/tools/.phpstan/vendor/composer/InstalledVersions.php b/tools/.phpstan/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000..6d29bff
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/InstalledVersions.php
@@ -0,0 +1,378 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool
+ */
+ private static $installedIsLocalDir;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+
+ // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+ // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+ // so we have to assume it does not, and that may result in duplicate data being returned when listing
+ // all installed packages for example
+ self::$installedIsLocalDir = false;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+ $copiedLocalDir = false;
+
+ if (self::$canGetVendors) {
+ $selfDir = strtr(__DIR__, '\\', '/');
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ $vendorDir = strtr($vendorDir, '\\', '/');
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ self::$installedByVendor[$vendorDir] = $required;
+ $installed[] = $required;
+ if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+ self::$installed = $required;
+ self::$installedIsLocalDir = true;
+ }
+ }
+ if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+ $copiedLocalDir = true;
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array() && !$copiedLocalDir) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/tools/.phpstan/vendor/composer/LICENSE b/tools/.phpstan/vendor/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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/composer/autoload_classmap.php b/tools/.phpstan/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..feb7a8f
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/autoload_classmap.php
@@ -0,0 +1,59 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+ 'Nette\\ArgumentOutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\DeprecatedException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\DirectoryNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\FileNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\HtmlStringable' => $vendorDir . '/nette/utils/src/HtmlStringable.php',
+ 'Nette\\IOException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\InvalidArgumentException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\InvalidStateException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\Iterators\\CachingIterator' => $vendorDir . '/nette/utils/src/Iterators/CachingIterator.php',
+ 'Nette\\Iterators\\Mapper' => $vendorDir . '/nette/utils/src/Iterators/Mapper.php',
+ 'Nette\\Localization\\ITranslator' => $vendorDir . '/nette/utils/src/compatibility.php',
+ 'Nette\\Localization\\Translator' => $vendorDir . '/nette/utils/src/Translator.php',
+ 'Nette\\MemberAccessException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\NotImplementedException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\NotSupportedException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\OutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\SmartObject' => $vendorDir . '/nette/utils/src/SmartObject.php',
+ 'Nette\\StaticClass' => $vendorDir . '/nette/utils/src/StaticClass.php',
+ 'Nette\\UnexpectedValueException' => $vendorDir . '/nette/utils/src/exceptions.php',
+ 'Nette\\Utils\\ArrayHash' => $vendorDir . '/nette/utils/src/Utils/ArrayHash.php',
+ 'Nette\\Utils\\ArrayList' => $vendorDir . '/nette/utils/src/Utils/ArrayList.php',
+ 'Nette\\Utils\\Arrays' => $vendorDir . '/nette/utils/src/Utils/Arrays.php',
+ 'Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php',
+ 'Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php',
+ 'Nette\\Utils\\FileInfo' => $vendorDir . '/nette/utils/src/Utils/FileInfo.php',
+ 'Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php',
+ 'Nette\\Utils\\Finder' => $vendorDir . '/nette/utils/src/Utils/Finder.php',
+ 'Nette\\Utils\\Floats' => $vendorDir . '/nette/utils/src/Utils/Floats.php',
+ 'Nette\\Utils\\Helpers' => $vendorDir . '/nette/utils/src/Utils/Helpers.php',
+ 'Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php',
+ 'Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/compatibility.php',
+ 'Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php',
+ 'Nette\\Utils\\ImageColor' => $vendorDir . '/nette/utils/src/Utils/ImageColor.php',
+ 'Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\ImageType' => $vendorDir . '/nette/utils/src/Utils/ImageType.php',
+ 'Nette\\Utils\\Iterables' => $vendorDir . '/nette/utils/src/Utils/Iterables.php',
+ 'Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php',
+ 'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php',
+ 'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php',
+ 'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php',
+ 'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php',
+ 'Nette\\Utils\\ReflectionMethod' => $vendorDir . '/nette/utils/src/Utils/ReflectionMethod.php',
+ 'Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php',
+ 'Nette\\Utils\\Type' => $vendorDir . '/nette/utils/src/Utils/Type.php',
+ 'Nette\\Utils\\UnknownImageFileException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\Validators' => $vendorDir . '/nette/utils/src/Utils/Validators.php',
+);
diff --git a/tools/.phpstan/vendor/composer/autoload_files.php b/tools/.phpstan/vendor/composer/autoload_files.php
new file mode 100644
index 0000000..b62e293
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/autoload_files.php
@@ -0,0 +1,10 @@
+ $vendorDir . '/phpstan/phpstan/bootstrap.php',
+);
diff --git a/tools/.phpstan/vendor/composer/autoload_namespaces.php b/tools/.phpstan/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..15a2ff3
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/tomasvotruba/type-coverage/src'),
+ 'PHPStan\\ExtensionInstaller\\' => array($vendorDir . '/phpstan/extension-installer/src'),
+ 'PHPStan\\' => array($vendorDir . '/phpstan/phpstan-strict-rules/src'),
+ 'Ergebnis\\PHPStan\\Rules\\' => array($vendorDir . '/ergebnis/phpstan-rules/src'),
+);
diff --git a/tools/.phpstan/vendor/composer/autoload_real.php b/tools/.phpstan/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..c91f94e
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/autoload_real.php
@@ -0,0 +1,48 @@
+register(true);
+
+ $filesToLoad = \Composer\Autoload\ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$files;
+ $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+ require $file;
+ }
+ }, null, null);
+ foreach ($filesToLoad as $fileIdentifier => $file) {
+ $requireFile($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
diff --git a/tools/.phpstan/vendor/composer/autoload_static.php b/tools/.phpstan/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..041214c
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/autoload_static.php
@@ -0,0 +1,110 @@
+ __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'T' =>
+ array (
+ 'TomasVotruba\\TypeCoverage\\' => 26,
+ ),
+ 'P' =>
+ array (
+ 'PHPStan\\ExtensionInstaller\\' => 27,
+ 'PHPStan\\' => 8,
+ ),
+ 'E' =>
+ array (
+ 'Ergebnis\\PHPStan\\Rules\\' => 23,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'TomasVotruba\\TypeCoverage\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/tomasvotruba/type-coverage/src',
+ ),
+ 'PHPStan\\ExtensionInstaller\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/phpstan/extension-installer/src',
+ ),
+ 'PHPStan\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/phpstan/phpstan-strict-rules/src',
+ ),
+ 'Ergebnis\\PHPStan\\Rules\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ergebnis/phpstan-rules/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'Nette\\ArgumentOutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\DeprecatedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\DirectoryNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\FileNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\HtmlStringable' => __DIR__ . '/..' . '/nette/utils/src/HtmlStringable.php',
+ 'Nette\\IOException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\InvalidArgumentException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\InvalidStateException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\Iterators\\CachingIterator' => __DIR__ . '/..' . '/nette/utils/src/Iterators/CachingIterator.php',
+ 'Nette\\Iterators\\Mapper' => __DIR__ . '/..' . '/nette/utils/src/Iterators/Mapper.php',
+ 'Nette\\Localization\\ITranslator' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php',
+ 'Nette\\Localization\\Translator' => __DIR__ . '/..' . '/nette/utils/src/Translator.php',
+ 'Nette\\MemberAccessException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\NotImplementedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\NotSupportedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\OutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\SmartObject' => __DIR__ . '/..' . '/nette/utils/src/SmartObject.php',
+ 'Nette\\StaticClass' => __DIR__ . '/..' . '/nette/utils/src/StaticClass.php',
+ 'Nette\\UnexpectedValueException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+ 'Nette\\Utils\\ArrayHash' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayHash.php',
+ 'Nette\\Utils\\ArrayList' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayList.php',
+ 'Nette\\Utils\\Arrays' => __DIR__ . '/..' . '/nette/utils/src/Utils/Arrays.php',
+ 'Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php',
+ 'Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php',
+ 'Nette\\Utils\\FileInfo' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileInfo.php',
+ 'Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php',
+ 'Nette\\Utils\\Finder' => __DIR__ . '/..' . '/nette/utils/src/Utils/Finder.php',
+ 'Nette\\Utils\\Floats' => __DIR__ . '/..' . '/nette/utils/src/Utils/Floats.php',
+ 'Nette\\Utils\\Helpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/Helpers.php',
+ 'Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php',
+ 'Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php',
+ 'Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php',
+ 'Nette\\Utils\\ImageColor' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageColor.php',
+ 'Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\ImageType' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageType.php',
+ 'Nette\\Utils\\Iterables' => __DIR__ . '/..' . '/nette/utils/src/Utils/Iterables.php',
+ 'Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php',
+ 'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php',
+ 'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php',
+ 'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php',
+ 'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php',
+ 'Nette\\Utils\\ReflectionMethod' => __DIR__ . '/..' . '/nette/utils/src/Utils/ReflectionMethod.php',
+ 'Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php',
+ 'Nette\\Utils\\Type' => __DIR__ . '/..' . '/nette/utils/src/Utils/Type.php',
+ 'Nette\\Utils\\UnknownImageFileException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+ 'Nette\\Utils\\Validators' => __DIR__ . '/..' . '/nette/utils/src/Utils/Validators.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/tools/.phpstan/vendor/composer/installed.json b/tools/.phpstan/vendor/composer/installed.json
new file mode 100644
index 0000000..e7a7c7b
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/installed.json
@@ -0,0 +1,400 @@
+{
+ "packages": [
+ {
+ "name": "ergebnis/phpstan-rules",
+ "version": "2.8.0",
+ "version_normalized": "2.8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/phpstan-rules.git",
+ "reference": "30e790621fbad05573ef9cd355279fff5122e080"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/30e790621fbad05573ef9cd355279fff5122e080",
+ "reference": "30e790621fbad05573ef9cd355279fff5122e080",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
+ "phpstan/phpstan": "^2.0.0"
+ },
+ "require-dev": {
+ "doctrine/orm": "^2.20.0 || ^3.3.0",
+ "ergebnis/composer-normalize": "^2.45.0",
+ "ergebnis/license": "^2.6.0",
+ "ergebnis/php-cs-fixer-config": "^6.43.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.18.0",
+ "nette/di": "^3.1.10",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan-deprecation-rules": "^2.0.1",
+ "phpstan/phpstan-phpunit": "^2.0.4",
+ "phpstan/phpstan-strict-rules": "^2.0.3",
+ "phpunit/phpunit": "^9.6.21",
+ "psr/container": "^2.0.2",
+ "symfony/finder": "^5.4.45",
+ "symfony/process": "^5.4.47"
+ },
+ "time": "2025-02-18T11:20:05+00:00",
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\PHPStan\\Rules\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides rules for phpstan/phpstan.",
+ "homepage": "https://github.com/ergebnis/phpstan-rules",
+ "keywords": [
+ "PHPStan",
+ "phpstan-rules"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/phpstan-rules/issues",
+ "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/phpstan-rules"
+ },
+ "install-path": "../ergebnis/phpstan-rules"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v4.0.5",
+ "version_normalized": "4.0.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
+ "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
+ "shasum": ""
+ },
+ "require": {
+ "php": "8.0 - 8.4"
+ },
+ "conflict": {
+ "nette/finder": "<3",
+ "nette/schema": "<1.2.2"
+ },
+ "require-dev": {
+ "jetbrains/phpstorm-attributes": "dev-master",
+ "nette/tester": "^2.5",
+ "phpstan/phpstan": "^1.0",
+ "tracy/tracy": "^2.9"
+ },
+ "suggest": {
+ "ext-gd": "to use Image",
+ "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+ "ext-json": "to use Nette\\Utils\\Json",
+ "ext-mbstring": "to use Strings::lower() etc...",
+ "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
+ },
+ "time": "2024-08-07T15:39:19+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "array",
+ "core",
+ "datetime",
+ "images",
+ "json",
+ "nette",
+ "paginator",
+ "password",
+ "slugify",
+ "string",
+ "unicode",
+ "utf-8",
+ "utility",
+ "validation"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/utils/issues",
+ "source": "https://github.com/nette/utils/tree/v4.0.5"
+ },
+ "install-path": "../nette/utils"
+ },
+ {
+ "name": "phpstan/extension-installer",
+ "version": "1.4.3",
+ "version_normalized": "1.4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/extension-installer.git",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.0",
+ "php": "^7.2 || ^8.0",
+ "phpstan/phpstan": "^1.9.0 || ^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.0",
+ "php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0"
+ },
+ "time": "2024-09-04T20:21:43+00:00",
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PHPStan\\ExtensionInstaller\\Plugin"
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\ExtensionInstaller\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Composer plugin for automatic installation of PHPStan extensions",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpstan/extension-installer/issues",
+ "source": "https://github.com/phpstan/extension-installer/tree/1.4.3"
+ },
+ "install-path": "../phpstan/extension-installer"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.1.8",
+ "version_normalized": "2.1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f",
+ "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "time": "2025-03-09T09:30:48+00:00",
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "install-path": "../phpstan/phpstan"
+ },
+ {
+ "name": "phpstan/phpstan-strict-rules",
+ "version": "2.0.3",
+ "version_normalized": "2.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-strict-rules.git",
+ "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba",
+ "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.0.4"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6"
+ },
+ "time": "2025-01-21T10:52:14+00:00",
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Extra strict and opinionated rules for PHPStan",
+ "support": {
+ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
+ "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3"
+ },
+ "install-path": "../phpstan/phpstan-strict-rules"
+ },
+ {
+ "name": "tomasvotruba/type-coverage",
+ "version": "2.0.2",
+ "version_normalized": "2.0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/TomasVotruba/type-coverage.git",
+ "reference": "d033429580f2c18bda538fa44f2939236a990e0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c",
+ "reference": "d033429580f2c18bda538fa44f2939236a990e0c",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^3.2 || ^4.0",
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.0"
+ },
+ "time": "2025-01-07T00:10:26+00:00",
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "config/extension.neon"
+ ]
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "TomasVotruba\\TypeCoverage\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Measure type coverage of your project",
+ "keywords": [
+ "phpstan-extension",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/TomasVotruba/type-coverage/issues",
+ "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.me/rectorphp",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/tomasvotruba",
+ "type": "github"
+ }
+ ],
+ "install-path": "../tomasvotruba/type-coverage"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": [
+ "ergebnis/phpstan-rules",
+ "nette/utils",
+ "phpstan/extension-installer",
+ "phpstan/phpstan",
+ "phpstan/phpstan-strict-rules",
+ "tomasvotruba/type-coverage"
+ ]
+}
diff --git a/tools/.phpstan/vendor/composer/installed.php b/tools/.phpstan/vendor/composer/installed.php
new file mode 100644
index 0000000..95d1f64
--- /dev/null
+++ b/tools/.phpstan/vendor/composer/installed.php
@@ -0,0 +1,77 @@
+ array(
+ 'name' => '__root__',
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => null,
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => null,
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'ergebnis/phpstan-rules' => array(
+ 'pretty_version' => '2.8.0',
+ 'version' => '2.8.0.0',
+ 'reference' => '30e790621fbad05573ef9cd355279fff5122e080',
+ 'type' => 'phpstan-extension',
+ 'install_path' => __DIR__ . '/../ergebnis/phpstan-rules',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
+ 'nette/utils' => array(
+ 'pretty_version' => 'v4.0.5',
+ 'version' => '4.0.5.0',
+ 'reference' => '736c567e257dbe0fcf6ce81b4d6dbe05c6899f96',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../nette/utils',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
+ 'phpstan/extension-installer' => array(
+ 'pretty_version' => '1.4.3',
+ 'version' => '1.4.3.0',
+ 'reference' => '85e90b3942d06b2326fba0403ec24fe912372936',
+ 'type' => 'composer-plugin',
+ 'install_path' => __DIR__ . '/../phpstan/extension-installer',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
+ 'phpstan/phpstan' => array(
+ 'pretty_version' => '2.1.8',
+ 'version' => '2.1.8.0',
+ 'reference' => 'f9adff3b87c03b12cc7e46a30a524648e497758f',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../phpstan/phpstan',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
+ 'phpstan/phpstan-strict-rules' => array(
+ 'pretty_version' => '2.0.3',
+ 'version' => '2.0.3.0',
+ 'reference' => '8b88b5f818bfa301e0c99154ab622dace071c3ba',
+ 'type' => 'phpstan-extension',
+ 'install_path' => __DIR__ . '/../phpstan/phpstan-strict-rules',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
+ 'tomasvotruba/type-coverage' => array(
+ 'pretty_version' => '2.0.2',
+ 'version' => '2.0.2.0',
+ 'reference' => 'd033429580f2c18bda538fa44f2939236a990e0c',
+ 'type' => 'phpstan-extension',
+ 'install_path' => __DIR__ . '/../tomasvotruba/type-coverage',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
+ ),
+);
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md
new file mode 100644
index 0000000..8b8d5ce
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md
@@ -0,0 +1,637 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Unreleased
+
+For a full diff see [`2.8.0...main`][2.8.0...main].
+
+## [`2.8.0`][2.8.0]
+
+For a full diff see [`2.7.0...2.8.0`][2.7.0...2.8.0].
+
+### Added
+
+- Added `allRules` parameter to allow disabling and enabling all rules ([#913]), by [@localheinz]
+- Added `Expressions\NoAssignByReferenceRule`, which reports an error when a variable is assigned by reference ([#914]), by [@localheinz]
+
+## [`2.7.0`][2.7.0]
+
+For a full diff see [`2.6.1...2.7.0`][2.6.1...2.7.0].
+
+### Added
+
+- Added `Closures\NoParameterPassedByReferenceRule`, `Functions\NoParameterPassedByReferenceRule`, `Methods\NoParameterPassedByReferenceRule`, which report an error when a closure, a function, or a method has a parameter that is passed by reference ([#911]), by [@localheinz]
+- Added `Functions\NoReturnByReferenceRule` and `Methods\NoReturnByReferenceRule`, which report an error when a function or a method returns by reference ([#912]), by [@localheinz]
+
+## [`2.6.1`][2.6.1]
+
+For a full diff see [`2.6.0...2.6.1`][2.6.0...2.6.1].
+
+### Fixed
+
+- Adjusted `Methods\NoParameterWithNullableTypeDeclarationRule` to use the appropriate error identifier ([#902]), by [@manuelkiessling]
+
+## [`2.6.0`][2.6.0]
+
+For a full diff see [`2.5.2...2.6.0`][2.5.2...2.6.0].
+
+### Added
+
+- Added support for `phpstan/phpstan:^2.0.0` ([#873]), by [@localheinz]
+
+## [`2.5.2`][2.5.2]
+
+For a full diff see [`2.5.1...2.5.2`][2.5.1...2.5.2].
+
+### Fixed
+
+- Adjusted `Closures\NoNullableReturnTypeDeclarationRule`, `Closures\NoParameterWithNullableTypeDeclarationRule`, `Functions\NoNullableReturnTypeDeclarationRule`, `Functions\NoParameterWithNullableTypeDeclarationRule`, `Methods\NoNullableReturnTypeDeclarationRule`, `Methods\NoParameterWithNullableTypeDeclarationRule` to detect cases where `null` is referenced with incorrect case or relative to the root namespace ([#897]), by [@localheinz]
+
+## [`2.5.1`][2.5.1]
+
+For a full diff see [`2.5.0...2.5.1`][2.5.0...2.5.1].
+
+### Fixed
+
+- Returned rule with error identifier ([#882]), by [@localheinz]
+- Adjusted `Methods\FinalInAbstractClassRule` to ignore Doctrine embeddables and entities ([#396]), by [@localheinz]
+- Adjusted `Expressions\NoCompactRule` to detect usages of `compact()` with incorrect case ([#889]), by [@localheinz]
+- Adjusted `Methods\PrivateInFinalClassRule` to use more appropriate message when detecting a `protected` method in an anonymous class ([#890]), by [@localheinz]
+- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods from traits ([#891]), by [@localheinz]
+- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` attributes requiring methods to be `protected` ([#863]), by [@cosmastech]
+- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` annotations requiring methods to be `protected` ([#895]), by [@cosmastech]
+
+## [`2.5.0`][2.5.0]
+
+For a full diff see [`2.4.0...2.5.0`][2.4.0...2.5.0].
+
+### Added
+
+- Added rule error identifiers ([#875]), by [@localheinz]
+- Added support for PHP 8.0 ([#877]), by [@localheinz]
+- Added support for PHP 7.4 ([#880]), by [@localheinz]
+
+### Changed
+
+- Removed dependency on `nikic/php-parser` ([#878]), by [@localheinz]
+
+## [`2.4.0`][2.4.0]
+
+For a full diff see [`2.3.0...2.4.0`][2.3.0...2.4.0].
+
+### Added
+
+- Added support for PHP 8.4 ([#872]), by [@localheinz]
+
+## [`2.3.0`][2.3.0]
+
+For a full diff see [`2.2.0...2.3.0`][2.2.0...2.3.0].
+
+### Changed
+
+- Allowed installation on PHP 8.4 ([#862]), by [@localheinz]
+
+## [`2.2.0`][2.2.0]
+
+For a full diff see [`2.1.0...2.2.0`][2.1.0...2.2.0].
+
+### Changed
+
+- Allowed installation of `nikic/php-parser:^5.0.0` ([#735]), by [@localheinz]
+
+## [`2.1.0`][2.1.0]
+
+For a full diff see [`2.0.0...2.1.0`][2.0.0...2.1.0].
+
+### Changed
+
+- Dropped support for PHP 8.0 ([#567]), by [@localheinz]
+- Added support for PHP 8.3 ([#604]), by [@nunomaduro]
+
+## [`2.0.0`][2.0.0]
+
+For a full diff see [`1.0.0...2.0.0`][1.0.0...2.0.0].
+
+### Added
+
+- Added `methodsAllowedToUseContainerTypeDeclarations` parameter to allow configuring a list of method names that are allowed to have container parameter type declarations ([#541), by [@localheinz]
+- Allowed disabling rules ([#542), by [@localheinz]
+- Added support for nullable union types ([#543), by [@localheinz]
+
+### Changed
+
+- Dropped support for PHP 7.2 ([#496]), by [@localheinz]
+- Dropped support for PHP 7.3 ([#498]), by [@localheinz]
+- Dropped support for PHP 7.4 ([#499]), by [@localheinz]
+- Added support for PHP 8.2 ([#540]), by [@localheinz]
+
+### Removed
+
+- Removed `Expressions\NoEmptyRule` ([#525]), by [@enumag]
+
+## [`1.0.0`][1.0.0]
+
+For a full diff see [`0.15.3...1.0.0`][0.15.3...1.0.0].
+
+### Changed
+
+- Added support for `phpstan/phpstan:^1.0.0` and dropped support for non-stable versions of `phpstan/phpstan` ([#381]), by [@rpkamp]
+
+### Fixed
+
+- Adjusted `Classes\FinalRule` to not report an error when a non-final class has a `Doctrinbe\ORM\Mapping\Entity` attribute ([#395]), by [@localheinz]
+
+## [`0.15.3`][0.15.3]
+
+For a full diff see [`0.15.2...0.15.3`][0.15.2...0.15.3].
+
+### Changed
+
+- Allow installation with PHP 8.0 ([#294]), by [@localheinz]
+
+## [`0.15.2`][0.15.2]
+
+For a full diff see [`0.15.1...0.15.2`][0.15.1...0.15.2].
+
+### Changed
+
+- Dropped support for PHP 7.1 ([#259]), by [@localheinz]
+
+## [`0.15.1`][0.15.1]
+
+For a full diff see [`0.15.0...0.15.1`][0.15.0...0.15.1].
+
+### Changed
+
+- Adjusted `Methods\FinalInAbstractClass` rule to allow non-`final` `public` constructors in abstract classes ([#248]), by [@Slamdunk]
+
+## [`0.15.0`][0.15.0]
+
+For a full diff see [`0.14.4...0.15.0`][0.14.4...0.15.0].
+
+### Added
+
+- Added `Classes\PHPUnit\Framework\TestCaseWithSuffixRule`, which reports an error when a concrete class extending `PHPUnit\Framework\TestCase` does not have a `Test` suffix ([#225]), by [@localheinz]
+
+## [`0.14.4`][0.14.4]
+
+For a full diff see [`0.14.3...0.14.4`][0.14.3...0.14.4].
+
+### Fixed
+
+- Ignored classes with `@ORM\Mapping\Entity` annotations in `FinalRule` ([#202]), by [@localheinz]
+
+## [`0.14.3`][0.14.3]
+
+For a full diff see [`0.14.2...0.14.3`][0.14.2...0.14.3].
+
+### Fixed
+
+- Ignored first line in `DeclareStrictTypesRule` when it is a shebang ([#186]), by [@Great-Antique]
+
+## [`0.14.2`][0.14.2]
+
+For a full diff see [`0.14.1...0.14.2`][0.14.1...0.14.2].
+
+### Fixed
+
+- Brought back support for PHP 7.1 ([#166]), by [@localheinz]
+
+## [`0.14.1`][0.14.1]
+
+For a full diff see [`0.14.0...0.14.1`][0.14.0...0.14.1].
+
+### Fixed
+
+- Removed an inappropriate `replace` configuration from `composer.json` ([#161]), by [@localheinz]
+
+## [`0.14.0`][0.14.0]
+
+For a full diff see [`0.13.0...0.14.0`][0.13.0...0.14.0].
+
+### Changed
+
+- Allowed installation of `phpstan/phpstan:~0.12.0` ([#147]), by [@localheinz]
+- Renamed vendor namespace `Localheinz` to `Ergebnis` after move to [@ergebnis] ([#157]), by [@localheinz]
+
+ Run
+
+ ```sh
+ composer remove localheinz/phpstan-rules
+ ```
+
+ and
+
+ ```sh
+ composer require ergebnis/phpstan-rules
+ ```
+
+ to update.
+
+ Run
+
+ ```sh
+ find . -type f -exec sed -i '.bak' 's/Localheinz\\PHPStan/Ergebnis\\PHPStan/g' {} \;
+ ```
+
+ to replace occurrences of `Localheinz\PHPStan` with `Ergebnis\PHPStan`.
+
+ Run
+
+ ```sh
+ find -type f -name '*.bak' -delete
+ ```
+
+ to delete backup files created in the previous step.
+
+- Moved parameters into `ergebnis` section to prevent conflicts with global parameters ([#158]), by [@localheinz]
+
+ Where previously `phpstan.neon` looked like the following
+
+ ```neon
+ parameters:
+ allowAbstractClasses: true
+ classesAllowedToBeExtended: []
+ classesNotRequiredToBeAbstractOrFinal: []
+ interfacesImplementedByContainers:
+ - Psr\Container\ContainerInterface
+ ```
+
+ these parameters now need to be moved into an `ergebnis` section:
+
+ ```diff
+ parameters:
+ - allowAbstractClasses: true
+ - classesAllowedToBeExtended: []
+ - classesNotRequiredToBeAbstractOrFinal: []
+ - interfacesImplementedByContainers:
+ - - Psr\Container\ContainerInterface
+ + ergebnis:
+ + allowAbstractClasses: true
+ + classesAllowedToBeExtended: []
+ + classesNotRequiredToBeAbstractOrFinal: []
+ + interfacesImplementedByContainers:
+ + - Psr\Container\ContainerInterface
+ ```
+
+### Fixed
+
+- Dropped support for PHP 7.1 ([#141]), by [@localheinz]
+
+## [`0.13.0`][0.13.0]
+
+For a full diff see [`0.12.2...0.13.0`][0.12.2...0.13.0].
+
+### Added
+
+- Added `Methods\PrivateInFinalClassRule` which reports an error when a method in a `final` class is `protected` when it could be `private` ([#126]), by [@localheinz]
+
+## [`0.12.2`][0.12.2]
+
+For a full diff see [`0.12.1...0.12.2`][0.12.1...0.12.2].
+
+### Fixed
+
+- Started ignoring interfaces from analysis by `Methods\FinalInAbstractClassRule` to avoid inappropriate errors ([#132]), by [@localheinz]
+
+## [`0.12.1`][0.12.1]
+
+For a full diff see [`0.12.0...0.12.1`][0.12.0...0.12.1].
+
+### Fixed
+
+- Started resolving class name in type declaration before attempting to analyze it in the `Methods\NoParameterWithContainerTypeDeclarationRule` to avoid errors where class `self` is not found ([#128]), by [@localheinz]
+
+## [`0.12.0`][0.12.0]
+
+For a full diff see [`0.11.0...0.12.0`][0.11.0...0.12.0].
+
+### Added
+
+- Added `Methods\NoParameterWithContainerTypeDeclarationRule`, which reports an error when a method has a type declaration that corresponds to a known dependency injection container or service locator ([#122]), by [@localheinz]
+- Added `Methods\FinalInAbstractClassRule`, which reports an error when a concrete `public` or `protected` method in an `abstract` class is not `final` ([#123]), by [@localheinz]
+
+## [`0.11.0`][0.11.0]
+
+For a full diff see [`0.10.0...0.11.0`][0.10.0...0.11.0].
+
+### Added
+
+- Added `Files\DeclareStrictTypesRule`, which reports an error when a PHP file does not have a `declare(strict_types=1)` declaration ([#79]
+- Added `Expressions\NoEmptyRule`, which reports an error when the language construct `empty()` is used ([#110]), by [@localheinz]
+- Added `Expressions\NoEvalRule`, which reports an error when the language construct `eval()` is used ([#112]), by [@localheinz]
+- Added `Expressions\NoErrorSuppressionRule`, which reports an error when `@` is used to suppress errors ([#113]), by [@localheinz]
+- Added `Expressions\NoCompactRule`, which reports an error when the function `compact()` is used ([#116]), by [@localheinz]
+- Added `Statements\NoSwitchRule`, which reports an error when the statement `switch()` is used ([#117]), by [@localheinz]
+
+### Changed
+
+- Require at least `nikic/php-parser:^4.2.3` ([#102]), by [@localheinz]
+- Require at least `phpstan/phpstan:~0.11.15` ([#103]), by [@localheinz]
+
+## [`0.10.0`][0.10.0]
+
+For a full diff see [`0.9.1...0.10.0`][0.9.1...0.10.0].
+
+### Changed
+
+- Require at least `phpstan/phpstan:~0.11.7` ([#91]), by [@localheinz]
+
+### Fixed
+
+- Added missing `parametersSchema` configuration to `rules.neon`, which is required for use with `bleedingEdge.neon`, see [`phpstan/phpstan@54a125d`](https://github.com/phpstan/phpstan/commit/54a125df47fa097b792cb9a3e70c49f753f66b85) ([#93]), by [@localheinz]
+*
+## [`0.9.1`][0.9.1]
+
+For a full diff see [`0.9.0...0.9.1`][0.9.0...0.9.1].
+
+### Changed
+
+- Allow usage with [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer) ([#89]), by [@localheinz]
+
+## [`0.9.0`][0.9.0]
+
+For a full diff see [`0.8.1...0.9.0`][0.8.1...0.9.0].
+
+### Changed
+
+- Adjusted `Classes\FinalRule` to ignore Doctrine entities when they are annotated ([#84]), by [@localheinz]
+
+## [`0.8.1`][0.8.1]
+
+For a full diff see [`0.8.0...0.8.1`][0.8.0...0.8.1].
+
+### Fixed
+
+- Actually enable `Expressions\NoIssetRule` ([#83]), by [@localheinz]
+
+## [`0.8.0`][0.8.0]
+
+For a full diff see [`0.7.1...0.8.0`][0.7.1...0.8.0].
+
+### Added
+
+- Added `Expressions\NoIssetRule`, which reports an error when the language construct `isset()` is used ([#81]), by [@localheinz]
+
+## [`0.7.1`][0.7.1]
+
+For a full diff see [`0.7.0...0.7.1`][0.7.0...0.7.1].
+
+### Changed
+
+- Configured `Classes\NoExtendsRule` to allow a set of default classes to be extended ([#73]), by [@localheinz]
+
+## [`0.7.0`][0.7.0]
+
+For a full diff see [`0.6.0...0.7.0`][0.6.0...0.7.0].
+
+### Added
+
+- Added `Classes\NoExtendsRule`, which reports an error when a class extends a class that is not allowed to be extended ([#68]), by [@localheinz]
+
+## [`0.6.0`][0.6.0]
+
+For a full diff see [`0.5.0...0.6.0`][0.5.0...0.6.0].
+
+### Added
+
+- Allow installation with `phpstan/phpstan:~0.11.0` ([#65]), by [@localheinz]
+
+## [`0.5.0`][0.5.0]
+
+For a full diff see [`0.4.0...0.5.0`][0.4.0...0.5.0].
+
+### Added
+
+- Added `Methods\NoConstructorParameterWithDefaultValueRule`, which reports an error when a constructor of an anonymous class or a class has a parameter with a default value ([#45]), by [@localheinz]
+- Added parameters `$allowAbstractClasses` and `$classesNotRequiredToBeAbstractOrFinal` to allow configuration of `Classes`FinalRule` ([#51]), by [@localheinz]
+
+### Removed
+
+- Removed `Classes\AbstractOrFinalRule` after merging behaviour into `Classes\FinalRule` ([#51]), by [@localheinz]
+- Removed default values from constructor of `Classes\FinalRule` ([#53]), by [@localheinz]
+
+## [`0.4.0`][0.4.0]
+
+For a full diff see [`0.3.0...0.4.0`][0.3.0...0.4.0]
+
+### Changed
+
+- Removed double-quotes from error messages to be more consistent with error messages generated by `phpstan/phstan` ([#39]), by [@localheinz]
+- Modified wording and placement of method, function, and parameter names in error messages to be more consistent with error messages generated by `phpstan/phstan` ([#42]), by [@localheinz]
+
+## [`0.3.0`][0.3.0]
+
+For a full diff see [`0.2.0...0.3.0`][0.2.0...0.3.0]
+
+### Added
+
+- Added `Functions\NoNullableReturnTypeDeclarationRule`, which reports an error when a function has a nullable return type declaration, and `Methods\NoNullableReturnTypeDeclarationRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a nullable return type declaration ([#16]), by [@localheinz]
+- Added `Closures\NoParameterWithNullDefaultValueRule`, which reports an error when a closure has a parameter with `null` as default value ([#26]), by [@localheinz]
+- Added `Closures\NoNullableReturnTypeDeclarationRule`, which reports an error when a closure has a nullable return type declaration ([#29]), by [@localheinz]
+- Added `Functions\NoParameterWithNullDefaultValueRule`, which reports an error when a function has a parameter with `null` as default value ([#31]), by [@localheinz]
+- Added `Methods\NoParameterWithNullDefaultValueRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a parameter with `null` as default value ([#32]), by [@localheinz]
+- Added `Closures\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a closure has a parameter with a nullable type declaration ([#33]), by [@localheinz]
+- Added `Functions\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a function has a parameter with a nullable type declaration ([#34]), by [@localheinz]
+- Added `Methods\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a parameter with a nullable type declaration ([#35]), by [@localheinz]
+- Extracted `rules.neon`, so you can easily enable all rules by including it in your `phpstan.neon` ([#37]), by [@localheinz]
+
+## [`0.2.0`][0.2.0]
+
+For a full diff see [`0.1.0...0.2.0`][0.1.0...0.2.0]
+
+### Added
+
+- Added `Classes\FinalRule`, which reports an error when a non-anonymous class is not `final`, ([#4]), by [@localheinz]
+
+### Changed
+
+- Added an `$excludeClassNames` argument to the constructors of `Classes\AbstractOrFinalRule` and `Classes\FinalRule` to allow whitelisting of classes, ([#11]), by [@localheinz]
+
+## [`0.1.0`][0.1.0]
+
+For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0].
+
+### Added
+
+- Added `Classes\AbstractOrFinalRule`, which reports an error when a non-anonymous class is neither `abstract` nor `final`, ([#1]), by [@localheinz]
+
+[0.1.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.1.0
+[0.2.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.2.0
+[0.3.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.3.0
+[0.4.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.4.0
+[0.5.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.5.0
+[0.6.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.6.0
+[0.7.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.7.0
+[0.7.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.7.1
+[0.8.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.8.0
+[0.8.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.8.1
+[0.9.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.9.0
+[0.9.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.9.1
+[0.10.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.10.0
+[0.11.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.11.0
+[0.12.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.0
+[0.12.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.1
+[0.12.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.2
+[0.13.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.13.0
+[0.14.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.0
+[0.14.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.1
+[0.14.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.2
+[0.14.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.3
+[0.14.4]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.4
+[0.15.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.0
+[0.15.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.1
+[0.15.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.2
+[0.15.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.3
+[1.0.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/1.0.0
+[2.0.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.0.0
+[2.1.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.1.0
+[2.2.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.2.0
+[2.3.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.3.0
+[2.4.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.4.0
+[2.5.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.0
+[2.5.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.1
+[2.5.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.2
+[2.6.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.6.0
+[2.6.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.6.1
+[2.7.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.7.0
+[2.8.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.8.0
+
+[362c7ea...0.1.0]: https://github.com/ergebnis/phpstan-rules/compare/362c7ea...0.1.0
+[0.1.0...0.2.0]: https://github.com/ergebnis/phpstan-rules/compare/0.1.0...0.2.0
+[0.2.0...0.3.0]: https://github.com/ergebnis/phpstan-rules/compare/0.2.0...0.3.0
+[0.3.0...0.4.0]: https://github.com/ergebnis/phpstan-rules/compare/0.3.0...0.4.0
+[0.4.0...0.5.0]: https://github.com/ergebnis/phpstan-rules/compare/0.4.0...0.5.0
+[0.5.0...0.6.0]: https://github.com/ergebnis/phpstan-rules/compare/0.5.0...0.6.0
+[0.6.0...0.7.0]: https://github.com/ergebnis/phpstan-rules/compare/0.6.0...0.7.0
+[0.7.0...0.7.1]: https://github.com/ergebnis/phpstan-rules/compare/0.7.0...0.7.1
+[0.7.1...0.8.0]: https://github.com/ergebnis/phpstan-rules/compare/0.7.1...0.8.0
+[0.8.0...0.8.1]: https://github.com/ergebnis/phpstan-rules/compare/0.8.0...0.8.1
+[0.8.1...0.9.0]: https://github.com/ergebnis/phpstan-rules/compare/0.8.1...0.9.0
+[0.9.0...0.9.1]: https://github.com/ergebnis/phpstan-rules/compare/0.9.0...0.9.1
+[0.9.1...0.10.0]: https://github.com/ergebnis/phpstan-rules/compare/0.9.1...0.10.0
+[0.10.0...0.11.0]: https://github.com/ergebnis/phpstan-rules/compare/0.10.0...0.11.0
+[0.11.0...0.12.0]: https://github.com/ergebnis/phpstan-rules/compare/0.11.0...0.12.0
+[0.12.0...0.12.1]: https://github.com/ergebnis/phpstan-rules/compare/0.12.0...0.12.1
+[0.12.1...0.12.2]: https://github.com/ergebnis/phpstan-rules/compare/0.12.1...0.12.2
+[0.12.2...0.13.0]: https://github.com/ergebnis/phpstan-rules/compare/0.12.2...0.13.0
+[0.13.0...0.14.0]: https://github.com/ergebnis/phpstan-rules/compare/0.13.0...0.14.0
+[0.14.0...0.14.1]: https://github.com/ergebnis/phpstan-rules/compare/0.14.0...0.14.1
+[0.14.1...0.14.2]: https://github.com/ergebnis/phpstan-rules/compare/0.14.1...0.14.2
+[0.14.2...0.14.3]: https://github.com/ergebnis/phpstan-rules/compare/0.14.2...0.14.3
+[0.14.3...0.14.4]: https://github.com/ergebnis/phpstan-rules/compare/0.14.3...0.14.4
+[0.14.4...0.15.0]: https://github.com/ergebnis/phpstan-rules/compare/0.14.4...0.15.0
+[0.15.0...0.15.1]: https://github.com/ergebnis/phpstan-rules/compare/0.15.0...0.15.1
+[0.15.1...0.15.2]: https://github.com/ergebnis/phpstan-rules/compare/0.15.1...0.15.2
+[0.15.2...0.15.3]: https://github.com/ergebnis/phpstan-rules/compare/0.15.2...0.15.3
+[0.15.3...1.0.0]: https://github.com/ergebnis/phpstan-rules/compare/0.15.3...1.0.0
+[1.0.0...2.0.0]: https://github.com/ergebnis/phpstan-rules/compare/1.0.0...2.0.0
+[2.0.0...2.1.0]: https://github.com/ergebnis/phpstan-rules/compare/2.0.0...2.1.0
+[2.1.0...2.2.0]: https://github.com/ergebnis/phpstan-rules/compare/2.1.0...2.2.0
+[2.2.0...2.3.0]: https://github.com/ergebnis/phpstan-rules/compare/2.2.0...2.3.0
+[2.3.0...2.4.0]: https://github.com/ergebnis/phpstan-rules/compare/2.3.0...2.4.0
+[2.4.0...2.5.0]: https://github.com/ergebnis/phpstan-rules/compare/2.4.0...2.5.0
+[2.5.0...2.5.1]: https://github.com/ergebnis/phpstan-rules/compare/2.5.0...2.5.1
+[2.5.1...2.5.2]: https://github.com/ergebnis/phpstan-rules/compare/2.5.1...2.5.2
+[2.5.2...2.6.0]: https://github.com/ergebnis/phpstan-rules/compare/2.5.2...2.6.0
+[2.6.0...2.6.1]: https://github.com/ergebnis/phpstan-rules/compare/2.6.0...2.6.1
+[2.6.1...2.7.0]: https://github.com/ergebnis/phpstan-rules/compare/2.6.1...2.7.0
+[2.7.0...2.8.0]: https://github.com/ergebnis/phpstan-rules/compare/2.7.0...2.8.0
+[2.8.0...main]: https://github.com/ergebnis/phpstan-rules/compare/2.8.0...main
+
+[#1]: https://github.com/ergebnis/phpstan-rules/pull/1
+[#4]: https://github.com/ergebnis/phpstan-rules/pull/4
+[#11]: https://github.com/ergebnis/phpstan-rules/pull/11
+[#16]: https://github.com/ergebnis/phpstan-rules/pull/16
+[#26]: https://github.com/ergebnis/phpstan-rules/pull/26
+[#29]: https://github.com/ergebnis/phpstan-rules/pull/29
+[#31]: https://github.com/ergebnis/phpstan-rules/pull/31
+[#32]: https://github.com/ergebnis/phpstan-rules/pull/32
+[#33]: https://github.com/ergebnis/phpstan-rules/pull/33
+[#34]: https://github.com/ergebnis/phpstan-rules/pull/34
+[#35]: https://github.com/ergebnis/phpstan-rules/pull/35
+[#37]: https://github.com/ergebnis/phpstan-rules/pull/37
+[#39]: https://github.com/ergebnis/phpstan-rules/pull/39
+[#42]: https://github.com/ergebnis/phpstan-rules/pull/42
+[#45]: https://github.com/ergebnis/phpstan-rules/pull/45
+[#51]: https://github.com/ergebnis/phpstan-rules/pull/51
+[#53]: https://github.com/ergebnis/phpstan-rules/pull/53
+[#65]: https://github.com/ergebnis/phpstan-rules/pull/65
+[#68]: https://github.com/ergebnis/phpstan-rules/pull/68
+[#73]: https://github.com/ergebnis/phpstan-rules/pull/73
+[#79]: https://github.com/ergebnis/phpstan-rules/pull/79
+[#81]: https://github.com/ergebnis/phpstan-rules/pull/81
+[#83]: https://github.com/ergebnis/phpstan-rules/pull/83
+[#84]: https://github.com/ergebnis/phpstan-rules/pull/84
+[#89]: https://github.com/ergebnis/phpstan-rules/pull/89
+[#91]: https://github.com/ergebnis/phpstan-rules/pull/91
+[#93]: https://github.com/ergebnis/phpstan-rules/pull/93
+[#102]: https://github.com/ergebnis/phpstan-rules/pull/102
+[#103]: https://github.com/ergebnis/phpstan-rules/pull/103
+[#110]: https://github.com/ergebnis/phpstan-rules/pull/110
+[#112]: https://github.com/ergebnis/phpstan-rules/pull/112
+[#113]: https://github.com/ergebnis/phpstan-rules/pull/113
+[#116]: https://github.com/ergebnis/phpstan-rules/pull/116
+[#117]: https://github.com/ergebnis/phpstan-rules/pull/117
+[#122]: https://github.com/ergebnis/phpstan-rules/pull/122
+[#123]: https://github.com/ergebnis/phpstan-rules/pull/123
+[#126]: https://github.com/ergebnis/phpstan-rules/pull/126
+[#128]: https://github.com/ergebnis/phpstan-rules/pull/128
+[#132]: https://github.com/ergebnis/phpstan-rules/pull/132
+[#141]: https://github.com/ergebnis/phpstan-rules/pull/141
+[#147]: https://github.com/ergebnis/phpstan-rules/pull/147
+[#157]: https://github.com/ergebnis/phpstan-rules/pull/157
+[#158]: https://github.com/ergebnis/phpstan-rules/pull/158
+[#161]: https://github.com/ergebnis/phpstan-rules/pull/161
+[#166]: https://github.com/ergebnis/phpstan-rules/pull/166
+[#186]: https://github.com/ergebnis/phpstan-rules/pull/186
+[#202]: https://github.com/ergebnis/phpstan-rules/pull/202
+[#225]: https://github.com/ergebnis/phpstan-rules/pull/225
+[#248]: https://github.com/ergebnis/phpstan-rules/pull/248
+[#259]: https://github.com/ergebnis/phpstan-rules/pull/259
+[#294]: https://github.com/ergebnis/phpstan-rules/pull/294
+[#381]: https://github.com/ergebnis/phpstan-rules/pull/381
+[#395]: https://github.com/ergebnis/phpstan-rules/pull/395
+[#396]: https://github.com/ergebnis/phpstan-rules/pull/396
+[#496]: https://github.com/ergebnis/phpstan-rules/pull/496
+[#498]: https://github.com/ergebnis/phpstan-rules/pull/498
+[#499]: https://github.com/ergebnis/phpstan-rules/pull/498
+[#525]: https://github.com/ergebnis/phpstan-rules/pull/525
+[#540]: https://github.com/ergebnis/phpstan-rules/pull/540
+[#541]: https://github.com/ergebnis/phpstan-rules/pull/541
+[#542]: https://github.com/ergebnis/phpstan-rules/pull/542
+[#543]: https://github.com/ergebnis/phpstan-rules/pull/543
+[#567]: https://github.com/ergebnis/phpstan-rules/pull/567
+[#735]: https://github.com/ergebnis/phpstan-rules/pull/735
+[#862]: https://github.com/ergebnis/phpstan-rules/pull/862
+[#863]: https://github.com/ergebnis/phpstan-rules/pull/863
+[#872]: https://github.com/ergebnis/phpstan-rules/pull/872
+[#873]: https://github.com/ergebnis/phpstan-rules/pull/873
+[#875]: https://github.com/ergebnis/phpstan-rules/pull/875
+[#877]: https://github.com/ergebnis/phpstan-rules/pull/877
+[#878]: https://github.com/ergebnis/phpstan-rules/pull/878
+[#880]: https://github.com/ergebnis/phpstan-rules/pull/880
+[#882]: https://github.com/ergebnis/phpstan-rules/pull/882
+[#889]: https://github.com/ergebnis/phpstan-rules/pull/889
+[#890]: https://github.com/ergebnis/phpstan-rules/pull/890
+[#891]: https://github.com/ergebnis/phpstan-rules/pull/891
+[#895]: https://github.com/ergebnis/phpstan-rules/pull/895
+[#897]: https://github.com/ergebnis/phpstan-rules/pull/897
+[#902]: https://github.com/ergebnis/phpstan-rules/pull/902
+[#911]: https://github.com/ergebnis/phpstan-rules/pull/911
+[#912]: https://github.com/ergebnis/phpstan-rules/pull/912
+[#913]: https://github.com/ergebnis/phpstan-rules/pull/913
+[#914]: https://github.com/ergebnis/phpstan-rules/pull/914
+
+[@cosmastech]: https://github.com/cosmastech
+[@enumag]: https://github.com/enumag
+[@ergebnis]: https://github.com/ergebnis
+[@Great-Antique]: https://github.com/Great-Antique
+[@localheinz]: https://github.com/localheinz
+[@manuelkiessling]: https://github.com/manuelkiessling
+[@nunomaduro]: https://github.com/nunomaduro
+[@rpkamp]: https://github.com/rpkamp
+[@Slamdunk]: https://github.com/Slamdunk
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md
new file mode 100644
index 0000000..130e719
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md
@@ -0,0 +1,16 @@
+# The MIT License (MIT)
+
+Copyright (c) 2018-2025 Andreas Möller
+
+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/ergebnis/phpstan-rules/README.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md
new file mode 100644
index 0000000..8296743
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md
@@ -0,0 +1,683 @@
+# phpstan-rules
+
+[](https://github.com/ergebnis/phpstan-rules/actions)
+[](https://github.com/ergebnis/phpstan-rules/actions)
+[](https://github.com/ergebnis/phpstan-rules/actions)
+[](https://github.com/ergebnis/phpstan-rules/actions)
+
+[](https://codecov.io/gh/ergebnis/phpstan-rules)
+
+[](https://packagist.org/packages/ergebnis/phpstan-rules)
+[](https://packagist.org/packages/ergebnis/phpstan-rules)
+[](https://packagist.org/packages/ergebnis/phpstan-rules)
+
+This project provides a [`composer`](https://getcomposer.org) package with rules for [`phpstan/phpstan`](https://github.com/phpstan/phpstan).
+
+## Installation
+
+Run
+
+```sh
+composer require --dev ergebnis/phpstan-rules
+```
+
+## Usage
+
+All of the [rules](https://github.com/ergebnis/phpstan-rules#rules) provided (and used) by this library are included in [`rules.neon`](rules.neon).
+
+When you are using [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer), `rules.neon` will be automatically included.
+
+Otherwise you need to include `rules.neon` in your `phpstan.neon`:
+
+```neon
+includes:
+ - vendor/ergebnis/phpstan-rules/rules.neon
+```
+
+:bulb: You probably want to use these rules on top of the rules provided by:
+
+- [`phpstan/phpstan`](https://github.com/phpstan/phpstan)
+- [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules)
+- [`phpstan/phpstan-strict-rules`](https://github.com/phpstan/phpstan-strict-rules)
+
+## Rules
+
+This package provides the following rules for use with [`phpstan/phpstan`](https://github.com/phpstan/phpstan):
+
+- [`Ergebnis\PHPStan\Rules\Classes\FinalRule`](https://github.com/ergebnis/phpstan-rules#classesfinalrule)
+- [`Ergebnis\PHPStan\Rules\Classes\NoExtendsRule`](https://github.com/ergebnis/phpstan-rules#classesnoextendsrule)
+- [`Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule`](https://github.com/ergebnis/phpstan-rules#classesphpunitframeworktestcasewithsuffixrule)
+- [`Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#closuresnonullablereturntypedeclarationrule)
+- [`Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterpassedbyreferencerule)
+- [`Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterwithnullabletypedeclarationrule)
+- [`Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterwithnulldefaultvaluerule)
+- [`Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoassignbyreferencerule)
+- [`Ergebnis\PHPStan\Rules\Expressions\NoCompactRule`](https://github.com/ergebnis/phpstan-rules#expressionsnocompactrule)
+- [`Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoerrorsuppressionrule)
+- [`Ergebnis\PHPStan\Rules\Expressions\NoEvalRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoevalrule)
+- [`Ergebnis\PHPStan\Rules\Expressions\NoIssetRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoissetrule)
+- [`Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule`](https://github.com/ergebnis/phpstan-rules#filesdeclarestricttypesrule)
+- [`Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnonullablereturntypedeclarationrule)
+- [`Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterpassedbyreferencerule)
+- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnullabletypedeclarationrule)
+- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnulldefaultvaluerule)
+- [`Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoreturnbyreferencerule)
+- [`Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule`](https://github.com/ergebnis/phpstan-rules#methodsfinalinabstractclassrule)
+- [`Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoconstructorparameterwithdefaultvaluerule)
+- [`Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnonullablereturntypedeclarationrule)
+- [`Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterpassedbyreferencerule)
+- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithcontainertypedeclarationrule)
+- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnullabletypedeclarationrule)
+- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnulldefaultvaluerule)
+- [`Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoreturnbyreferencerule)
+- [`Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule`](https://github.com/ergebnis/phpstan-rules#methodsprivateinfinalclassrule)
+- [`Ergebnis\PHPStan\Rules\Statements\NoSwitchRule`](https://github.com/ergebnis/phpstan-rules#statementsnoswitchrule)
+
+### Classes
+
+#### `Classes\FinalRule`
+
+This rule reports an error when a non-anonymous class is not `final`.
+
+:bulb: This rule ignores classes that
+
+- use `@Entity`, `@ORM\Entity`, or `@ORM\Mapping\Entity` annotations
+- use `Doctrine\ORM\Mapping\Entity` attributes
+
+on the class level.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ final:
+ enabled: false
+```
+
+##### Disallowing `abstract` classes
+
+By default, this rule allows to declare `abstract` classes.
+
+You can set the `allowAbstractClasses` parameter to `false` to disallow abstract classes.
+
+```neon
+parameters:
+ ergebnis:
+ final:
+ allowAbstractClasses: false
+```
+
+##### Excluding classes from inspection
+
+You can set the `classesNotRequiredToBeAbstractOrFinal` parameter to a list of class names that you want to exclude from inspection.
+
+```neon
+parameters:
+ ergebnis:
+ final:
+ classesNotRequiredToBeAbstractOrFinal:
+ - Foo\Bar\NeitherAbstractNorFinal
+ - Bar\Baz\NeitherAbstractNorFinal
+```
+
+#### `Classes\NoExtendsRule`
+
+This rule reports an error when a class extends another class.
+
+##### Defaults
+
+By default, this rule allows the following classes to be extended:
+
+- [`PHPUnit\Framework\TestCase`](https://github.com/sebastianbergmann/phpunit/blob/7.5.2/src/Framework/TestCase.php)
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noExtends:
+ enabled: false
+```
+
+##### Allowing classes to be extended
+
+You can set the `classesAllowedToBeExtended` parameter to a list of class names that you want to allow to be extended.
+
+```neon
+parameters:
+ ergebnis:
+ noExtends:
+ classesAllowedToBeExtended:
+ - Ergebnis\PHPStan\Rules\Test\Integration\AbstractTestCase
+ - Ergebnis\PHPStan\Rules\Test\Integration\AbstractTestCase
+```
+
+#### `Classes\PHPUnit\Framework\TestCaseWithSuffixRule`
+
+This rule reports an error when a concrete class is a sub-class of `PHPUnit\Framework\TestCase` but does not have a `Test` suffix.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ testCaseWithSuffix:
+ enabled: false
+```
+
+### Closures
+
+#### `Closures\NoNullableReturnTypeDeclarationRule`
+
+This rule reports an error when a closure uses a nullable return type declaration.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noNullableReturnTypeDeclaration:
+ enabled: false
+```
+
+#### `Closures\NoParameterPassedByReferenceRule`
+
+This rule reports an error when a closure has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php).
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterPassedByReference:
+ enabled: false
+```
+
+#### `Closures\NoParameterWithNullableTypeDeclarationRule`
+
+This rule reports an error when a closure has a parameter with a nullable type declaration.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithNullableTypeDeclaration:
+ enabled: false
+```
+
+#### `Closures\NoParameterWithNullDefaultValueRule`
+
+This rule reports an error when a closure has a parameter with `null` as default value.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithNullDefaultValue:
+ enabled: false
+```
+
+### Expressions
+
+#### `Expressions\NoAssignByReferenceRule`
+
+This rule reports an error when [a variable is assigned by reference](https://www.php.net/manual/en/language.references.whatdo.php#language.references.whatdo.assign).
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noAssignByReference:
+ enabled: false
+```
+
+#### `Expressions\NoCompactRule`
+
+This rule reports an error when the function [`compact()`](https://www.php.net/compact) is used.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noCompact:
+ enabled: false
+```
+
+#### `Expressions\NoEvalRule`
+
+This rule reports an error when the language construct [`eval()`](https://www.php.net/eval) is used.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noEval:
+ enabled: false
+```
+
+#### `Expressions\NoErrorSuppressionRule`
+
+This rule reports an error when [`@`](https://www.php.net/manual/en/language.operators.errorcontrol.php) is used to suppress errors.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noErrorSuppression:
+ enabled: false
+```
+
+#### `Expressions\NoIssetRule`
+
+This rule reports an error when the language construct [`isset()`](https://www.php.net/isset) is used.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noIsset:
+ enabled: false
+```
+
+### Files
+
+#### `Files\DeclareStrictTypesRule`
+
+This rule reports an error when a non-empty file does not contain a `declare(strict_types=1)` declaration.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ declareStrictTypes:
+ enabled: false
+```
+
+### Functions
+
+#### `Functions\NoNullableReturnTypeDeclarationRule`
+
+This rule reports an error when a function uses a nullable return type declaration.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noNullableReturnTypeDeclaration:
+ enabled: false
+```
+
+#### `Functions\NoParameterPassedByReferenceRule`
+
+This rule reports an error when a function has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php).
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterPassedByReference:
+ enabled: false
+```
+
+#### `Functions\NoParameterWithNullableTypeDeclarationRule`
+
+This rule reports an error when a function has a parameter with a nullable type declaration.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithNullableTypeDeclaration:
+ enabled: false
+```
+
+#### `Functions\NoParameterWithNullDefaultValueRule`
+
+This rule reports an error when a function has a parameter with `null` as default value.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithNullDefaultValue:
+ enabled: false
+```
+
+#### `Functions\NoReturnByReferenceRule`
+
+This rule reports an error when a function [returns by reference](https://www.php.net/manual/en/language.references.return.php).
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noReturnByReference:
+ enabled: false
+```
+
+### Methods
+
+#### `Methods\FinalInAbstractClassRule`
+
+This rule reports an error when a concrete `public` or `protected `method in an `abstract` class is not `final`.
+
+:bulb: This rule ignores
+
+- Doctrine embeddables
+- Doctrine entities
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ finalInAbstractClass:
+ enabled: false
+```
+
+#### `Methods\NoConstructorParameterWithDefaultValueRule`
+
+This rule reports an error when a constructor declared in
+
+- an anonymous class
+- a class
+
+has a default value.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noConstructorParameterWithDefaultValue:
+ enabled: false
+```
+
+#### `Methods\NoParameterPassedByReferenceRule`
+
+This rule reports an error when a method has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php).
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterPassedByReference:
+ enabled: false
+```
+
+#### `Methods\NoNullableReturnTypeDeclarationRule`
+
+This rule reports an error when a method declared in
+
+- an anonymous class
+- a class
+- an interface
+
+uses a nullable return type declaration.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noNullableReturnTypeDeclaration:
+ enabled: false
+```
+
+#### `Methods\NoParameterWithContainerTypeDeclarationRule`
+
+This rule reports an error when a method has a type declaration for a known dependency injection container or service locator.
+
+##### Defaults
+
+By default, this rule disallows the use of type declarations indicating an implementation of
+
+- [`Psr\Container\ContainerInterface`](https://github.com/php-fig/container/blob/1.0.0/src/ContainerInterface.php)
+
+is expected to be injected into a method.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithContainerTypeDeclaration:
+ enabled: false
+```
+
+##### Configuring container interfaces
+
+You can set the `interfacesImplementedByContainers` parameter to a list of interface names of additional containers and service locators.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithContainerTypeDeclaration:
+ interfacesImplementedByContainers:
+ - Fancy\DependencyInjection\ContainerInterface
+ - Other\ServiceLocatorInterface
+```
+
+##### Configuring methods allowed to use parameters with container type declarations
+
+You can set the `methodsAllowedToUseContainerTypeDeclarations` parameter to a list of method names that are allowed to use parameters with container type declarations.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithContainerTypeDeclaration:
+ methodsAllowedToUseContainerTypeDeclarations:
+ - loadExtension
+```
+
+#### `Methods\NoParameterWithNullableTypeDeclarationRule`
+
+This rule reports an error when a method declared in
+
+- an anonymous class
+- a class
+- an interface
+
+has a parameter with a nullable type declaration.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithNullableTypeDeclaration:
+ enabled: false
+```
+
+#### `Methods\NoParameterWithNullDefaultValueRule`
+
+This rule reports an error when a method declared in
+
+- an anonymous class
+- a class
+- an interface
+
+has a parameter with `null` as default value.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noParameterWithNullDefaultValue:
+ enabled: false
+```
+
+#### `Functions\NoReturnByReferenceRule`
+
+This rule reports an error when a method [returns by reference](https://www.php.net/manual/en/language.references.return.php).
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noReturnByReference:
+ enabled: false
+```
+
+#### `Methods\PrivateInFinalClassRule`
+
+This rule reports an error when a method in a `final` class is `protected` but could be `private`.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ privateInFinalClass:
+ enabled: false
+```
+
+### Statements
+
+#### `Statements\NoSwitchRule`
+
+This rule reports an error when the statement [`switch()`](https://www.php.net/manual/control-structures.switch.php) is used.
+
+##### Disabling the rule
+
+You can set the `enabled` parameter to `false` to disable this rule.
+
+```neon
+parameters:
+ ergebnis:
+ noSwitch:
+ enabled: false
+```
+
+## Disabling all rules
+
+You can disable all rules using the `allRules` configuration parameter:
+
+```neon
+parameters:
+ ergebnis:
+ allRules: false
+```
+
+## Enabling rules one-by-one
+
+If you have disabled all rules using the `allRules` configuration parameter, you can re-enable individual rules with their corresponding configuration parameters:
+
+```neon
+parameters:
+ ergebnis:
+ allRules: false
+ privateInFinalClass:
+ enabled: true
+```
+
+## Changelog
+
+The maintainers of this project record notable changes to this project in a [changelog](CHANGELOG.md).
+
+## Contributing
+
+The maintainers of this project suggest following the [contribution guide](.github/CONTRIBUTING.md).
+
+## Code of Conduct
+
+The maintainers of this project ask contributors to follow the [code of conduct](https://github.com/ergebnis/.github/blob/main/CODE_OF_CONDUCT.md).
+
+## General Support Policy
+
+The maintainers of this project provide limited support.
+
+You can support the maintenance of this project by [sponsoring @localheinz](https://github.com/sponsors/localheinz) or [requesting an invoice for services related to this project](mailto:am@localheinz.com?subject=ergebnis/phpstan-rules:%20Requesting%20invoice%20for%20services).
+
+## PHP Version Support Policy
+
+This project supports PHP versions with [active and security support](https://www.php.net/supported-versions.php).
+
+The maintainers of this project add support for a PHP version following its initial release and drop support for a PHP version when it has reached the end of security support.
+
+## Security Policy
+
+This project has a [security policy](.github/SECURITY.md).
+
+## License
+
+This project uses the [MIT license](LICENSE.md).
+
+## Credits
+
+The method [`FinalRule::isWhitelistedClass()`](src/Classes/FinalRule.php) is inspired by the work on [`FinalClassFixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalClassFixer.php) and [`FinalInternalClassFixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalInternalClassFixer.php), contributed by [Dariusz Rumiński](https://github.com/keradus), [Filippo Tessarotto](https://github.com/Slamdunk), and [Spacepossum](https://github.com/SpacePossum) for [`friendsofphp/php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (originally licensed under MIT).
+
+## Social
+
+Follow [@localheinz](https://twitter.com/intent/follow?screen_name=localheinz) and [@ergebnis](https://twitter.com/intent/follow?screen_name=ergebnis) on Twitter.
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json
new file mode 100644
index 0000000..89d6871
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json
@@ -0,0 +1,76 @@
+{
+ "name": "ergebnis/phpstan-rules",
+ "description": "Provides rules for phpstan/phpstan.",
+ "license": "MIT",
+ "type": "phpstan-extension",
+ "keywords": [
+ "phpstan",
+ "phpstan-rules"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "homepage": "https://github.com/ergebnis/phpstan-rules",
+ "support": {
+ "issues": "https://github.com/ergebnis/phpstan-rules/issues",
+ "source": "https://github.com/ergebnis/phpstan-rules",
+ "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md"
+ },
+ "require": {
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
+ "ext-mbstring": "*",
+ "phpstan/phpstan": "^2.0.0"
+ },
+ "require-dev": {
+ "doctrine/orm": "^2.20.0 || ^3.3.0",
+ "ergebnis/composer-normalize": "^2.45.0",
+ "ergebnis/license": "^2.6.0",
+ "ergebnis/php-cs-fixer-config": "^6.43.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.18.0",
+ "nette/di": "^3.1.10",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan-deprecation-rules": "^2.0.1",
+ "phpstan/phpstan-phpunit": "^2.0.4",
+ "phpstan/phpstan-strict-rules": "^2.0.3",
+ "phpunit/phpunit": "^9.6.21",
+ "psr/container": "^2.0.2",
+ "symfony/finder": "^5.4.45",
+ "symfony/process": "^5.4.47"
+ },
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\PHPStan\\Rules\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Ergebnis\\PHPStan\\Rules\\Test\\": "test/"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "ergebnis/composer-normalize": true,
+ "infection/extension-installer": true,
+ "phpstan/extension-installer": true
+ },
+ "audit": {
+ "abandoned": "report"
+ },
+ "platform": {
+ "php": "7.4.33"
+ },
+ "preferred-install": "dist",
+ "sort-packages": true
+ },
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon b/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon
new file mode 100644
index 0000000..a81a7e8
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon
@@ -0,0 +1,263 @@
+conditionalTags:
+ Ergebnis\PHPStan\Rules\Classes\FinalRule:
+ phpstan.rules.rule: %ergebnis.final.enabled%
+ Ergebnis\PHPStan\Rules\Classes\NoExtendsRule:
+ phpstan.rules.rule: %ergebnis.noExtends.enabled%
+ Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule:
+ phpstan.rules.rule: %ergebnis.testCaseWithSuffix.enabled%
+ Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule:
+ phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled%
+ Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule:
+ phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled%
+ Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule:
+ phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled%
+ Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule:
+ phpstan.rules.rule: %ergebnis.noAssignByReference.enabled%
+ Ergebnis\PHPStan\Rules\Expressions\NoCompactRule:
+ phpstan.rules.rule: %ergebnis.noCompact.enabled%
+ Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule:
+ phpstan.rules.rule: %ergebnis.noErrorSuppression.enabled%
+ Ergebnis\PHPStan\Rules\Expressions\NoEvalRule:
+ phpstan.rules.rule: %ergebnis.noEval.enabled%
+ Ergebnis\PHPStan\Rules\Expressions\NoIssetRule:
+ phpstan.rules.rule: %ergebnis.noIsset.enabled%
+ Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule:
+ phpstan.rules.rule: %ergebnis.declareStrictTypes.enabled%
+ Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule:
+ phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled%
+ Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule:
+ phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled%
+ Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule:
+ phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled%
+ Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule:
+ phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled%
+ Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule:
+ phpstan.rules.rule: %ergebnis.noReturnByReference.enabled%
+ Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule:
+ phpstan.rules.rule: %ergebnis.finalInAbstractClass.enabled%
+ Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule:
+ phpstan.rules.rule: %ergebnis.noConstructorParameterWithDefaultValue.enabled%
+ Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule:
+ phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled%
+ Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule:
+ phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled%
+ Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule:
+ phpstan.rules.rule: %ergebnis.noParameterWithContainerTypeDeclaration.enabled%
+ Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule:
+ phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled%
+ Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule:
+ phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled%
+ Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule:
+ phpstan.rules.rule: %ergebnis.noReturnByReference.enabled%
+ Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule:
+ phpstan.rules.rule: %ergebnis.privateInFinalClass.enabled%
+ Ergebnis\PHPStan\Rules\Statements\NoSwitchRule:
+ phpstan.rules.rule: %ergebnis.noSwitch.enabled%
+
+parameters:
+ ergebnis:
+ allRules: true
+ declareStrictTypes:
+ enabled: %ergebnis.allRules%
+ final:
+ allowAbstractClasses: true
+ classesNotRequiredToBeAbstractOrFinal: []
+ enabled: %ergebnis.allRules%
+ finalInAbstractClass:
+ enabled: %ergebnis.allRules%
+ noAssignByReference:
+ enabled: %ergebnis.allRules%
+ noCompact:
+ enabled: %ergebnis.allRules%
+ noConstructorParameterWithDefaultValue:
+ enabled: %ergebnis.allRules%
+ noErrorSuppression:
+ enabled: %ergebnis.allRules%
+ noEval:
+ enabled: %ergebnis.allRules%
+ noExtends:
+ classesAllowedToBeExtended: []
+ enabled: %ergebnis.allRules%
+ noIsset:
+ enabled: %ergebnis.allRules%
+ noNullableReturnTypeDeclaration:
+ enabled: %ergebnis.allRules%
+ noParameterPassedByReference:
+ enabled: %ergebnis.allRules%
+ noParameterWithContainerTypeDeclaration:
+ enabled: %ergebnis.allRules%
+ interfacesImplementedByContainers:
+ - Psr\Container\ContainerInterface
+ methodsAllowedToUseContainerTypeDeclarations: []
+ noParameterWithNullableTypeDeclaration:
+ enabled: %ergebnis.allRules%
+ noParameterWithNullDefaultValue:
+ enabled: %ergebnis.allRules%
+ noReturnByReference:
+ enabled: %ergebnis.allRules%
+ noSwitch:
+ enabled: %ergebnis.allRules%
+ privateInFinalClass:
+ enabled: %ergebnis.allRules%
+ testCaseWithSuffix:
+ enabled: %ergebnis.allRules%
+
+parametersSchema:
+ ergebnis: structure([
+ allRules: bool()
+ declareStrictTypes: structure([
+ enabled: bool(),
+ ])
+ final: structure([
+ allowAbstractClasses: bool()
+ classesNotRequiredToBeAbstractOrFinal: listOf(string())
+ enabled: bool(),
+ ])
+ finalInAbstractClass: structure([
+ enabled: bool(),
+ ])
+ noAssignByReference: structure([
+ enabled: bool(),
+ ])
+ noCompact: structure([
+ enabled: bool(),
+ ])
+ noConstructorParameterWithDefaultValue: structure([
+ enabled: bool(),
+ ])
+ noErrorSuppression: structure([
+ enabled: bool(),
+ ])
+ noExtends: structure([
+ classesAllowedToBeExtended: listOf(string())
+ enabled: bool(),
+ ])
+ noEval: structure([
+ enabled: bool(),
+ ])
+ noIsset: structure([
+ enabled: bool(),
+ ])
+ noNullableReturnTypeDeclaration: structure([
+ enabled: bool(),
+ ])
+ noParameterPassedByReference: structure([
+ enabled: bool(),
+ ])
+ noParameterWithContainerTypeDeclaration: structure([
+ enabled: bool(),
+ interfacesImplementedByContainers: listOf(string())
+ methodsAllowedToUseContainerTypeDeclarations: listOf(string())
+ ])
+ noParameterWithNullableTypeDeclaration: structure([
+ enabled: bool(),
+ ])
+ noParameterWithNullDefaultValue: structure([
+ enabled: bool(),
+ ])
+ noReturnByReference: structure([
+ enabled: bool(),
+ ])
+ noSwitch: structure([
+ enabled: bool(),
+ ])
+ privateInFinalClass: structure([
+ enabled: bool(),
+ ])
+ testCaseWithSuffix: structure([
+ enabled: bool(),
+ ])
+ ])
+
+services:
+ -
+ class: Ergebnis\PHPStan\Rules\Analyzer
+
+ -
+ class: Ergebnis\PHPStan\Rules\Classes\FinalRule
+ arguments:
+ allowAbstractClasses: %ergebnis.final.allowAbstractClasses%
+ classesNotRequiredToBeAbstractOrFinal: %ergebnis.final.classesNotRequiredToBeAbstractOrFinal%
+
+ -
+ class: Ergebnis\PHPStan\Rules\Classes\NoExtendsRule
+ arguments:
+ classesAllowedToBeExtended: %ergebnis.noExtends.classesAllowedToBeExtended%
+
+ -
+ class: Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Expressions\NoCompactRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Expressions\NoEvalRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Expressions\NoIssetRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule
+ arguments:
+ interfacesImplementedByContainers: %ergebnis.noParameterWithContainerTypeDeclaration.interfacesImplementedByContainers%
+ methodsAllowedToUseContainerTypeDeclarations: %ergebnis.noParameterWithContainerTypeDeclaration.methodsAllowedToUseContainerTypeDeclarations%
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule
+
+ -
+ class: Ergebnis\PHPStan\Rules\Statements\NoSwitchRule
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php
new file mode 100644
index 0000000..938cab7
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php
@@ -0,0 +1,68 @@
+default instanceof Node\Expr\ConstFetch) {
+ return false;
+ }
+
+ return 'null' === $parameter->default->name->toLowerString();
+ }
+
+ /**
+ * @param null|Node\ComplexType|Node\Identifier|Node\Name $typeDeclaration
+ */
+ public function isNullableTypeDeclaration($typeDeclaration): bool
+ {
+ if ($typeDeclaration instanceof Node\NullableType) {
+ return true;
+ }
+
+ if ($typeDeclaration instanceof Node\UnionType) {
+ foreach ($typeDeclaration->types as $type) {
+ if (
+ $type instanceof Node\Identifier
+ && 'null' === $type->toLowerString()
+ ) {
+ return true;
+ }
+
+ if (
+ $type instanceof Node\Name\FullyQualified
+ && $type->hasAttribute('originalName')
+ ) {
+ $originalName = $type->getAttribute('originalName');
+
+ if (
+ $originalName instanceof Node\Name
+ && 'null' === $originalName->toLowerString()
+ ) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php
new file mode 100644
index 0000000..937bf0e
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php
@@ -0,0 +1,162 @@
+
+ */
+final class FinalRule implements Rules\Rule
+{
+ /**
+ * @var list
+ */
+ private static array $whitelistedAnnotations = [
+ 'Entity',
+ 'ORM\Entity',
+ 'ORM\Mapping\Entity',
+ ];
+
+ /**
+ * @var list
+ */
+ private static array $whitelistedAttributes = [
+ ORM\Mapping\Entity::class,
+ ];
+ private bool $allowAbstractClasses;
+
+ /**
+ * @var list
+ */
+ private array $classesNotRequiredToBeAbstractOrFinal;
+ private string $errorMessageTemplate = 'Class %s is not final.';
+
+ /**
+ * @param list $classesNotRequiredToBeAbstractOrFinal
+ */
+ public function __construct(
+ bool $allowAbstractClasses,
+ array $classesNotRequiredToBeAbstractOrFinal
+ ) {
+ $this->allowAbstractClasses = $allowAbstractClasses;
+ $this->classesNotRequiredToBeAbstractOrFinal = \array_map(static function (string $classNotRequiredToBeAbstractOrFinal): string {
+ return $classNotRequiredToBeAbstractOrFinal;
+ }, $classesNotRequiredToBeAbstractOrFinal);
+
+ if ($allowAbstractClasses) {
+ $this->errorMessageTemplate = 'Class %s is neither abstract nor final.';
+ }
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Class_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (!isset($node->namespacedName)) {
+ return [];
+ }
+
+ if (\in_array($node->namespacedName->toString(), $this->classesNotRequiredToBeAbstractOrFinal, true)) {
+ return [];
+ }
+
+ if (
+ $this->allowAbstractClasses
+ && $node->isAbstract()
+ ) {
+ return [];
+ }
+
+ if ($node->isFinal()) {
+ return [];
+ }
+
+ if ($this->hasWhitelistedAnnotation($node)) {
+ return [];
+ }
+
+ if ($this->hasWhitelistedAttribute($node)) {
+ return [];
+ }
+
+ $message = \sprintf(
+ $this->errorMessageTemplate,
+ $node->namespacedName->toString(),
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::final()->toString())
+ ->build(),
+ ];
+ }
+
+ /**
+ * This method is inspired by the work on PhpCsFixer\Fixer\ClassNotation\FinalClassFixer and
+ * PhpCsFixer\Fixer\ClassNotation\FinalInternalClassFixer contributed by Dariusz Rumiński, Filippo Tessarotto, and
+ * Spacepossum for friendsofphp/php-cs-fixer.
+ *
+ * @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalClassFixer.php
+ * @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalInternalClassFixer.php
+ * @see https://github.com/keradus
+ * @see https://github.com/SpacePossum
+ * @see https://github.com/Slamdunk
+ */
+ private function hasWhitelistedAnnotation(Node\Stmt\Class_ $node): bool
+ {
+ $docComment = $node->getDocComment();
+
+ if (!$docComment instanceof Comment\Doc) {
+ return false;
+ }
+
+ $reformattedComment = $docComment->getReformattedText();
+
+ if (\is_int(\preg_match_all('/@(\S+)(?=\s|$)/', $reformattedComment, $matches))) {
+ foreach ($matches[1] as $annotation) {
+ foreach (self::$whitelistedAnnotations as $whitelistedAnnotation) {
+ if (0 === \mb_strpos($annotation, $whitelistedAnnotation)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private function hasWhitelistedAttribute(Node\Stmt\Class_ $node): bool
+ {
+ foreach ($node->attrGroups as $attributeGroup) {
+ foreach ($attributeGroup->attrs as $attribute) {
+ if (\in_array($attribute->name->toString(), self::$whitelistedAttributes, true)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php
new file mode 100644
index 0000000..93ecf7c
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php
@@ -0,0 +1,98 @@
+
+ */
+final class NoExtendsRule implements Rules\Rule
+{
+ /**
+ * @var list
+ */
+ private static array $defaultClassesAllowedToBeExtended = [
+ 'PHPUnit\\Framework\\TestCase',
+ ];
+
+ /**
+ * @var list
+ */
+ private array $classesAllowedToBeExtended;
+
+ /**
+ * @param list $classesAllowedToBeExtended
+ */
+ public function __construct(array $classesAllowedToBeExtended)
+ {
+ $this->classesAllowedToBeExtended = \array_values(\array_unique(\array_merge(
+ self::$defaultClassesAllowedToBeExtended,
+ \array_map(static function (string $classAllowedToBeExtended): string {
+ /** @var class-string $classAllowedToBeExtended */
+ return $classAllowedToBeExtended;
+ }, $classesAllowedToBeExtended),
+ )));
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Class_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (!$node->extends instanceof Node\Name) {
+ return [];
+ }
+
+ $extendedClassName = $node->extends->toString();
+
+ if (\in_array($extendedClassName, $this->classesAllowedToBeExtended, true)) {
+ return [];
+ }
+
+ if (!isset($node->namespacedName)) {
+ $message = \sprintf(
+ 'Anonymous class is not allowed to extend "%s".',
+ $extendedClassName,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noExtends()->toString())
+ ->build(),
+ ];
+ }
+
+ $extendingClassName = $node->namespacedName->toString();
+
+ $message = \sprintf(
+ 'Class "%s" is not allowed to extend "%s".',
+ $extendingClassName,
+ $extendedClassName,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noExtends()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php
new file mode 100644
index 0000000..76e96c5
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php
@@ -0,0 +1,95 @@
+
+ */
+final class TestCaseWithSuffixRule implements Rules\Rule
+{
+ /**
+ * @var list
+ */
+ private static array $phpunitTestCaseClassNames = [
+ 'PHPUnit\Framework\TestCase',
+ ];
+ private Reflection\ReflectionProvider $reflectionProvider;
+
+ public function __construct(Reflection\ReflectionProvider $reflectionProvider)
+ {
+ $this->reflectionProvider = $reflectionProvider;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Class_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if ($node->isAbstract()) {
+ return [];
+ }
+
+ if (!$node->extends instanceof Node\Name) {
+ return [];
+ }
+
+ if (!isset($node->namespacedName)) {
+ return [];
+ }
+
+ $fullyQualifiedClassName = $node->namespacedName->toString();
+
+ $classReflection = $this->reflectionProvider->getClass($fullyQualifiedClassName);
+
+ $extendedPhpunitTestCaseClassName = '';
+
+ foreach (self::$phpunitTestCaseClassNames as $phpunitTestCaseClassName) {
+ if ($classReflection->isSubclassOf($phpunitTestCaseClassName)) {
+ $extendedPhpunitTestCaseClassName = $phpunitTestCaseClassName;
+
+ break;
+ }
+ }
+
+ if ('' === $extendedPhpunitTestCaseClassName) {
+ return [];
+ }
+
+ if (1 === \preg_match('/Test$/', $fullyQualifiedClassName)) {
+ return [];
+ }
+
+ $message = \sprintf(
+ 'Class %s extends %s, is concrete, but does not have a Test suffix.',
+ $fullyQualifiedClassName,
+ $extendedPhpunitTestCaseClassName,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::testCaseWithSuffix()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php
new file mode 100644
index 0000000..6435157
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php
@@ -0,0 +1,53 @@
+
+ */
+final class NoNullableReturnTypeDeclarationRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Expr\Closure::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) {
+ return [];
+ }
+
+ return [
+ Rules\RuleErrorBuilder::message('Closure has a nullable return type declaration.')
+ ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php
new file mode 100644
index 0000000..2c47cb7
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php
@@ -0,0 +1,64 @@
+
+ */
+final class NoParameterPassedByReferenceRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Expr\Closure::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool {
+ return $parameter->byRef;
+ }));
+
+ if (0 === \count($parametersPassedByReference)) {
+ return [];
+ }
+
+ return \array_map(static function (Node\Param $parameterPassedByReference): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterPassedByReference->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Closure has parameter $%s that is passed by reference.',
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString())
+ ->build();
+ }, $parametersPassedByReference);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php
new file mode 100644
index 0000000..94bd146
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php
@@ -0,0 +1,72 @@
+
+ */
+final class NoParameterWithNullDefaultValueRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Expr\Closure::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool {
+ return $this->analyzer->hasNullDefaultValue($parameter);
+ }));
+
+ if (0 === \count($parametersWithNullDefaultValue)) {
+ return [];
+ }
+
+ return \array_map(static function (Node\Param $parameterWithNullDefaultValue): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullDefaultValue->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Closure has parameter $%s with null as default value.',
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString())
+ ->build();
+ }, $parametersWithNullDefaultValue);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php
new file mode 100644
index 0000000..3cff6ee
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php
@@ -0,0 +1,72 @@
+
+ */
+final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Expr\Closure::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool {
+ return $this->analyzer->isNullableTypeDeclaration($parameter->type);
+ }));
+
+ if (0 === \count($parametersWithNullableTypeDeclaration)) {
+ return [];
+ }
+
+ return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullableTypeDeclaration->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Closure has parameter $%s with a nullable type declaration.',
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString())
+ ->build();
+ }, $parametersWithNullableTypeDeclaration);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php
new file mode 100644
index 0000000..3fcbb10
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php
@@ -0,0 +1,130 @@
+value = $value;
+ }
+
+ public static function declareStrictTypes(): self
+ {
+ return new self('declareStrictTypes');
+ }
+
+ public static function final(): self
+ {
+ return new self('final');
+ }
+
+ public static function finalInAbstractClass(): self
+ {
+ return new self('finalInAbstractClass');
+ }
+
+ public static function noCompact(): self
+ {
+ return new self('noCompact');
+ }
+
+ public static function noConstructorParameterWithDefaultValue(): self
+ {
+ return new self('noConstructorParameterWithDefaultValue');
+ }
+
+ public static function noAssignByReference(): self
+ {
+ return new self('noAssignByReference');
+ }
+
+ public static function noErrorSuppression(): self
+ {
+ return new self('noErrorSuppression');
+ }
+
+ public static function noEval(): self
+ {
+ return new self('noEval');
+ }
+
+ public static function noExtends(): self
+ {
+ return new self('noExtends');
+ }
+
+ public static function noIsset(): self
+ {
+ return new self('noIsset');
+ }
+
+ public static function noParameterPassedByReference(): self
+ {
+ return new self('noParameterPassedByReference');
+ }
+
+ public static function noParameterWithContainerTypeDeclaration(): self
+ {
+ return new self('noParameterWithContainerTypeDeclaration');
+ }
+
+ public static function noParameterWithNullDefaultValue(): self
+ {
+ return new self('noParameterWithNullDefaultValue');
+ }
+
+ public static function noParameterWithNullableTypeDeclaration(): self
+ {
+ return new self('noParameterWithNullableTypeDeclaration');
+ }
+
+ public static function noNullableReturnTypeDeclaration(): self
+ {
+ return new self('noNullableReturnTypeDeclaration');
+ }
+
+ public static function noReturnByReference(): self
+ {
+ return new self('noReturnByReference');
+ }
+
+ public static function noSwitch(): self
+ {
+ return new self('noSwitch');
+ }
+
+ public static function privateInFinalClass(): self
+ {
+ return new self('privateInFinalClass');
+ }
+
+ public static function testCaseWithSuffix(): self
+ {
+ return new self('testCaseWithSuffix');
+ }
+
+ public function toString(): string
+ {
+ return \sprintf(
+ 'ergebnis.%s',
+ $this->value,
+ );
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php
new file mode 100644
index 0000000..ad8cc1e
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php
@@ -0,0 +1,41 @@
+
+ */
+final class NoAssignByReferenceRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Expr\AssignRef::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ return [
+ Rules\RuleErrorBuilder::message('Assign by reference should not be used.')
+ ->identifier(ErrorIdentifier::noAssignByReference()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php
new file mode 100644
index 0000000..cd12ffe
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php
@@ -0,0 +1,49 @@
+
+ */
+final class NoCompactRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Expr\FuncCall::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (!$node->name instanceof Node\Name) {
+ return [];
+ }
+
+ if ('compact' !== \mb_strtolower($scope->resolveName($node->name))) {
+ return [];
+ }
+
+ return [
+ Rules\RuleErrorBuilder::message('Function compact() should not be used.')
+ ->identifier(ErrorIdentifier::noCompact()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php
new file mode 100644
index 0000000..49c0939
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php
@@ -0,0 +1,41 @@
+
+ */
+final class NoErrorSuppressionRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Expr\ErrorSuppress::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ return [
+ Rules\RuleErrorBuilder::message('Error suppression via "@" should not be used.')
+ ->identifier(ErrorIdentifier::noErrorSuppression()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php
new file mode 100644
index 0000000..b542057
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php
@@ -0,0 +1,41 @@
+
+ */
+final class NoEvalRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Expr\Eval_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ return [
+ Rules\RuleErrorBuilder::message('Language construct eval() should not be used.')
+ ->identifier(ErrorIdentifier::noEval()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php
new file mode 100644
index 0000000..f1443d6
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php
@@ -0,0 +1,41 @@
+
+ */
+final class NoIssetRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Expr\Isset_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ return [
+ Rules\RuleErrorBuilder::message('Language construct isset() should not be used.')
+ ->identifier(ErrorIdentifier::noIsset()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php
new file mode 100644
index 0000000..5f9ec11
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php
@@ -0,0 +1,70 @@
+
+ */
+final class DeclareStrictTypesRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return FileNode::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ $nodes = $node->getNodes();
+
+ if (0 === \count($nodes)) {
+ return [];
+ }
+
+ $firstNode = \array_shift($nodes);
+
+ if (
+ $firstNode instanceof Node\Stmt\InlineHTML
+ && 2 === $firstNode->getEndLine()
+ && 0 === \mb_strpos($firstNode->value, '#!')
+ ) {
+ $firstNode = \array_shift($nodes);
+ }
+
+ if ($firstNode instanceof Node\Stmt\Declare_) {
+ foreach ($firstNode->declares as $declare) {
+ if (
+ 'strict_types' === $declare->key->toLowerString()
+ && $declare->value instanceof Node\Scalar\LNumber
+ && 1 === $declare->value->value
+ ) {
+ return [];
+ }
+ }
+ }
+
+ return [
+ Rules\RuleErrorBuilder::message('File is missing a "declare(strict_types=1)" declaration.')
+ ->identifier(ErrorIdentifier::declareStrictTypes()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php
new file mode 100644
index 0000000..06da4f7
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php
@@ -0,0 +1,62 @@
+
+ */
+final class NoNullableReturnTypeDeclarationRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Function_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (!isset($node->namespacedName)) {
+ return [];
+ }
+
+ if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) {
+ return [];
+ }
+
+ $message = \sprintf(
+ 'Function %s() has a nullable return type declaration.',
+ $node->namespacedName->toString(),
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php
new file mode 100644
index 0000000..e461aa1
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php
@@ -0,0 +1,67 @@
+
+ */
+final class NoParameterPassedByReferenceRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Function_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool {
+ return $parameter->byRef;
+ }));
+
+ if (0 === \count($parametersPassedByReference)) {
+ return [];
+ }
+
+ $functionName = $node->namespacedName;
+
+ return \array_map(static function (Node\Param $parameterPassedByReference) use ($functionName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterPassedByReference->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Function %s() has parameter $%s that is passed by reference.',
+ $functionName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString())
+ ->build();
+ }, $parametersPassedByReference);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php
new file mode 100644
index 0000000..fe82a4e
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php
@@ -0,0 +1,75 @@
+
+ */
+final class NoParameterWithNullDefaultValueRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Function_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool {
+ return $this->analyzer->hasNullDefaultValue($parameter);
+ }));
+
+ if (0 === \count($parametersWithNullDefaultValue)) {
+ return [];
+ }
+
+ $functionName = $node->namespacedName;
+
+ return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($functionName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullDefaultValue->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Function %s() has parameter $%s with null as default value.',
+ $functionName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString())
+ ->build();
+ }, $parametersWithNullDefaultValue);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php
new file mode 100644
index 0000000..9027553
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php
@@ -0,0 +1,75 @@
+
+ */
+final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Function_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool {
+ return $this->analyzer->isNullableTypeDeclaration($parameter->type);
+ }));
+
+ if (0 === \count($parametersWithNullableTypeDeclaration)) {
+ return [];
+ }
+
+ $functionName = $node->namespacedName;
+
+ return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($functionName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullableTypeDeclaration->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Function %s() has parameter $%s with a nullable type declaration.',
+ $functionName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString())
+ ->build();
+ }, $parametersWithNullableTypeDeclaration);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php
new file mode 100644
index 0000000..0efb700
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php
@@ -0,0 +1,50 @@
+
+ */
+final class NoReturnByReferenceRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Function_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (false === $node->byRef) {
+ return [];
+ }
+
+ $message = \sprintf(
+ 'Function %s() returns by reference.',
+ $node->namespacedName,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noReturnByReference()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php
new file mode 100644
index 0000000..89fc226
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php
@@ -0,0 +1,123 @@
+
+ */
+final class FinalInAbstractClassRule implements Rules\Rule
+{
+ private const DOCTRINE_ATTRIBUTE_NAMES = [
+ 'Doctrine\\ORM\\Mapping\\Embeddable',
+ 'Doctrine\\ORM\\Mapping\\Entity',
+ ];
+ private const DOCTRINE_TAG_NAMES = [
+ '@ORM\\Mapping\\Embeddable',
+ '@ORM\\Embeddable',
+ '@Embeddable',
+ '@ORM\\Mapping\\Entity',
+ '@ORM\\Entity',
+ '@Entity',
+ ];
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ /** @var Reflection\ClassReflection $containingClass */
+ $containingClass = $scope->getClassReflection();
+
+ if (self::isDoctrineEntity($containingClass)) {
+ return [];
+ }
+
+ if (!$containingClass->isAbstract()) {
+ return [];
+ }
+
+ if ($containingClass->isInterface()) {
+ return [];
+ }
+
+ if ($node->isAbstract()) {
+ return [];
+ }
+
+ if ($node->isFinal()) {
+ return [];
+ }
+
+ if ($node->isPrivate()) {
+ return [];
+ }
+
+ if ('__construct' === $node->name->name) {
+ return [];
+ }
+
+ $message = \sprintf(
+ 'Method %s::%s() is not final, but since the containing class is abstract, it should be.',
+ $containingClass->getName(),
+ $node->name->toString(),
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::finalInAbstractClass()->toString())
+ ->build(),
+ ];
+ }
+
+ private static function isDoctrineEntity(Reflection\ClassReflection $containingClass): bool
+ {
+ $attributes = $containingClass->getNativeReflection()->getAttributes();
+
+ foreach ($attributes as $attribute) {
+ if (\in_array($attribute->getName(), self::DOCTRINE_ATTRIBUTE_NAMES, true)) {
+ return true;
+ }
+ }
+
+ $resolvedPhpDocBlock = $containingClass->getResolvedPhpDoc();
+
+ if ($resolvedPhpDocBlock instanceof PhpDoc\ResolvedPhpDocBlock) {
+ foreach ($resolvedPhpDocBlock->getPhpDocNodes() as $phpDocNode) {
+ foreach ($phpDocNode->children as $child) {
+ if (!$child instanceof PhpDocParser\Ast\PhpDoc\PhpDocTagNode) {
+ continue;
+ }
+
+ if (\in_array($child->name, self::DOCTRINE_TAG_NAMES, true)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php
new file mode 100644
index 0000000..e8b7e34
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php
@@ -0,0 +1,99 @@
+
+ */
+final class NoConstructorParameterWithDefaultValueRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if ('__construct' !== $node->name->toLowerString()) {
+ return [];
+ }
+
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersWithDefaultValue = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool {
+ return self::hasDefaultValue($parameter);
+ }));
+
+ if (0 === \count($parametersWithDefaultValue)) {
+ return [];
+ }
+
+ /** @var Reflection\ClassReflection $classReflection */
+ $classReflection = $scope->getClassReflection();
+
+ if ($classReflection->isAnonymous()) {
+ return \array_map(static function (Node\Param $parameterWithDefaultValue): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithDefaultValue->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Constructor in anonymous class has parameter $%s with default value.',
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noConstructorParameterWithDefaultValue()->toString())
+ ->build();
+ }, $parametersWithDefaultValue);
+ }
+
+ $className = $classReflection->getName();
+
+ return \array_map(static function (Node\Param $parameterWithDefaultValue) use ($className): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithDefaultValue->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Constructor in %s has parameter $%s with default value.',
+ $className,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noConstructorParameterWithDefaultValue()->toString())
+ ->build();
+ }, $parametersWithDefaultValue);
+ }
+
+ private static function hasDefaultValue(Node\Param $parameter): bool
+ {
+ return null !== $parameter->default;
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php
new file mode 100644
index 0000000..e0e84c9
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php
@@ -0,0 +1,76 @@
+
+ */
+final class NoNullableReturnTypeDeclarationRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) {
+ return [];
+ }
+
+ /** @var Reflection\ClassReflection $classReflection */
+ $classReflection = $scope->getClassReflection();
+
+ if ($classReflection->isAnonymous()) {
+ $message = \sprintf(
+ 'Method %s() in anonymous class has a nullable return type declaration.',
+ $node->name->name,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString())
+ ->build(),
+ ];
+ }
+
+ $message = \sprintf(
+ 'Method %s::%s() has a nullable return type declaration.',
+ $classReflection->getName(),
+ $node->name->name,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php
new file mode 100644
index 0000000..4c90f69
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php
@@ -0,0 +1,94 @@
+
+ */
+final class NoParameterPassedByReferenceRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersExplicitlyPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool {
+ return $parameter->byRef;
+ }));
+
+ if (0 === \count($parametersExplicitlyPassedByReference)) {
+ return [];
+ }
+
+ $methodName = $node->name->toString();
+
+ /** @var Reflection\ClassReflection $classReflection */
+ $classReflection = $scope->getClassReflection();
+
+ if ($classReflection->isAnonymous()) {
+ return \array_map(static function (Node\Param $parameterExplicitlyPassedByReference) use ($methodName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterExplicitlyPassedByReference->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Method %s() in anonymous class has parameter $%s that is passed by reference.',
+ $methodName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString())
+ ->build();
+ }, $parametersExplicitlyPassedByReference);
+ }
+
+ $className = $classReflection->getName();
+
+ return \array_map(static function (Node\Param $parameterExplicitlyPassedByReference) use ($className, $methodName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterExplicitlyPassedByReference->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Method %s::%s() has parameter $%s that is passed by reference.',
+ $className,
+ $methodName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString())
+ ->build();
+ }, $parametersExplicitlyPassedByReference);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php
new file mode 100644
index 0000000..f64a831
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php
@@ -0,0 +1,179 @@
+
+ */
+final class NoParameterWithContainerTypeDeclarationRule implements Rules\Rule
+{
+ private Reflection\ReflectionProvider $reflectionProvider;
+
+ /**
+ * @var list
+ */
+ private array $interfacesImplementedByContainers;
+
+ /**
+ * @var list
+ */
+ private array $methodsAllowedToUseContainerTypeDeclarations;
+
+ /**
+ * @param list $interfacesImplementedByContainers
+ * @param list $methodsAllowedToUseContainerTypeDeclarations
+ */
+ public function __construct(
+ Reflection\ReflectionProvider $reflectionProvider,
+ array $interfacesImplementedByContainers,
+ array $methodsAllowedToUseContainerTypeDeclarations
+ ) {
+ $this->reflectionProvider = $reflectionProvider;
+ $this->interfacesImplementedByContainers = \array_values(\array_filter(
+ \array_map(static function (string $interfaceImplementedByContainers): string {
+ return $interfaceImplementedByContainers;
+ }, $interfacesImplementedByContainers),
+ static function (string $interfaceImplementedByContainer): bool {
+ return \interface_exists($interfaceImplementedByContainer);
+ },
+ ));
+ $this->methodsAllowedToUseContainerTypeDeclarations = $methodsAllowedToUseContainerTypeDeclarations;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($this->interfacesImplementedByContainers)) {
+ return [];
+ }
+
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $methodName = $node->name->toString();
+
+ if (\in_array($methodName, $this->methodsAllowedToUseContainerTypeDeclarations, true)) {
+ return [];
+ }
+
+ /** @var Reflection\ClassReflection $containingClass */
+ $containingClass = $scope->getClassReflection();
+
+ return \array_values(\array_reduce(
+ $node->params,
+ function (array $errors, Node\Param $node) use ($scope, $containingClass, $methodName): array {
+ $type = $node->type;
+
+ if (!$type instanceof Node\Name) {
+ return $errors;
+ }
+
+ /** @var Node\Expr\Variable $variable */
+ $variable = $node->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $classUsedInTypeDeclaration = $this->reflectionProvider->getClass($scope->resolveName($type));
+
+ if ($classUsedInTypeDeclaration->isInterface()) {
+ foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) {
+ if ($classUsedInTypeDeclaration->getName() === $interfaceImplementedByContainer) {
+ $errors[] = self::createError(
+ $containingClass,
+ $methodName,
+ $parameterName,
+ $classUsedInTypeDeclaration,
+ );
+
+ return $errors;
+ }
+
+ if ($classUsedInTypeDeclaration->getNativeReflection()->isSubclassOf($interfaceImplementedByContainer)) {
+ $errors[] = self::createError(
+ $containingClass,
+ $methodName,
+ $parameterName,
+ $classUsedInTypeDeclaration,
+ );
+
+ return $errors;
+ }
+ }
+ }
+
+ foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) {
+ if ($classUsedInTypeDeclaration->getNativeReflection()->implementsInterface($interfaceImplementedByContainer)) {
+ $errors[] = self::createError(
+ $containingClass,
+ $methodName,
+ $parameterName,
+ $classUsedInTypeDeclaration,
+ );
+
+ return $errors;
+ }
+ }
+
+ return $errors;
+ },
+ [],
+ ));
+ }
+
+ private static function createError(
+ Reflection\ClassReflection $classReflection,
+ string $methodName,
+ string $parameterName,
+ Reflection\ClassReflection $classUsedInTypeDeclaration
+ ): Rules\RuleError {
+ if ($classReflection->isAnonymous()) {
+ $message = \sprintf(
+ 'Method %s() in anonymous class has a parameter $%s with a type declaration of %s, but containers should not be injected.',
+ $methodName,
+ $parameterName,
+ $classUsedInTypeDeclaration->getName(),
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithContainerTypeDeclaration()->toString())
+ ->build();
+ }
+
+ $message = \sprintf(
+ 'Method %s::%s() has a parameter $%s with a type declaration of %s, but containers should not be injected.',
+ $classReflection->getName(),
+ $methodName,
+ $parameterName,
+ $classUsedInTypeDeclaration->getName(),
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithContainerTypeDeclaration()->toString())
+ ->build();
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php
new file mode 100644
index 0000000..9888974
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php
@@ -0,0 +1,102 @@
+
+ */
+final class NoParameterWithNullDefaultValueRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool {
+ return $this->analyzer->hasNullDefaultValue($parameter);
+ }));
+
+ if (0 === \count($parametersWithNullDefaultValue)) {
+ return [];
+ }
+
+ $methodName = $node->name->toString();
+
+ /** @var Reflection\ClassReflection $classReflection */
+ $classReflection = $scope->getClassReflection();
+
+ if ($classReflection->isAnonymous()) {
+ return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($methodName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullDefaultValue->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Method %s() in anonymous class has parameter $%s with null as default value.',
+ $methodName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString())
+ ->build();
+ }, $parametersWithNullDefaultValue);
+ }
+
+ $className = $classReflection->getName();
+
+ return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($className, $methodName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullDefaultValue->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Method %s::%s() has parameter $%s with null as default value.',
+ $className,
+ $methodName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString())
+ ->build();
+ }, $parametersWithNullDefaultValue);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php
new file mode 100644
index 0000000..632cd7b
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php
@@ -0,0 +1,102 @@
+
+ */
+final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule
+{
+ private Analyzer $analyzer;
+
+ public function __construct(Analyzer $analyzer)
+ {
+ $this->analyzer = $analyzer;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (0 === \count($node->params)) {
+ return [];
+ }
+
+ $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool {
+ return $this->analyzer->isNullableTypeDeclaration($parameter->type);
+ }));
+
+ if (0 === \count($parametersWithNullableTypeDeclaration)) {
+ return [];
+ }
+
+ $methodName = $node->name->toString();
+
+ /** @var Reflection\ClassReflection $classReflection */
+ $classReflection = $scope->getClassReflection();
+
+ if ($classReflection->isAnonymous()) {
+ return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($methodName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullableTypeDeclaration->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Method %s() in anonymous class has parameter $%s with a nullable type declaration.',
+ $methodName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString())
+ ->build();
+ }, $parametersWithNullableTypeDeclaration);
+ }
+
+ $className = $classReflection->getName();
+
+ return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($className, $methodName): Rules\RuleError {
+ /** @var Node\Expr\Variable $variable */
+ $variable = $parameterWithNullableTypeDeclaration->var;
+
+ /** @var string $parameterName */
+ $parameterName = $variable->name;
+
+ $message = \sprintf(
+ 'Method %s::%s() has parameter $%s with a nullable type declaration.',
+ $className,
+ $methodName,
+ $parameterName,
+ );
+
+ return Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString())
+ ->build();
+ }, $parametersWithNullableTypeDeclaration);
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php
new file mode 100644
index 0000000..a1bf87f
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php
@@ -0,0 +1,72 @@
+
+ */
+final class NoReturnByReferenceRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ if (false === $node->byRef) {
+ return [];
+ }
+
+ $methodName = $node->name->toString();
+
+ /** @var Reflection\ClassReflection $classReflection */
+ $classReflection = $scope->getClassReflection();
+
+ if ($classReflection->isAnonymous()) {
+ $message = \sprintf(
+ 'Method %s() in anonymous class returns by reference.',
+ $methodName,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noReturnByReference()->toString())
+ ->build(),
+ ];
+ }
+
+ $className = $classReflection->getName();
+
+ $message = \sprintf(
+ 'Method %s::%s() returns by reference.',
+ $className,
+ $methodName,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::noReturnByReference()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php
new file mode 100644
index 0000000..9aa2c3c
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php
@@ -0,0 +1,198 @@
+
+ */
+final class PrivateInFinalClassRule implements Rules\Rule
+{
+ /**
+ * @var list
+ */
+ private static array $whitelistedAnnotations = [
+ '@after',
+ '@before',
+ '@postCondition',
+ '@preCondition',
+ ];
+
+ /**
+ * @var list
+ */
+ private static array $whitelistedAttributes = [
+ Framework\Attributes\After::class,
+ Framework\Attributes\Before::class,
+ Framework\Attributes\PostCondition::class,
+ Framework\Attributes\PreCondition::class,
+ ];
+ private Type\FileTypeMapper $fileTypeMapper;
+
+ public function __construct(Type\FileTypeMapper $fileTypeMapper)
+ {
+ $this->fileTypeMapper = $fileTypeMapper;
+ }
+
+ public function getNodeType(): string
+ {
+ return Node\Stmt\ClassMethod::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ /** @var Reflection\ClassReflection $containingClass */
+ $containingClass = $scope->getClassReflection();
+
+ if (!$containingClass->isFinal()) {
+ return [];
+ }
+
+ if ($node->isPublic()) {
+ return [];
+ }
+
+ if ($node->isPrivate()) {
+ return [];
+ }
+
+ if ($this->hasWhitelistedAnnotation($node, $containingClass)) {
+ return [];
+ }
+
+ if (self::hasWhitelistedAttribute($node)) {
+ return [];
+ }
+
+ $methodName = $node->name->toString();
+
+ if (self::isDeclaredByParentClass($containingClass, $methodName)) {
+ return [];
+ }
+
+ if (self::isDeclaredByTrait($containingClass, $methodName)) {
+ return [];
+ }
+
+ /** @var Reflection\ClassReflection $classReflection */
+ $classReflection = $scope->getClassReflection();
+
+ if ($classReflection->isAnonymous()) {
+ $message = \sprintf(
+ 'Method %s() in anonymous class is protected, but since the containing class is final, it can be private.',
+ $node->name->name,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::privateInFinalClass()->toString())
+ ->build(),
+ ];
+ }
+
+ $message = \sprintf(
+ 'Method %s::%s() is protected, but since the containing class is final, it can be private.',
+ $containingClass->getName(),
+ $methodName,
+ );
+
+ return [
+ Rules\RuleErrorBuilder::message($message)
+ ->identifier(ErrorIdentifier::privateInFinalClass()->toString())
+ ->build(),
+ ];
+ }
+
+ private function hasWhitelistedAnnotation(
+ Node\Stmt\ClassMethod $node,
+ Reflection\ClassReflection $containingClass
+ ): bool {
+ $docComment = $node->getDocComment();
+
+ if (!$docComment instanceof Comment\Doc) {
+ return false;
+ }
+
+ $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
+ null,
+ $containingClass->getName(),
+ null,
+ null,
+ $docComment->getText(),
+ );
+
+ foreach ($resolvedPhpDoc->getPhpDocNodes() as $phpDocNode) {
+ foreach ($phpDocNode->getTags() as $tag) {
+ if (\in_array($tag->name, self::$whitelistedAnnotations, true)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static function hasWhitelistedAttribute(Node\Stmt\ClassMethod $node): bool
+ {
+ foreach ($node->attrGroups as $attributeGroup) {
+ foreach ($attributeGroup->attrs as $attribute) {
+ if (\in_array($attribute->name->toString(), self::$whitelistedAttributes, true)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static function isDeclaredByParentClass(
+ Reflection\ClassReflection $containingClass,
+ string $methodName
+ ): bool {
+ $parentClass = $containingClass->getNativeReflection()->getParentClass();
+
+ if (!$parentClass instanceof \ReflectionClass) {
+ return false;
+ }
+
+ if (!$parentClass->hasMethod($methodName)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static function isDeclaredByTrait(
+ Reflection\ClassReflection $containingClass,
+ string $methodName
+ ): bool {
+ foreach ($containingClass->getTraits() as $trait) {
+ if ($trait->hasMethod($methodName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php
new file mode 100644
index 0000000..127e5ee
--- /dev/null
+++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php
@@ -0,0 +1,41 @@
+
+ */
+final class NoSwitchRule implements Rules\Rule
+{
+ public function getNodeType(): string
+ {
+ return Node\Stmt\Switch_::class;
+ }
+
+ public function processNode(
+ Node $node,
+ Analyser\Scope $scope
+ ): array {
+ return [
+ Rules\RuleErrorBuilder::message('Control structures using switch should not be used.')
+ ->identifier(ErrorIdentifier::noSwitch()->toString())
+ ->build(),
+ ];
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php b/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php
new file mode 100644
index 0000000..25851af
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php
@@ -0,0 +1,13 @@
+
+✅ [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
+✅ [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …
+✅ [Finder](https://doc.nette.org/utils/finder) - finds files and directories
+✅ [Floats](https://doc.nette.org/utils/floats) - floating point numbers
+✅ [Helper Functions](https://doc.nette.org/utils/helpers)
+✅ [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
+✅ [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
+✅ [Iterables](https://doc.nette.org/utils/iterables)
+✅ [JSON](https://doc.nette.org/utils/json) - encoding and decoding
+✅ [Generating Random Strings](https://doc.nette.org/utils/random)
+✅ [Paginator](https://doc.nette.org/utils/paginator) - pagination math
+✅ [PHP Reflection](https://doc.nette.org/utils/reflection)
+✅ [Strings](https://doc.nette.org/utils/strings) - useful text functions
+✅ [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements
+✅ [Type](https://doc.nette.org/utils/type) - PHP data type
+✅ [Validation](https://doc.nette.org/utils/validators) - validate inputs
+
+
+
+Installation
+------------
+
+The recommended way to install is via Composer:
+
+```
+composer require nette/utils
+```
+
+Nette Utils 4.0 is compatible with PHP 8.0 to 8.4.
+
+
+
+[Support Me](https://github.com/sponsors/dg)
+--------------------------------------------
+
+Do you like Nette Utils? Are you looking forward to the new features?
+
+[](https://github.com/sponsors/dg)
+
+Thank you!
diff --git a/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php b/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php
new file mode 100644
index 0000000..d749d4e
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php
@@ -0,0 +1,22 @@
+counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0);
+ }
+
+
+ /**
+ * Is the current element the last one?
+ */
+ public function isLast(?int $gridWidth = null): bool
+ {
+ return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0);
+ }
+
+
+ /**
+ * Is the iterator empty?
+ */
+ public function isEmpty(): bool
+ {
+ return $this->counter === 0;
+ }
+
+
+ /**
+ * Is the counter odd?
+ */
+ public function isOdd(): bool
+ {
+ return $this->counter % 2 === 1;
+ }
+
+
+ /**
+ * Is the counter even?
+ */
+ public function isEven(): bool
+ {
+ return $this->counter % 2 === 0;
+ }
+
+
+ /**
+ * Returns the counter.
+ */
+ public function getCounter(): int
+ {
+ return $this->counter;
+ }
+
+
+ /**
+ * Returns the count of elements.
+ */
+ public function count(): int
+ {
+ $inner = $this->getInnerIterator();
+ if ($inner instanceof \Countable) {
+ return $inner->count();
+
+ } else {
+ throw new Nette\NotSupportedException('Iterator is not countable.');
+ }
+ }
+
+
+ /**
+ * Forwards to the next element.
+ */
+ public function next(): void
+ {
+ parent::next();
+ if (parent::valid()) {
+ $this->counter++;
+ }
+ }
+
+
+ /**
+ * Rewinds the Iterator.
+ */
+ public function rewind(): void
+ {
+ parent::rewind();
+ $this->counter = parent::valid() ? 1 : 0;
+ }
+
+
+ /**
+ * Returns the next key.
+ */
+ public function getNextKey(): mixed
+ {
+ return $this->getInnerIterator()->key();
+ }
+
+
+ /**
+ * Returns the next element.
+ */
+ public function getNextValue(): mixed
+ {
+ return $this->getInnerIterator()->current();
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php b/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php
new file mode 100644
index 0000000..284da29
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php
@@ -0,0 +1,33 @@
+callback = $callback;
+ }
+
+
+ public function current(): mixed
+ {
+ return ($this->callback)(parent::current(), parent::key());
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/SmartObject.php b/tools/.phpstan/vendor/nette/utils/src/SmartObject.php
new file mode 100644
index 0000000..3b2203f
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/SmartObject.php
@@ -0,0 +1,140 @@
+$name ?? null;
+ if (is_iterable($handlers)) {
+ foreach ($handlers as $handler) {
+ $handler(...$args);
+ }
+ } elseif ($handlers !== null) {
+ throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . get_debug_type($handlers) . ' given.');
+ }
+
+ return null;
+ }
+
+ ObjectHelpers::strictCall($class, $name);
+ }
+
+
+ /**
+ * @throws MemberAccessException
+ */
+ public static function __callStatic(string $name, array $args)
+ {
+ ObjectHelpers::strictStaticCall(static::class, $name);
+ }
+
+
+ /**
+ * @return mixed
+ * @throws MemberAccessException if the property is not defined.
+ */
+ public function &__get(string $name)
+ {
+ $class = static::class;
+
+ if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter
+ if (!($prop & 0b0001)) {
+ throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
+ }
+
+ $m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name);
+ if ($prop & 0b10000) {
+ $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
+ $loc = isset($trace['file'], $trace['line'])
+ ? " in $trace[file] on line $trace[line]"
+ : '';
+ trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
+ }
+
+ if ($prop & 0b0100) { // return by reference
+ return $this->$m();
+ } else {
+ $val = $this->$m();
+ return $val;
+ }
+ } else {
+ ObjectHelpers::strictGet($class, $name);
+ }
+ }
+
+
+ /**
+ * @throws MemberAccessException if the property is not defined or is read-only
+ */
+ public function __set(string $name, mixed $value): void
+ {
+ $class = static::class;
+
+ if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property
+ $this->$name = $value;
+
+ } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter
+ if (!($prop & 0b1000)) {
+ throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
+ }
+
+ $m = 'set' . ucfirst($name);
+ if ($prop & 0b10000) {
+ $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
+ $loc = isset($trace['file'], $trace['line'])
+ ? " in $trace[file] on line $trace[line]"
+ : '';
+ trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
+ }
+
+ $this->$m($value);
+
+ } else {
+ ObjectHelpers::strictSet($class, $name);
+ }
+ }
+
+
+ /**
+ * @throws MemberAccessException
+ */
+ public function __unset(string $name): void
+ {
+ $class = static::class;
+ if (!ObjectHelpers::hasProperty($class, $name)) {
+ throw new MemberAccessException("Cannot unset the property $class::\$$name.");
+ }
+ }
+
+
+ public function __isset(string $name): bool
+ {
+ return isset(ObjectHelpers::getMagicProperties(static::class)[$name]);
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/StaticClass.php b/tools/.phpstan/vendor/nette/utils/src/StaticClass.php
new file mode 100644
index 0000000..b1d8486
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/StaticClass.php
@@ -0,0 +1,34 @@
+
+ * @implements \ArrayAccess
+ */
+class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
+{
+ /**
+ * Transforms array to ArrayHash.
+ * @param array $array
+ */
+ public static function from(array $array, bool $recursive = true): static
+ {
+ $obj = new static;
+ foreach ($array as $key => $value) {
+ $obj->$key = $recursive && is_array($value)
+ ? static::from($value)
+ : $value;
+ }
+
+ return $obj;
+ }
+
+
+ /**
+ * Returns an iterator over all items.
+ * @return \Iterator
+ */
+ public function &getIterator(): \Iterator
+ {
+ foreach ((array) $this as $key => $foo) {
+ yield $key => $this->$key;
+ }
+ }
+
+
+ /**
+ * Returns items count.
+ */
+ public function count(): int
+ {
+ return count((array) $this);
+ }
+
+
+ /**
+ * Replaces or appends a item.
+ * @param array-key $key
+ * @param T $value
+ */
+ public function offsetSet($key, $value): void
+ {
+ if (!is_scalar($key)) { // prevents null
+ throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', get_debug_type($key)));
+ }
+
+ $this->$key = $value;
+ }
+
+
+ /**
+ * Returns a item.
+ * @param array-key $key
+ * @return T
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key)
+ {
+ return $this->$key;
+ }
+
+
+ /**
+ * Determines whether a item exists.
+ * @param array-key $key
+ */
+ public function offsetExists($key): bool
+ {
+ return isset($this->$key);
+ }
+
+
+ /**
+ * Removes the element from this list.
+ * @param array-key $key
+ */
+ public function offsetUnset($key): void
+ {
+ unset($this->$key);
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php
new file mode 100644
index 0000000..a402f9b
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php
@@ -0,0 +1,136 @@
+
+ * @implements \ArrayAccess
+ */
+class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
+{
+ use Nette\SmartObject;
+
+ private array $list = [];
+
+
+ /**
+ * Transforms array to ArrayList.
+ * @param list $array
+ */
+ public static function from(array $array): static
+ {
+ if (!Arrays::isList($array)) {
+ throw new Nette\InvalidArgumentException('Array is not valid list.');
+ }
+
+ $obj = new static;
+ $obj->list = $array;
+ return $obj;
+ }
+
+
+ /**
+ * Returns an iterator over all items.
+ * @return \Iterator
+ */
+ public function &getIterator(): \Iterator
+ {
+ foreach ($this->list as &$item) {
+ yield $item;
+ }
+ }
+
+
+ /**
+ * Returns items count.
+ */
+ public function count(): int
+ {
+ return count($this->list);
+ }
+
+
+ /**
+ * Replaces or appends a item.
+ * @param int|null $index
+ * @param T $value
+ * @throws Nette\OutOfRangeException
+ */
+ public function offsetSet($index, $value): void
+ {
+ if ($index === null) {
+ $this->list[] = $value;
+
+ } elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) {
+ throw new Nette\OutOfRangeException('Offset invalid or out of range');
+
+ } else {
+ $this->list[$index] = $value;
+ }
+ }
+
+
+ /**
+ * Returns a item.
+ * @param int $index
+ * @return T
+ * @throws Nette\OutOfRangeException
+ */
+ public function offsetGet($index): mixed
+ {
+ if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
+ throw new Nette\OutOfRangeException('Offset invalid or out of range');
+ }
+
+ return $this->list[$index];
+ }
+
+
+ /**
+ * Determines whether a item exists.
+ * @param int $index
+ */
+ public function offsetExists($index): bool
+ {
+ return is_int($index) && $index >= 0 && $index < count($this->list);
+ }
+
+
+ /**
+ * Removes the element at the specified position in this list.
+ * @param int $index
+ * @throws Nette\OutOfRangeException
+ */
+ public function offsetUnset($index): void
+ {
+ if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
+ throw new Nette\OutOfRangeException('Offset invalid or out of range');
+ }
+
+ array_splice($this->list, $index, 1);
+ }
+
+
+ /**
+ * Prepends a item.
+ * @param T $value
+ */
+ public function prepend(mixed $value): void
+ {
+ $first = array_slice($this->list, 0, 1);
+ $this->offsetSet(0, $value);
+ array_splice($this->list, 1, 0, $first);
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php
new file mode 100644
index 0000000..00a4a8c
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php
@@ -0,0 +1,553 @@
+ $array
+ * @param array-key|array-key[] $key
+ * @param ?T $default
+ * @return ?T
+ * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
+ */
+ public static function get(array $array, string|int|array $key, mixed $default = null): mixed
+ {
+ foreach (is_array($key) ? $key : [$key] as $k) {
+ if (is_array($array) && array_key_exists($k, $array)) {
+ $array = $array[$k];
+ } else {
+ if (func_num_args() < 3) {
+ throw new Nette\InvalidArgumentException("Missing item '$k'.");
+ }
+
+ return $default;
+ }
+ }
+
+ return $array;
+ }
+
+
+ /**
+ * Returns reference to array item. If the index does not exist, new one is created with value null.
+ * @template T
+ * @param array $array
+ * @param array-key|array-key[] $key
+ * @return ?T
+ * @throws Nette\InvalidArgumentException if traversed item is not an array
+ */
+ public static function &getRef(array &$array, string|int|array $key): mixed
+ {
+ foreach (is_array($key) ? $key : [$key] as $k) {
+ if (is_array($array) || $array === null) {
+ $array = &$array[$k];
+ } else {
+ throw new Nette\InvalidArgumentException('Traversed item is not an array.');
+ }
+ }
+
+ return $array;
+ }
+
+
+ /**
+ * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
+ * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
+ * the value from the first array in the case of a key collision.
+ * @template T1
+ * @template T2
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ public static function mergeTree(array $array1, array $array2): array
+ {
+ $res = $array1 + $array2;
+ foreach (array_intersect_key($array1, $array2) as $k => $v) {
+ if (is_array($v) && is_array($array2[$k])) {
+ $res[$k] = self::mergeTree($v, $array2[$k]);
+ }
+ }
+
+ return $res;
+ }
+
+
+ /**
+ * Returns zero-indexed position of given array key. Returns null if key is not found.
+ */
+ public static function getKeyOffset(array $array, string|int $key): ?int
+ {
+ return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), strict: true));
+ }
+
+
+ /**
+ * @deprecated use getKeyOffset()
+ */
+ public static function searchKey(array $array, $key): ?int
+ {
+ return self::getKeyOffset($array, $key);
+ }
+
+
+ /**
+ * Tests an array for the presence of value.
+ */
+ public static function contains(array $array, mixed $value): bool
+ {
+ return in_array($value, $array, true);
+ }
+
+
+ /**
+ * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
+ * @template K of int|string
+ * @template V
+ * @param array $array
+ * @param ?callable(V, K, array): bool $predicate
+ * @return ?V
+ */
+ public static function first(array $array, ?callable $predicate = null, ?callable $else = null): mixed
+ {
+ $key = self::firstKey($array, $predicate);
+ return $key === null
+ ? ($else ? $else() : null)
+ : $array[$key];
+ }
+
+
+ /**
+ * Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
+ * @template K of int|string
+ * @template V
+ * @param array $array
+ * @param ?callable(V, K, array): bool $predicate
+ * @return ?V
+ */
+ public static function last(array $array, ?callable $predicate = null, ?callable $else = null): mixed
+ {
+ $key = self::lastKey($array, $predicate);
+ return $key === null
+ ? ($else ? $else() : null)
+ : $array[$key];
+ }
+
+
+ /**
+ * Returns the key of first item (matching the specified predicate if given) or null if there is no such item.
+ * @template K of int|string
+ * @template V
+ * @param array $array
+ * @param ?callable(V, K, array): bool $predicate
+ * @return ?K
+ */
+ public static function firstKey(array $array, ?callable $predicate = null): int|string|null
+ {
+ if (!$predicate) {
+ return array_key_first($array);
+ }
+ foreach ($array as $k => $v) {
+ if ($predicate($v, $k, $array)) {
+ return $k;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns the key of last item (matching the specified predicate if given) or null if there is no such item.
+ * @template K of int|string
+ * @template V
+ * @param array $array
+ * @param ?callable(V, K, array): bool $predicate
+ * @return ?K
+ */
+ public static function lastKey(array $array, ?callable $predicate = null): int|string|null
+ {
+ return $predicate
+ ? self::firstKey(array_reverse($array, preserve_keys: true), $predicate)
+ : array_key_last($array);
+ }
+
+
+ /**
+ * Inserts the contents of the $inserted array into the $array immediately after the $key.
+ * If $key is null (or does not exist), it is inserted at the beginning.
+ */
+ public static function insertBefore(array &$array, string|int|null $key, array $inserted): void
+ {
+ $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
+ $array = array_slice($array, 0, $offset, preserve_keys: true)
+ + $inserted
+ + array_slice($array, $offset, count($array), preserve_keys: true);
+ }
+
+
+ /**
+ * Inserts the contents of the $inserted array into the $array before the $key.
+ * If $key is null (or does not exist), it is inserted at the end.
+ */
+ public static function insertAfter(array &$array, string|int|null $key, array $inserted): void
+ {
+ if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
+ $offset = count($array) - 1;
+ }
+
+ $array = array_slice($array, 0, $offset + 1, preserve_keys: true)
+ + $inserted
+ + array_slice($array, $offset + 1, count($array), preserve_keys: true);
+ }
+
+
+ /**
+ * Renames key in array.
+ */
+ public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool
+ {
+ $offset = self::getKeyOffset($array, $oldKey);
+ if ($offset === null) {
+ return false;
+ }
+
+ $val = &$array[$oldKey];
+ $keys = array_keys($array);
+ $keys[$offset] = $newKey;
+ $array = array_combine($keys, $array);
+ $array[$newKey] = &$val;
+ return true;
+ }
+
+
+ /**
+ * Returns only those array items, which matches a regular expression $pattern.
+ * @param string[] $array
+ * @return string[]
+ */
+ public static function grep(
+ array $array,
+ #[Language('RegExp')]
+ string $pattern,
+ bool|int $invert = false,
+ ): array
+ {
+ $flags = $invert ? PREG_GREP_INVERT : 0;
+ return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
+ }
+
+
+ /**
+ * Transforms multidimensional array to flat array.
+ */
+ public static function flatten(array $array, bool $preserveKeys = false): array
+ {
+ $res = [];
+ $cb = $preserveKeys
+ ? function ($v, $k) use (&$res): void { $res[$k] = $v; }
+ : function ($v) use (&$res): void { $res[] = $v; };
+ array_walk_recursive($array, $cb);
+ return $res;
+ }
+
+
+ /**
+ * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
+ * @return ($value is list ? true : false)
+ */
+ public static function isList(mixed $value): bool
+ {
+ return is_array($value) && (PHP_VERSION_ID < 80100
+ ? !$value || array_keys($value) === range(0, count($value) - 1)
+ : array_is_list($value)
+ );
+ }
+
+
+ /**
+ * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
+ * @param string|string[] $path
+ */
+ public static function associate(array $array, $path): array|\stdClass
+ {
+ $parts = is_array($path)
+ ? $path
+ : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+ if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
+ throw new Nette\InvalidArgumentException("Invalid path '$path'.");
+ }
+
+ $res = $parts[0] === '->' ? new \stdClass : [];
+
+ foreach ($array as $rowOrig) {
+ $row = (array) $rowOrig;
+ $x = &$res;
+
+ for ($i = 0; $i < count($parts); $i++) {
+ $part = $parts[$i];
+ if ($part === '[]') {
+ $x = &$x[];
+
+ } elseif ($part === '=') {
+ if (isset($parts[++$i])) {
+ $x = $row[$parts[$i]];
+ $row = null;
+ }
+ } elseif ($part === '->') {
+ if (isset($parts[++$i])) {
+ if ($x === null) {
+ $x = new \stdClass;
+ }
+
+ $x = &$x->{$row[$parts[$i]]};
+ } else {
+ $row = is_object($rowOrig) ? $rowOrig : (object) $row;
+ }
+ } elseif ($part !== '|') {
+ $x = &$x[(string) $row[$part]];
+ }
+ }
+
+ if ($x === null) {
+ $x = $row;
+ }
+ }
+
+ return $res;
+ }
+
+
+ /**
+ * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
+ */
+ public static function normalize(array $array, mixed $filling = null): array
+ {
+ $res = [];
+ foreach ($array as $k => $v) {
+ $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
+ }
+
+ return $res;
+ }
+
+
+ /**
+ * Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
+ * or returns $default, if provided.
+ * @template T
+ * @param array $array
+ * @param ?T $default
+ * @return ?T
+ * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
+ */
+ public static function pick(array &$array, string|int $key, mixed $default = null): mixed
+ {
+ if (array_key_exists($key, $array)) {
+ $value = $array[$key];
+ unset($array[$key]);
+ return $value;
+
+ } elseif (func_num_args() < 3) {
+ throw new Nette\InvalidArgumentException("Missing item '$key'.");
+
+ } else {
+ return $default;
+ }
+ }
+
+
+ /**
+ * Tests whether at least one element in the array passes the test implemented by the provided function.
+ * @template K of int|string
+ * @template V
+ * @param array $array
+ * @param callable(V, K, array): bool $predicate
+ */
+ public static function some(iterable $array, callable $predicate): bool
+ {
+ foreach ($array as $k => $v) {
+ if ($predicate($v, $k, $array)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Tests whether all elements in the array pass the test implemented by the provided function.
+ * @template K of int|string
+ * @template V
+ * @param array $array
+ * @param callable(V, K, array): bool $predicate
+ */
+ public static function every(iterable $array, callable $predicate): bool
+ {
+ foreach ($array as $k => $v) {
+ if (!$predicate($v, $k, $array)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Returns a new array containing all key-value pairs matching the given $predicate.
+ * @template K of int|string
+ * @template V
+ * @param array $array
+ * @param callable(V, K, array): bool $predicate
+ * @return array
+ */
+ public static function filter(array $array, callable $predicate): array
+ {
+ $res = [];
+ foreach ($array as $k => $v) {
+ if ($predicate($v, $k, $array)) {
+ $res[$k] = $v;
+ }
+ }
+ return $res;
+ }
+
+
+ /**
+ * Returns an array containing the original keys and results of applying the given transform function to each element.
+ * @template K of int|string
+ * @template V
+ * @template R
+ * @param array $array
+ * @param callable(V, K, array): R $transformer
+ * @return array
+ */
+ public static function map(iterable $array, callable $transformer): array
+ {
+ $res = [];
+ foreach ($array as $k => $v) {
+ $res[$k] = $transformer($v, $k, $array);
+ }
+
+ return $res;
+ }
+
+
+ /**
+ * Returns an array containing new keys and values generated by applying the given transform function to each element.
+ * If the function returns null, the element is skipped.
+ * @template K of int|string
+ * @template V
+ * @template ResK of int|string
+ * @template ResV
+ * @param array $array
+ * @param callable(V, K, array): ?array{ResK, ResV} $transformer
+ * @return array
+ */
+ public static function mapWithKeys(array $array, callable $transformer): array
+ {
+ $res = [];
+ foreach ($array as $k => $v) {
+ $pair = $transformer($v, $k, $array);
+ if ($pair) {
+ $res[$pair[0]] = $pair[1];
+ }
+ }
+
+ return $res;
+ }
+
+
+ /**
+ * Invokes all callbacks and returns array of results.
+ * @param callable[] $callbacks
+ */
+ public static function invoke(iterable $callbacks, ...$args): array
+ {
+ $res = [];
+ foreach ($callbacks as $k => $cb) {
+ $res[$k] = $cb(...$args);
+ }
+
+ return $res;
+ }
+
+
+ /**
+ * Invokes method on every object in an array and returns array of results.
+ * @param object[] $objects
+ */
+ public static function invokeMethod(iterable $objects, string $method, ...$args): array
+ {
+ $res = [];
+ foreach ($objects as $k => $obj) {
+ $res[$k] = $obj->$method(...$args);
+ }
+
+ return $res;
+ }
+
+
+ /**
+ * Copies the elements of the $array array to the $object object and then returns it.
+ * @template T of object
+ * @param T $object
+ * @return T
+ */
+ public static function toObject(iterable $array, object $object): object
+ {
+ foreach ($array as $k => $v) {
+ $object->$k = $v;
+ }
+
+ return $object;
+ }
+
+
+ /**
+ * Converts value to array key.
+ */
+ public static function toKey(mixed $value): int|string
+ {
+ return key([$value => null]);
+ }
+
+
+ /**
+ * Returns copy of the $array where every item is converted to string
+ * and prefixed by $prefix and suffixed by $suffix.
+ * @param string[] $array
+ * @return string[]
+ */
+ public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
+ {
+ $res = [];
+ foreach ($array as $k => $v) {
+ $res[$k] = $prefix . $v . $suffix;
+ }
+
+ return $res;
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php
new file mode 100644
index 0000000..1777428
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php
@@ -0,0 +1,137 @@
+getClosureScopeClass()?->name;
+ if (str_ends_with($r->name, '}')) {
+ return $closure;
+
+ } elseif (($obj = $r->getClosureThis()) && $obj::class === $class) {
+ return [$obj, $r->name];
+
+ } elseif ($class) {
+ return [$class, $r->name];
+
+ } else {
+ return $r->name;
+ }
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php
new file mode 100644
index 0000000..6ad6520
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php
@@ -0,0 +1,140 @@
+format('Y-m-d H:i:s.u'), $time->getTimezone());
+
+ } elseif (is_numeric($time)) {
+ if ($time <= self::YEAR) {
+ $time += time();
+ }
+
+ return (new static)->setTimestamp((int) $time);
+
+ } else { // textual or null
+ return new static((string) $time);
+ }
+ }
+
+
+ /**
+ * Creates DateTime object.
+ * @throws Nette\InvalidArgumentException if the date and time are not valid.
+ */
+ public static function fromParts(
+ int $year,
+ int $month,
+ int $day,
+ int $hour = 0,
+ int $minute = 0,
+ float $second = 0.0,
+ ): static
+ {
+ $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second);
+ if (
+ !checkdate($month, $day, $year)
+ || $hour < 0
+ || $hour > 23
+ || $minute < 0
+ || $minute > 59
+ || $second < 0
+ || $second >= 60
+ ) {
+ throw new Nette\InvalidArgumentException("Invalid date '$s'");
+ }
+
+ return new static($s);
+ }
+
+
+ /**
+ * Returns new DateTime object formatted according to the specified format.
+ */
+ public static function createFromFormat(
+ string $format,
+ string $time,
+ string|\DateTimeZone|null $timezone = null,
+ ): static|false
+ {
+ if ($timezone === null) {
+ $timezone = new \DateTimeZone(date_default_timezone_get());
+
+ } elseif (is_string($timezone)) {
+ $timezone = new \DateTimeZone($timezone);
+ }
+
+ $date = parent::createFromFormat($format, $time, $timezone);
+ return $date ? static::from($date) : false;
+ }
+
+
+ /**
+ * Returns JSON representation in ISO 8601 (used by JavaScript).
+ */
+ public function jsonSerialize(): string
+ {
+ return $this->format('c');
+ }
+
+
+ /**
+ * Returns the date and time in the format 'Y-m-d H:i:s'.
+ */
+ public function __toString(): string
+ {
+ return $this->format('Y-m-d H:i:s');
+ }
+
+
+ /**
+ * You'd better use: (clone $dt)->modify(...)
+ */
+ public function modifyClone(string $modify = ''): static
+ {
+ $dolly = clone $this;
+ return $modify ? $dolly->modify($modify) : $dolly;
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php
new file mode 100644
index 0000000..fb92d11
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php
@@ -0,0 +1,69 @@
+setInfoClass(static::class);
+ $this->relativePath = $relativePath;
+ }
+
+
+ /**
+ * Returns the relative directory path.
+ */
+ public function getRelativePath(): string
+ {
+ return $this->relativePath;
+ }
+
+
+ /**
+ * Returns the relative path including file name.
+ */
+ public function getRelativePathname(): string
+ {
+ return ($this->relativePath === '' ? '' : $this->relativePath . DIRECTORY_SEPARATOR)
+ . $this->getBasename();
+ }
+
+
+ /**
+ * Returns the contents of the file.
+ * @throws Nette\IOException
+ */
+ public function read(): string
+ {
+ return FileSystem::read($this->getPathname());
+ }
+
+
+ /**
+ * Writes the contents to the file.
+ * @throws Nette\IOException
+ */
+ public function write(string $content): void
+ {
+ FileSystem::write($this->getPathname(), $content);
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php
new file mode 100644
index 0000000..ab9a7e8
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php
@@ -0,0 +1,326 @@
+getPathname());
+ }
+
+ foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
+ if ($item->isDir()) {
+ static::createDir($target . '/' . $iterator->getSubPathName());
+ } else {
+ static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
+ }
+ }
+ } else {
+ static::createDir(dirname($target));
+ if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to copy file '%s' to '%s'. %s",
+ self::normalizePath($origin),
+ self::normalizePath($target),
+ Helpers::getLastError(),
+ ));
+ }
+ }
+ }
+
+
+ /**
+ * Opens file and returns resource.
+ * @return resource
+ * @throws Nette\IOException on error occurred
+ */
+ public static function open(string $path, string $mode)
+ {
+ $f = @fopen($path, $mode); // @ is escalated to exception
+ if (!$f) {
+ throw new Nette\IOException(sprintf(
+ "Unable to open file '%s'. %s",
+ self::normalizePath($path),
+ Helpers::getLastError(),
+ ));
+ }
+ return $f;
+ }
+
+
+ /**
+ * Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first.
+ * @throws Nette\IOException on error occurred
+ */
+ public static function delete(string $path): void
+ {
+ if (is_file($path) || is_link($path)) {
+ $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink';
+ if (!@$func($path)) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to delete '%s'. %s",
+ self::normalizePath($path),
+ Helpers::getLastError(),
+ ));
+ }
+ } elseif (is_dir($path)) {
+ foreach (new \FilesystemIterator($path) as $item) {
+ static::delete($item->getPathname());
+ }
+
+ if (!@rmdir($path)) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to delete directory '%s'. %s",
+ self::normalizePath($path),
+ Helpers::getLastError(),
+ ));
+ }
+ }
+ }
+
+
+ /**
+ * Renames or moves a file or a directory. Overwrites existing files and directories by default.
+ * @throws Nette\IOException on error occurred
+ * @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
+ */
+ public static function rename(string $origin, string $target, bool $overwrite = true): void
+ {
+ if (!$overwrite && file_exists($target)) {
+ throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
+
+ } elseif (!file_exists($origin)) {
+ throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
+
+ } else {
+ static::createDir(dirname($target));
+ if (realpath($origin) !== realpath($target)) {
+ static::delete($target);
+ }
+
+ if (!@rename($origin, $target)) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to rename file or directory '%s' to '%s'. %s",
+ self::normalizePath($origin),
+ self::normalizePath($target),
+ Helpers::getLastError(),
+ ));
+ }
+ }
+ }
+
+
+ /**
+ * Reads the content of a file.
+ * @throws Nette\IOException on error occurred
+ */
+ public static function read(string $file): string
+ {
+ $content = @file_get_contents($file); // @ is escalated to exception
+ if ($content === false) {
+ throw new Nette\IOException(sprintf(
+ "Unable to read file '%s'. %s",
+ self::normalizePath($file),
+ Helpers::getLastError(),
+ ));
+ }
+
+ return $content;
+ }
+
+
+ /**
+ * Reads the file content line by line. Because it reads continuously as we iterate over the lines,
+ * it is possible to read files larger than the available memory.
+ * @return \Generator
+ * @throws Nette\IOException on error occurred
+ */
+ public static function readLines(string $file, bool $stripNewLines = true): \Generator
+ {
+ return (function ($f) use ($file, $stripNewLines) {
+ $counter = 0;
+ do {
+ $line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf(
+ "Unable to read file '%s'. %s",
+ self::normalizePath($file),
+ $error,
+ )));
+ if ($line === false) {
+ fclose($f);
+ break;
+ }
+ if ($stripNewLines) {
+ $line = rtrim($line, "\r\n");
+ }
+
+ yield $counter++ => $line;
+
+ } while (true);
+ })(static::open($file, 'r'));
+ }
+
+
+ /**
+ * Writes the string to a file.
+ * @throws Nette\IOException on error occurred
+ */
+ public static function write(string $file, string $content, ?int $mode = 0666): void
+ {
+ static::createDir(dirname($file));
+ if (@file_put_contents($file, $content) === false) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to write file '%s'. %s",
+ self::normalizePath($file),
+ Helpers::getLastError(),
+ ));
+ }
+
+ if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to chmod file '%s' to mode %s. %s",
+ self::normalizePath($file),
+ decoct($mode),
+ Helpers::getLastError(),
+ ));
+ }
+ }
+
+
+ /**
+ * Sets file permissions to `$fileMode` or directory permissions to `$dirMode`.
+ * Recursively traverses and sets permissions on the entire contents of the directory as well.
+ * @throws Nette\IOException on error occurred
+ */
+ public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
+ {
+ if (is_file($path)) {
+ if (!@chmod($path, $fileMode)) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to chmod file '%s' to mode %s. %s",
+ self::normalizePath($path),
+ decoct($fileMode),
+ Helpers::getLastError(),
+ ));
+ }
+ } elseif (is_dir($path)) {
+ foreach (new \FilesystemIterator($path) as $item) {
+ static::makeWritable($item->getPathname(), $dirMode, $fileMode);
+ }
+
+ if (!@chmod($path, $dirMode)) { // @ is escalated to exception
+ throw new Nette\IOException(sprintf(
+ "Unable to chmod directory '%s' to mode %s. %s",
+ self::normalizePath($path),
+ decoct($dirMode),
+ Helpers::getLastError(),
+ ));
+ }
+ } else {
+ throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path)));
+ }
+ }
+
+
+ /**
+ * Determines if the path is absolute.
+ */
+ public static function isAbsolute(string $path): bool
+ {
+ return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
+ }
+
+
+ /**
+ * Normalizes `..` and `.` and directory separators in path.
+ */
+ public static function normalizePath(string $path): string
+ {
+ $parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path);
+ $res = [];
+ foreach ($parts as $part) {
+ if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
+ array_pop($res);
+ } elseif ($part !== '.') {
+ $res[] = $part;
+ }
+ }
+
+ return $res === ['']
+ ? DIRECTORY_SEPARATOR
+ : implode(DIRECTORY_SEPARATOR, $res);
+ }
+
+
+ /**
+ * Joins all segments of the path and normalizes the result.
+ */
+ public static function joinPaths(string ...$paths): string
+ {
+ return self::normalizePath(implode('/', $paths));
+ }
+
+
+ /**
+ * Converts backslashes to slashes.
+ */
+ public static function unixSlashes(string $path): string
+ {
+ return strtr($path, '\\', '/');
+ }
+
+
+ /**
+ * Converts slashes to platform-specific directory separators.
+ */
+ public static function platformSlashes(string $path): string
+ {
+ return DIRECTORY_SEPARATOR === '/'
+ ? strtr($path, '\\', '/')
+ : str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol://
+ }
+}
diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php
new file mode 100644
index 0000000..a496528
--- /dev/null
+++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php
@@ -0,0 +1,510 @@
+size('> 10kB')
+ * ->from('.')
+ * ->exclude('temp');
+ *
+ * @implements \IteratorAggregate
+ */
+class Finder implements \IteratorAggregate
+{
+ use Nette\SmartObject;
+
+ /** @var array */
+ private array $find = [];
+
+ /** @var string[] */
+ private array $in = [];
+
+ /** @var \Closure[] */
+ private array $filters = [];
+
+ /** @var \Closure[] */
+ private array $descentFilters = [];
+
+ /** @var array */
+ private array $appends = [];
+ private bool $childFirst = false;
+
+ /** @var ?callable */
+ private $sort;
+ private int $maxDepth = -1;
+ private bool $ignoreUnreadableDirs = true;
+
+
+ /**
+ * Begins search for files and directories matching mask.
+ */
+ public static function find(string|array $masks = ['*']): static
+ {
+ $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
+ return (new static)->addMask($masks, 'dir')->addMask($masks, 'file');
+ }
+
+
+ /**
+ * Begins search for files matching mask.
+ */
+ public static function findFiles(string|array $masks = ['*']): static
+ {
+ $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
+ return (new static)->addMask($masks, 'file');
+ }
+
+
+ /**
+ * Begins search for directories matching mask.
+ */
+ public static function findDirectories(string|array $masks = ['*']): static
+ {
+ $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
+ return (new static)->addMask($masks, 'dir');
+ }
+
+
+ /**
+ * Finds files matching the specified masks.
+ */
+ public function files(string|array $masks = ['*']): static
+ {
+ return $this->addMask((array) $masks, 'file');
+ }
+
+
+ /**
+ * Finds directories matching the specified masks.
+ */
+ public function directories(string|array $masks = ['*']): static
+ {
+ return $this->addMask((array) $masks, 'dir');
+ }
+
+
+ private function addMask(array $masks, string $mode): static
+ {
+ foreach ($masks as $mask) {
+ $mask = FileSystem::unixSlashes($mask);
+ if ($mode === 'dir') {
+ $mask = rtrim($mask, '/');
+ }
+ if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) {
+ throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
+ }
+ if (str_starts_with($mask, '**/')) {
+ $mask = substr($mask, 3);
+ }
+ $this->find[] = [$mask, $mode];
+ }
+ return $this;
+ }
+
+
+ /**
+ * Searches in the given directories. Wildcards are allowed.
+ */
+ public function in(string|array $paths): static
+ {
+ $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic
+ $this->addLocation($paths, '');
+ return $this;
+ }
+
+
+ /**
+ * Searches recursively from the given directories. Wildcards are allowed.
+ */
+ public function from(string|array $paths): static
+ {
+ $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic
+ $this->addLocation($paths, '/**');
+ return $this;
+ }
+
+
+ private function addLocation(array $paths, string $ext): void
+ {
+ foreach ($paths as $path) {
+ if ($path === '') {
+ throw new Nette\InvalidArgumentException("Invalid directory '$path'");
+ }
+ $path = rtrim(FileSystem::unixSlashes($path), '/');
+ $this->in[] = $path . $ext;
+ }
+ }
+
+
+ /**
+ * Lists directory's contents before the directory itself. By default, this is disabled.
+ */
+ public function childFirst(bool $state = true): static
+ {
+ $this->childFirst = $state;
+ return $this;
+ }
+
+
+ /**
+ * Ignores unreadable directories. By default, this is enabled.
+ */
+ public function ignoreUnreadableDirs(bool $state = true): static
+ {
+ $this->ignoreUnreadableDirs = $state;
+ return $this;
+ }
+
+
+ /**
+ * Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory.
+ * @param callable(FileInfo, FileInfo): int $callback
+ */
+ public function sortBy(callable $callback): static
+ {
+ $this->sort = $callback;
+ return $this;
+ }
+
+
+ /**
+ * Sorts files in each directory naturally by name.
+ */
+ public function sortByName(): static
+ {
+ $this->sort = fn(FileInfo $a, FileInfo $b): int => strnatcmp($a->getBasename(), $b->getBasename());
+ return $this;
+ }
+
+
+ /**
+ * Adds the specified paths or appends a new finder that returns.
+ */
+ public function append(string|array|null $paths = null): static
+ {
+ if ($paths === null) {
+ return $this->appends[] = new static;
+ }
+
+ $this->appends = array_merge($this->appends, (array) $paths);
+ return $this;
+ }
+
+
+ /********************* filtering ****************d*g**/
+
+
+ /**
+ * Skips entries that matches the given masks relative to the ones defined with the in() or from() methods.
+ */
+ public function exclude(string|array $masks): static
+ {
+ $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
+ foreach ($masks as $mask) {
+ $mask = FileSystem::unixSlashes($mask);
+ if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) {
+ throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
+ }
+ $end = $m[3];
+ $re = $this->buildPattern($m[2]);
+ $filter = fn(FileInfo $file): bool => ($end && !$file->isDir())
+ || !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname()));
+
+ $this->descentFilter($filter);
+ if ($end !== '/*') {
+ $this->filter($filter);
+ }
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Yields only entries which satisfy the given filter.
+ * @param callable(FileInfo): bool $callback
+ */
+ public function filter(callable $callback): static
+ {
+ $this->filters[] = \Closure::fromCallable($callback);
+ return $this;
+ }
+
+
+ /**
+ * It descends only to directories that match the specified filter.
+ * @param callable(FileInfo): bool $callback
+ */
+ public function descentFilter(callable $callback): static
+ {
+ $this->descentFilters[] = \Closure::fromCallable($callback);
+ return $this;
+ }
+
+
+ /**
+ * Sets the maximum depth of entries.
+ */
+ public function limitDepth(?int $depth): static
+ {
+ $this->maxDepth = $depth ?? -1;
+ return $this;
+ }
+
+
+ /**
+ * Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB
+ */
+ public function size(string $operator, ?int $size = null): static
+ {
+ if (func_num_args() === 1) { // in $operator is predicate
+ if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) {
+ throw new Nette\InvalidArgumentException('Invalid size predicate format.');
+ }
+
+ [, $operator, $size, $unit] = $matches;
+ $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
+ $size *= $units[strtolower($unit)];
+ $operator = $operator ?: '=';
+ }
+
+ return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size));
+ }
+
+
+ /**
+ * Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23
+ */
+ public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static
+ {
+ if (func_num_args() === 1) { // in $operator is predicate
+ if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) {
+ throw new Nette\InvalidArgumentException('Invalid date predicate format.');
+ }
+
+ [, $operator, $date] = $matches;
+ $operator = $operator ?: '=';
+ }
+
+ $date = DateTime::from($date)->format('U');
+ return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date));
+ }
+
+
+ /********************* iterator generator ****************d*g**/
+
+
+ /**
+ * Returns an array with all found files and directories.
+ * @return list
+ */
+ public function collect(): array
+ {
+ return iterator_to_array($this->getIterator(), preserve_keys: false);
+ }
+
+
+ /** @return \Generator */
+ public function getIterator(): \Generator
+ {
+ $plan = $this->buildPlan();
+ foreach ($plan as $dir => $searches) {
+ yield from $this->traverseDir($dir, $searches);
+ }
+
+ foreach ($this->appends as $item) {
+ if ($item instanceof self) {
+ yield from $item->getIterator();
+ } else {
+ $item = FileSystem::platformSlashes($item);
+ yield $item => new FileInfo($item);
+ }
+ }
+ }
+
+
+ /**
+ * @param array