diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..bc541e8
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+end_of_line = lf
+indent_style = tab
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{md,neon,yml}]
+indent_size = 2
+indent_style = space
+
+[phpstan-baseline.neon]
+indent_style = tab
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..acbcff9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,9 @@
+.editorconfig export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+.github export-ignore
+phpstan-baseline.neon export-ignore
+phpstan.neon export-ignore
+tests/ export-ignore
+
+*.php* diff=php linguist-language=PHP
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
new file mode 100644
index 0000000..5341bc9
--- /dev/null
+++ b/.github/workflows/checks.yml
@@ -0,0 +1,75 @@
+name: checks
+
+on:
+ - push
+
+jobs:
+ static_analysis:
+ name: Static analysis
+
+ runs-on: ubuntu-latest
+
+ env:
+ COMPOSER_NO_INTERACTION: "1"
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.4'
+ coverage: none
+
+ - run: composer install --ansi --no-progress --prefer-dist
+
+ - name: Run parallel-lint
+ run: composer run lint
+
+ - name: Run PHPStan
+ run: composer run phpstan
+
+ tests:
+ name: PHP ${{ matrix.php }} tests on ${{ matrix.os }} with ${{ matrix.deps }} deps
+
+ needs:
+ - static_analysis
+
+ strategy:
+ matrix:
+ deps:
+ - stable
+ - lowest
+ os:
+ - macos-latest
+ - ubuntu-latest
+ - windows-latest
+ php:
+ - '8.4'
+
+ fail-fast: false
+
+ runs-on: ${{ matrix.os }}
+
+ env:
+ COMPOSER_NO_INTERACTION: "1"
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - run: composer install --ansi --no-progress --prefer-dist
+
+ - run: composer update --ansi --no-progress --prefer-lowest
+ if: matrix.deps == 'lowest'
+
+ - name: Run tests
+ run: composer run test
+
+ - if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ path: tests/output
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13e687e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+.phpunit.cache
+.phpunit.result.cache
+tests-temp
+vendor
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8c8493d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2025, Vojtěch Dobeš
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..304fbc2
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,53 @@
+{
+ "authors": [
+ {
+ "name": "Vojtěch Dobeš",
+ "homepage": "https://vojtechdobes.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Vojtechdobes\\PHPStan\\": "src/",
+ "Vojtechdobes\\TestsShared\\": "tests-shared/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Vojtechdobes\\Tests\\": "tests/"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "keywords": [
+ "ci",
+ "graphql",
+ "phpstan",
+ "phpstan-rules",
+ "static-analysis",
+ "static-code-analysis"
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "name": "vojtech-dobes/phpstan-php-graphql-server",
+ "require": {
+ "php": "~8.4"
+ },
+ "require-dev": {
+ "nette/di": "^3.2",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpstan/phpstan": "^2.1.12",
+ "phpstan/phpstan-strict-rules": "^2.0.4",
+ "phpunit/phpunit": "^12.1",
+ "spaze/phpstan-disallowed-calls": "^4.5.0",
+ "tracy/tracy": "^2.10.9",
+ "vojtech-dobes/php-grammar-processing": "dev-master@dev",
+ "vojtech-dobes/php-graphql-server": "dev-master@dev"
+ },
+ "scripts": {
+ "lint": "parallel-lint src tests",
+ "phpstan": "phpstan analyse --memory-limit 256M",
+ "test": "composer dump-autoload && phpunit tests"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..1cd3a5c
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,2559 @@
+{
+ "_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": "ae327a4f793abad51c45e066374c7f89",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "guzzlehttp/promises",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
+ "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/promises/issues",
+ "source": "https://github.com/guzzle/promises/tree/2.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-03-27T13:27:01+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.13.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
+ "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-04-29T12:36:36+00:00"
+ },
+ {
+ "name": "nette/di",
+ "version": "v3.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/di.git",
+ "reference": "57f923a7af32435b6e4921c0adbc70c619625a17"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/di/zipball/57f923a7af32435b6e4921c0adbc70c619625a17",
+ "reference": "57f923a7af32435b6e4921c0adbc70c619625a17",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-tokenizer": "*",
+ "nette/neon": "^3.3 || ^4.0",
+ "nette/php-generator": "^4.1.6",
+ "nette/robot-loader": "^4.0",
+ "nette/schema": "^1.2.5",
+ "nette/utils": "^4.0",
+ "php": "8.1 - 8.4"
+ },
+ "require-dev": {
+ "nette/tester": "^2.5.2",
+ "phpstan/phpstan": "^1.0",
+ "tracy/tracy": "^2.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-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 Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP features.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "compiled",
+ "di",
+ "dic",
+ "factory",
+ "ioc",
+ "nette",
+ "static"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/di/issues",
+ "source": "https://github.com/nette/di/tree/v3.2.4"
+ },
+ "time": "2025-01-10T04:57:37+00:00"
+ },
+ {
+ "name": "nette/neon",
+ "version": "v3.4.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/neon.git",
+ "reference": "3411aa86b104e2d5b7e760da4600865ead963c3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/neon/zipball/3411aa86b104e2d5b7e760da4600865ead963c3c",
+ "reference": "3411aa86b104e2d5b7e760da4600865ead963c3c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "8.0 - 8.4"
+ },
+ "require-dev": {
+ "nette/tester": "^2.4",
+ "phpstan/phpstan": "^1.0",
+ "tracy/tracy": "^2.7"
+ },
+ "bin": [
+ "bin/neon-lint"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-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 NEON: encodes and decodes NEON file format.",
+ "homepage": "https://ne-on.org",
+ "keywords": [
+ "export",
+ "import",
+ "neon",
+ "nette",
+ "yaml"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/neon/issues",
+ "source": "https://github.com/nette/neon/tree/v3.4.4"
+ },
+ "time": "2024-10-04T22:00:08+00:00"
+ },
+ {
+ "name": "nette/php-generator",
+ "version": "v4.1.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/php-generator.git",
+ "reference": "42806049a7774a2bd316c958f5dcf01c6b5c56fa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/php-generator/zipball/42806049a7774a2bd316c958f5dcf01c6b5c56fa",
+ "reference": "42806049a7774a2bd316c958f5dcf01c6b5c56fa",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^3.2.9 || ^4.0",
+ "php": "8.0 - 8.4"
+ },
+ "require-dev": {
+ "jetbrains/phpstorm-attributes": "dev-master",
+ "nette/tester": "^2.4",
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "phpstan/phpstan": "^1.0",
+ "tracy/tracy": "^2.8"
+ },
+ "suggest": {
+ "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-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 PHP Generator: generates neat PHP code for you. Supports new PHP 8.4 features.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "code",
+ "nette",
+ "php",
+ "scaffolding"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/php-generator/issues",
+ "source": "https://github.com/nette/php-generator/tree/v4.1.8"
+ },
+ "time": "2025-03-31T00:29:29+00:00"
+ },
+ {
+ "name": "nette/robot-loader",
+ "version": "v4.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/robot-loader.git",
+ "reference": "45d67753fb4865bb718e9a6c9be69cc9470137b7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/robot-loader/zipball/45d67753fb4865bb718e9a6c9be69cc9470137b7",
+ "reference": "45d67753fb4865bb718e9a6c9be69cc9470137b7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "nette/utils": "^4.0",
+ "php": "8.0 - 8.4"
+ },
+ "require-dev": {
+ "nette/tester": "^2.4",
+ "phpstan/phpstan": "^1.0",
+ "tracy/tracy": "^2.9"
+ },
+ "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 RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "autoload",
+ "class",
+ "interface",
+ "nette",
+ "trait"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/robot-loader/issues",
+ "source": "https://github.com/nette/robot-loader/tree/v4.0.3"
+ },
+ "time": "2024-06-18T20:26:39+00:00"
+ },
+ {
+ "name": "nette/schema",
+ "version": "v1.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/schema.git",
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^4.0",
+ "php": "8.1 - 8.4"
+ },
+ "require-dev": {
+ "nette/tester": "^2.5.2",
+ "phpstan/phpstan-nette": "^1.0",
+ "tracy/tracy": "^2.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-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 Schema: validating data structures against a given Schema.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "config",
+ "nette"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/schema/issues",
+ "source": "https://github.com/nette/schema/tree/v1.3.2"
+ },
+ "time": "2024-10-06T23:10:23+00:00"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v4.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "ce708655043c7050eb050df361c5e313cf708309"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
+ "reference": "ce708655043c7050eb050df361c5e313cf708309",
+ "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.6"
+ },
+ "time": "2025-03-30T21:06:30+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
+ "reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
+ },
+ "time": "2025-05-31T08:24:38+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "php-parallel-lint/php-parallel-lint",
+ "version": "v1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
+ "reference": "6db563514f27e19595a19f45a4bf757b6401194e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e",
+ "reference": "6db563514f27e19595a19f45a4bf757b6401194e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": ">=5.3.0"
+ },
+ "replace": {
+ "grogy/php-parallel-lint": "*",
+ "jakub-onderka/php-parallel-lint": "*"
+ },
+ "require-dev": {
+ "nette/tester": "^1.3 || ^2.0",
+ "php-parallel-lint/php-console-highlighter": "0.* || ^1.0",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "suggest": {
+ "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
+ },
+ "bin": [
+ "parallel-lint"
+ ],
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "./src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jakub Onderka",
+ "email": "ahoj@jakubonderka.cz"
+ }
+ ],
+ "description": "This tool checks the syntax of PHP files about 20x faster than serial check.",
+ "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint",
+ "keywords": [
+ "lint",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues",
+ "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0"
+ },
+ "time": "2024-03-27T12:14:49+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.1.17",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053",
+ "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053",
+ "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-05-21T20:55:28+00:00"
+ },
+ {
+ "name": "phpstan/phpstan-strict-rules",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-strict-rules.git",
+ "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
+ "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
+ "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.4"
+ },
+ "time": "2025-03-18T11:42:40+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "12.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "9075a8efc66e11bc55c319062e147bdb06777267"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9075a8efc66e11bc55c319062e147bdb06777267",
+ "reference": "9075a8efc66e11bc55c319062e147bdb06777267",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.4.0",
+ "php": ">=8.3",
+ "phpunit/php-file-iterator": "^6.0",
+ "phpunit/php-text-template": "^5.0",
+ "sebastian/complexity": "^5.0",
+ "sebastian/environment": "^8.0",
+ "sebastian/lines-of-code": "^4.0",
+ "sebastian/version": "^6.0",
+ "theseer/tokenizer": "^1.2.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.1"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "12.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-23T15:49:03+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "961bc913d42fe24a257bfff826a5068079ac7782"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782",
+ "reference": "961bc913d42fe24a257bfff826a5068079ac7782",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:58:37+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406",
+ "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^12.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:58:58+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53",
+ "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:59:16+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
+ "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:59:38+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "12.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "2fdf0056c673c8f0f1eed00030be5f8243c1e6e0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2fdf0056c673c8f0f1eed00030be5f8243c1e6e0",
+ "reference": "2fdf0056c673c8f0f1eed00030be5f8243c1e6e0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.13.1",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.3",
+ "phpunit/php-code-coverage": "^12.2.1",
+ "phpunit/php-file-iterator": "^6.0.0",
+ "phpunit/php-invoker": "^6.0.0",
+ "phpunit/php-text-template": "^5.0.0",
+ "phpunit/php-timer": "^8.0.0",
+ "sebastian/cli-parser": "^4.0.0",
+ "sebastian/comparator": "^7.0.1",
+ "sebastian/diff": "^7.0.0",
+ "sebastian/environment": "^8.0.1",
+ "sebastian/exporter": "^7.0.0",
+ "sebastian/global-state": "^8.0.0",
+ "sebastian/object-enumerator": "^7.0.0",
+ "sebastian/type": "^6.0.2",
+ "sebastian/version": "^6.0.0",
+ "staabm/side-effects-detector": "^1.0.5"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "12.1-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.1.6"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-21T12:36:31+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/6d584c727d9114bcdc14c86711cd1cad51778e7c",
+ "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:53:50+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "7.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "b478f34614f934e0291598d0c08cbaba9644bee5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b478f34614f934e0291598d0c08cbaba9644bee5",
+ "reference": "b478f34614f934e0291598d0c08cbaba9644bee5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.3",
+ "sebastian/diff": "^7.0",
+ "sebastian/exporter": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "suggest": {
+ "ext-bcmath": "For comparing BcMath\\Number objects"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-07T07:00:32+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb",
+ "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:55:25+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "7ab1ea946c012266ca32390913653d844ecd085f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f",
+ "reference": "7ab1ea946c012266ca32390913653d844ecd085f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0",
+ "symfony/process": "^7.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:55:46+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "8.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "d364b9e5d0d3b18a2573351a1786fbf96b7e0792"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d364b9e5d0d3b18a2573351a1786fbf96b7e0792",
+ "reference": "d364b9e5d0d3b18a2573351a1786fbf96b7e0792",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/8.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-21T15:05:44+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "76432aafc58d50691a00d86d0632f1217a47b688"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/76432aafc58d50691a00d86d0632f1217a47b688",
+ "reference": "76432aafc58d50691a00d86d0632f1217a47b688",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.3",
+ "sebastian/recursion-context": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:56:42+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/570a2aeb26d40f057af686d63c4e99b075fb6cbc",
+ "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3",
+ "sebastian/object-reflector": "^5.0",
+ "sebastian/recursion-context": "^7.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:56:59+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f",
+ "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:57:28+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894",
+ "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3",
+ "sebastian/object-reflector": "^5.0",
+ "sebastian/recursion-context": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:57:48+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "4bfa827c969c98be1e527abd576533293c634f6a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a",
+ "reference": "4bfa827c969c98be1e527abd576533293c634f6a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:58:17+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c405ae3a63e01b32eb71577f8ec1604e39858a7c",
+ "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T05:00:01+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/1d7cd6e514384c36d7a390347f57c385d4be6069",
+ "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-18T13:37:31+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c",
+ "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T05:00:38+00:00"
+ },
+ {
+ "name": "spaze/phpstan-disallowed-calls",
+ "version": "v4.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spaze/phpstan-disallowed-calls.git",
+ "reference": "1c5e6996bd75a1460f5e2683fc4294665b37bee2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spaze/phpstan-disallowed-calls/zipball/1c5e6996bd75a1460f5e2683fc4294665b37bee2",
+ "reference": "1c5e6996bd75a1460f5e2683fc4294665b37bee2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^1.12.6 || ^2.0"
+ },
+ "require-dev": {
+ "nette/neon": "^3.3.1",
+ "nikic/php-parser": "^4.13.2 || ^5.0",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/phpstan-deprecation-rules": "^1.2 || ^2.0",
+ "phpunit/phpunit": "^8.5.14 || ^10.1 || ^11.0 || ^12.0",
+ "spaze/coding-standard": "^1.8"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spaze\\PHPStan\\Rules\\Disallowed\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michal Špaček",
+ "email": "mail@michalspacek.cz",
+ "homepage": "https://www.michalspacek.cz"
+ }
+ ],
+ "description": "PHPStan rules to detect disallowed method & function calls, constant, namespace, attribute & superglobal usages, with powerful rules to re-allow a call or a usage in places where it should be allowed.",
+ "keywords": [
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/spaze/phpstan-disallowed-calls/issues",
+ "source": "https://github.com/spaze/phpstan-disallowed-calls/tree/v4.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spaze",
+ "type": "github"
+ }
+ ],
+ "time": "2025-04-10T19:01:43+00:00"
+ },
+ {
+ "name": "staabm/side-effects-detector",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/staabm/side-effects-detector.git",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.6",
+ "phpunit/phpunit": "^9.6.21",
+ "symfony/var-dumper": "^5.4.43",
+ "tomasvotruba/type-coverage": "1.0.0",
+ "tomasvotruba/unused-public": "1.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A static analysis tool to detect side effects in PHP code",
+ "keywords": [
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/staabm/side-effects-detector/issues",
+ "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/staabm",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-20T05:08:20+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:36:25+00:00"
+ },
+ {
+ "name": "tracy/tracy",
+ "version": "v2.10.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/tracy.git",
+ "reference": "32303e02c222eea8571402a8310fc3fe70422c37"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/tracy/zipball/32303e02c222eea8571402a8310fc3fe70422c37",
+ "reference": "32303e02c222eea8571402a8310fc3fe70422c37",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-session": "*",
+ "php": "8.0 - 8.4"
+ },
+ "conflict": {
+ "nette/di": "<3.0"
+ },
+ "require-dev": {
+ "latte/latte": "^2.5 || ^3.0",
+ "nette/di": "^3.0",
+ "nette/http": "^3.0",
+ "nette/mail": "^3.0 || ^4.0",
+ "nette/tester": "^2.2",
+ "nette/utils": "^3.0 || ^4.0",
+ "phpstan/phpstan": "^1.0",
+ "psr/log": "^1.0 || ^2.0 || ^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.10-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Tracy/functions.php"
+ ],
+ "classmap": [
+ "src"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.",
+ "homepage": "https://tracy.nette.org",
+ "keywords": [
+ "Xdebug",
+ "debug",
+ "debugger",
+ "nette",
+ "profiler"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/tracy/issues",
+ "source": "https://github.com/nette/tracy/tree/v2.10.10"
+ },
+ "time": "2025-04-28T14:35:15+00:00"
+ },
+ {
+ "name": "vojtech-dobes/php-grammar-processing",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vojtech-dobes/php-grammar-processing.git",
+ "reference": "f90e1f0331464a781e6ee0271ec78cf5cd32d5f0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vojtech-dobes/php-grammar-processing/zipball/f90e1f0331464a781e6ee0271ec78cf5cd32d5f0",
+ "reference": "f90e1f0331464a781e6ee0271ec78cf5cd32d5f0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "php": "~8.4"
+ },
+ "require-dev": {
+ "nette/tester": "^2.5.4",
+ "phpstan/phpstan": "^2.1.12",
+ "phpstan/phpstan-strict-rules": "^2.0.4",
+ "spaze/phpstan-disallowed-calls": "^4.5.0",
+ "tracy/tracy": "^2.10.9"
+ },
+ "default-branch": true,
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Vojtechdobes\\GrammarProcessing\\": "src/GrammarProcessing"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Vojtěch Dobeš",
+ "homepage": "https://vojtechdobes.com"
+ }
+ ],
+ "description": "Library for tokenization, abstract syntax tree parsing & interpretation",
+ "keywords": [
+ "ast",
+ "context-free",
+ "grammar",
+ "language",
+ "lexer",
+ "parser",
+ "syntax",
+ "token"
+ ],
+ "support": {
+ "issues": "https://github.com/vojtech-dobes/php-grammar-processing/issues",
+ "source": "https://github.com/vojtech-dobes/php-grammar-processing/tree/master"
+ },
+ "time": "2025-04-29T15:17:38+00:00"
+ },
+ {
+ "name": "vojtech-dobes/php-graphql-server",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vojtech-dobes/php-graphql-server.git",
+ "reference": "6002078efa50c9c74e0aeb25069944db2a74146e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vojtech-dobes/php-graphql-server/zipball/6002078efa50c9c74e0aeb25069944db2a74146e",
+ "reference": "6002078efa50c9c74e0aeb25069944db2a74146e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "guzzlehttp/promises": "^2.0.3",
+ "nette/php-generator": "^4.0",
+ "php": "~8.4",
+ "vojtech-dobes/php-grammar-processing": "dev-master"
+ },
+ "require-dev": {
+ "jiripudil/phpstan-sealed-classes": "^1.3.0",
+ "nette/tester": "^2.5.4",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpstan/phpstan": "^2.1.12",
+ "phpstan/phpstan-strict-rules": "^2.0.4",
+ "psr/log": "^3.0",
+ "spaze/phpstan-disallowed-calls": "^4.5.0",
+ "tracy/tracy": "^2.10.9"
+ },
+ "default-branch": true,
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Vojtechdobes\\GraphQL\\": "src/GraphQL"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Vojtěch Dobeš",
+ "homepage": "https://vojtechdobes.com"
+ }
+ ],
+ "keywords": [
+ "api",
+ "graphql"
+ ],
+ "support": {
+ "issues": "https://github.com/vojtech-dobes/php-graphql-server/issues",
+ "source": "https://github.com/vojtech-dobes/php-graphql-server/tree/master"
+ },
+ "time": "2025-06-02T14:16:36+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {
+ "vojtech-dobes/php-grammar-processing": 20,
+ "vojtech-dobes/php-graphql-server": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "~8.4"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/extension.neon b/extension.neon
new file mode 100644
index 0000000..d7a6f94
--- /dev/null
+++ b/extension.neon
@@ -0,0 +1,22 @@
+parameters:
+ bootstrapFiles:
+ - src/GraphQL/bootstrap.php
+
+ scanDirectories:
+ - %graphql.generatedDir%
+
+parametersSchema:
+ graphql: structure([
+ generatedDir: string()
+ schemas: listOf(schema(string(), assert('is_file', 'Schema path must exist')))
+ ])
+
+services:
+ - class: Vojtechdobes\PHPStan\GraphQL\Config
+ arguments:
+ generatedDir: %graphql.generatedDir%
+ schemas: %graphql.schemas%
+
+ - class: Vojtechdobes\PHPStan\GraphQL\CorrespondanceRule
+ tags:
+ - phpstan.rules.rule
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000..841e0c9
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,13 @@
+parameters:
+ ignoreErrors:
+ -
+ message: '#^Method Vojtechdobes\\PHPStan\\GraphQL\\Helpers\:\:normalizeSchema\(\) throws checked exception Exception but it''s missing from the PHPDoc @throws tag\.$#'
+ identifier: missingType.checkedException
+ count: 1
+ path: src/GraphQL/Helpers.php
+
+ -
+ message: '#^Method Vojtechdobes\\PHPStan\\GraphQL\\SchemaServiceOraculum\:\:getFieldResolverType\(\) should return PHPStan\\Type\\ObjectType but returns PHPStan\\Type\\Type\.$#'
+ identifier: return.type
+ count: 1
+ path: src/GraphQL/SchemaServiceOraculum.php
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
new file mode 100644
index 0000000..f514c55
--- /dev/null
+++ b/phpstan.dist.neon
@@ -0,0 +1,36 @@
+includes:
+ - phpstan-baseline.neon
+ - vendor/phpstan/phpstan-strict-rules/rules.neon
+ - vendor/spaze/phpstan-disallowed-calls/extension.neon
+
+parameters:
+ checkMissingCallableSignature: true
+ disallowedFunctionCalls:
+ - function:
+ - 'dump()'
+ - 'var_dump()'
+ message: 'avoid committing debug calls'
+
+ exceptions:
+ check:
+ missingCheckedExceptionInThrows: true
+ tooWideThrowType: true
+
+ uncheckedExceptionClasses:
+ - Error
+ - LogicException
+ - Nette\IOException
+ - PHPStan\ShouldNotHappenException
+
+ excludePaths:
+ - tests/generated-graphql (?)
+
+ level: 8
+
+ paths:
+ - src
+ - tests
+ - tests-shared
+
+ strictRules:
+ disallowedShortTernary: false
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..5f2cf32
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..c869c2c
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,44 @@
+# PHPStan extension for [PHP GraphQL Server](https://github.com/vojtech-dobes/php-graphql-server)
+
+
+
+This is super-convenient companion if you use [`vojtech-dobes/php-graphql-server`](https://github.com/vojtech-dobes/php-graphql-server) and [PHPStan](https://phpstan.org/). With this extension, PHPStan will be able to point out:
+
+- mismatch between GraphQL Schema & what your resolvers actually return
+- mismatch between GraphQL Schema & what your resolvers actually accept as arguments
+- mismatch between declared parent value type and what resolver will actually receive
+- supports utility resolvers like `PropertyFieldResolver` etc.
+
+
+
+## Installation
+
+To install the latest version, run the following command:
+
+```
+composer require vojtech-dobes/phpstan-php-graphql-server
+```
+
+Then you can register in your `phpstan.neon`:
+
+```neon
+includes:
+ - vendor/vojtech-dobes/phpstan-php-graphql-server/extension.neon
+
+graphql:
+ generatedDir: ""
+ schemas:
+ - ""
+```
+
+Next you have to tell the extension about your resolvers. If you're using framework integration, use corresponding package:
+
+- **Integration:** `vojtech-dobes/php-graphql-server-nette-integration` (for Nette Framework)
+ **Package:** [`vojtech-dobes/phpstan-php-graphql-server-nette-integration`](https://github.com/vojtech-dobes/phpstan-php-graphql-server-nette-integration)
+
+In case of custom setup, please implement `Vojtechdobes\PHPStan\GraphQL\Adapter` interface and register like this in `phpstan.neon`:
+
+```neon
+services:
+ - class: MyCustomAdapter
+```
diff --git a/src/GraphQL/Adapter.php b/src/GraphQL/Adapter.php
new file mode 100644
index 0000000..eac947d
--- /dev/null
+++ b/src/GraphQL/Adapter.php
@@ -0,0 +1,20 @@
+ */
+ public readonly array $schemas;
+
+
+
+ /**
+ * @param list $schemas
+ * @throws Exception
+ */
+ public function __construct(
+ public readonly string $generatedDir,
+ array $schemas,
+ )
+ {
+ $this->schemas = array_map(
+ static function ($schema): string {
+ $schemaFile = realpath($schema);
+
+ if ($schemaFile === false) {
+ throw new Exception("$schema doesn't exist");
+ }
+
+ return $schemaFile;
+ },
+ $schemas,
+ );
+ }
+
+}
diff --git a/src/GraphQL/CorrespondanceRule.php b/src/GraphQL/CorrespondanceRule.php
new file mode 100644
index 0000000..b9de753
--- /dev/null
+++ b/src/GraphQL/CorrespondanceRule.php
@@ -0,0 +1,370 @@
+
+ */
+final class CorrespondanceRule implements PHPStan\Rules\Rule
+{
+
+ public function __construct(
+ private readonly Config $config,
+ private readonly PHPStan\Reflection\ReflectionProvider $reflectionProvider,
+ ) {}
+
+
+
+ public function getNodeType(): string
+ {
+ return PHPStan\Node\CollectedDataNode::class;
+ }
+
+
+
+ public function processNode(PhpParser\Node $node, PHPStan\Analyser\Scope $scope): array
+ {
+ $result = [];
+
+ foreach ($this->config->schemas as $schemaName) {
+ $validClassName = Helpers::createValidSchemaClassName($schemaName);
+ $invalidClassName = Helpers::createInvalidSchemaClassName($schemaName);
+
+ // following check bypasses PHPStan\Testing\RuleTestCase
+ // not being able to discover file generated on-the-fly
+ if (
+ class_exists($invalidClassName) === false
+ && is_file($invalidClassNameFile = ($this->config->generatedDir . '/' . $invalidClassName . '.php'))
+ ) {
+ require_once $invalidClassNameFile;
+ }
+
+ if ($this->reflectionProvider->hasClass($invalidClassName)) {
+ $result[] = PHPStan\Rules\RuleErrorBuilder::message("GraphQL schema isn't valid.")
+ ->identifier('graphql.schemaInvalid')
+ ->file($schemaName)
+ ->line(0)
+ ->nonIgnorable()
+ ->build();
+
+ continue;
+ }
+
+ $schemaServiceOraculum = $this->createSchemaServiceOraculum($validClassName);
+
+ $fields = $schemaServiceOraculum->listFields();
+
+ foreach ($fields as $field) {
+ [$objectType, $fieldName] = explode('.', $field);
+
+ $schemaType = $schemaServiceOraculum->getFieldSchemaType($objectType, $fieldName);
+
+ if ($schemaType[count($schemaType) - 1] === $schemaServiceOraculum->getRootOperationType(Vojtechdobes\GraphQL\OperationType::Query)) {
+ continue;
+ }
+
+ $resolverClassType = $schemaServiceOraculum->getFieldResolverType($objectType, $fieldName);
+
+ $expectedArgumentsType = $schemaServiceOraculum->getFieldArgumentsType($objectType, $fieldName);
+ $actualArgumentsType = $resolverClassType->getTemplateType(Vojtechdobes\GraphQL\FieldResolver::class, 'TArguments');
+
+ if (
+ $actualArgumentsType->isNull()->yes() === false // misconfigured generics are already reported in native rule
+ && $expectedArgumentsType->isSuperTypeOf($actualArgumentsType)->yes() === false
+ ) {
+ $message = sprintf(
+ "Arguments %s of field %s aren't contravariant with arguments %s of resolver %s",
+ $expectedArgumentsType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ $field,
+ $actualArgumentsType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ $resolverClassType->getClassName(),
+ );
+
+ $result[] = PHPStan\Rules\RuleErrorBuilder::message($message)
+ ->identifier('graphql.argumentsMismatch')
+ ->file($schemaName)
+ ->build();
+ }
+
+ $result = [
+ ...$result,
+ ...$this->listFieldResolvedValueErrors($scope, $schemaServiceOraculum, $schemaName, $objectType, $fieldName, $resolverClassType),
+ ];
+ }
+ }
+
+ return $result;
+ }
+
+
+
+ private function createSchemaServiceOraculum(string $validClassName): SchemaServiceOraculum
+ {
+ // following check bypasses PHPStan\Testing\RuleTestCase
+ // not being able to discover file generated on-the-fly
+ if (
+ class_exists($validClassName) === false
+ && is_file($validClassNameFile = ($this->config->generatedDir . '/' . $validClassName . '.php'))
+ ) {
+ require_once $validClassNameFile;
+ }
+
+ return new SchemaServiceOraculum(
+ $this->reflectionProvider->getClass($validClassName),
+ );
+ }
+
+
+
+ /**
+ * @return list
+ */
+ private function listFieldResolvedValueErrors(
+ PHPStan\Analyser\Scope $scope,
+ SchemaServiceOraculum $schemaServiceOraculum,
+ string $schemaName,
+ string $objectType,
+ string $fieldName,
+ PHPStan\Type\ObjectType $resolverClassType,
+ ): array
+ {
+ $result = [];
+
+ $expectedPhpType = $schemaServiceOraculum->getFieldPhpType($objectType, $fieldName);
+
+ [$actualPhpTypes, $errors] = $this->listFieldResolvedValueTypes(
+ $scope,
+ $schemaServiceOraculum,
+ $objectType,
+ $fieldName,
+ $resolverClassType,
+ );
+
+ foreach ($errors as $error) {
+ $result[] = PHPStan\Rules\RuleErrorBuilder::message($error)
+ ->identifier('graphql.typeMismatch')
+ ->file($schemaName)
+ ->build();
+ }
+
+ foreach ($actualPhpTypes as $actualPhpType) {
+ if ($expectedPhpType->isSuperTypeOf($actualPhpType)->yes() === false) {
+ $message = sprintf(
+ "Type of field %s should be %s but resolver %s returns %s",
+ "{$objectType}.{$fieldName}",
+ $expectedPhpType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ $actualPhpType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ );
+
+ $result[] = PHPStan\Rules\RuleErrorBuilder::message($message)
+ ->identifier('graphql.typeMismatch')
+ ->file($schemaName)
+ ->build();
+ }
+ }
+
+ return $result;
+ }
+
+
+
+ /**
+ * @return array{list, list}
+ */
+ private function listFieldResolvedValueTypes(
+ PHPStan\Analyser\Scope $scope,
+ SchemaServiceOraculum $schemaServiceOraculum,
+ string $objectType,
+ string $fieldName,
+ PHPStan\Type\ObjectType $resolverClassType,
+ ): array
+ {
+ $errors = [];
+ $types = [];
+
+ if ($resolverClassType->getClassName() === Vojtechdobes\GraphQL\ArrayFieldResolver::class) {
+ $offsetType = new PHPStan\Type\Constant\ConstantStringType($fieldName);
+
+ foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
+ if ($parentType->isOffsetAccessible()->yes() === false) {
+ $errors[] = sprintf(
+ "Resolver %s of field %s expects parent to have array access, but parent is resolved to %s",
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ "{$objectType}.{$fieldName}",
+ $parentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ );
+ } elseif ($parentType->hasOffsetValueType($offsetType)->yes() === false) {
+ $errors[] = sprintf(
+ "Resolver %s of field %s expects parent to have offset '%s', but parent is resolved to %s",
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ "{$objectType}.{$fieldName}",
+ $fieldName,
+ $parentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ );
+ } else {
+ $types[] = $parentType->getOffsetValueType($offsetType);
+ }
+ }
+ } elseif ($resolverClassType->getClassName() === Vojtechdobes\GraphQL\GetterFieldResolver::class) {
+ $methodName = 'get' . ucfirst($fieldName);
+
+ foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
+ if ($parentType->isObject()->yes() === false) {
+ $errors[] = sprintf(
+ "Resolver %s of field %s expects parent to be an object, but parent is resolved to %s",
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ "{$objectType}.{$fieldName}",
+ $parentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ );
+ } elseif ($parentType->hasMethod($methodName)->yes() === false) {
+ $errors[] = sprintf(
+ "Resolver %s of field %s expects parent to have method %s(), but method %s::%s() doesn't exist",
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ "{$objectType}.{$fieldName}",
+ $methodName,
+ $parentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ $methodName,
+ );
+ } else {
+ $types[] = PHPStan\Type\TypeCombinator::union(...array_map(
+ static fn ($variant) => $variant->getReturnType(),
+ $parentType->getMethod($methodName, $scope)->getVariants(),
+ ));
+ }
+ }
+ } elseif ($resolverClassType->getClassName() === Vojtechdobes\GraphQL\PropertyFieldResolver::class) {
+ foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
+ if ($parentType->isObject()->yes() === false) {
+ $errors[] = sprintf(
+ "Resolver %s of field %s expects parent to be an object, but parent is resolved to %s",
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ "{$objectType}.{$fieldName}",
+ $parentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ );
+ } elseif ($parentType->hasProperty($fieldName)->yes() === false) {
+ $errors[] = sprintf(
+ "Resolver %s of field %s expects parent to have property \$%s, but property %s::\$%s doesn't exist",
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ "{$objectType}.{$fieldName}",
+ $fieldName,
+ $parentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ $fieldName,
+ );
+ } else {
+ $types[] = $parentType->getProperty($fieldName, $scope)->getReadableType();
+ }
+ }
+ } else {
+ $expectedParentType = $resolverClassType->getTemplateType(Vojtechdobes\GraphQL\FieldResolver::class, 'TObjectValue');
+
+ foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
+ if ($parentType->isSuperTypeOf($expectedParentType)->yes() === false) {
+ $errors[] = sprintf(
+ "Resolver %s of field %s expects parent to be %s, but parent is resolved to %s",
+ $resolverClassType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ "{$objectType}.{$fieldName}",
+ $expectedParentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ $parentType->describe(PHPStan\Type\VerbosityLevel::precise()),
+ );
+ }
+ }
+
+ if ($errors === []) {
+ $types[] = $resolverClassType->getTemplateType(Vojtechdobes\GraphQL\FieldResolver::class, 'TResolvedValue');
+ }
+ }
+
+ return [
+ array_map(
+ fn ($type) => $this->stripAwayDeferred($type),
+ $types,
+ ),
+ $errors,
+ ];
+ }
+
+
+
+ /**
+ * @return list
+ */
+ private function listObjectTypeResolvedValueTypes(
+ PHPStan\Analyser\Scope $scope,
+ SchemaServiceOraculum $schemaServiceOraculum,
+ string $objectType,
+ ): array
+ {
+ $result = [];
+
+ foreach ($schemaServiceOraculum->listFieldsResolvedToObjectType($objectType) as [$parentObjectType, $parentFieldName]) {
+ [$parentTypes] = $this->listFieldResolvedValueTypes(
+ $scope,
+ $schemaServiceOraculum,
+ $parentObjectType,
+ $parentFieldName,
+ $schemaServiceOraculum->getFieldResolverType($parentObjectType, $parentFieldName),
+ );
+
+ foreach ($parentTypes as $parentType) {
+ $parentType = $this->unwrapType(
+ $schemaServiceOraculum,
+ $parentObjectType,
+ $parentFieldName,
+ $this->stripAwayDeferred($parentType),
+ );
+
+ $result[$parentType->describe(PHPStan\Type\VerbosityLevel::precise())] = $parentType;
+ }
+ }
+
+ return array_values($result);
+ }
+
+
+
+ private function stripAwayDeferred(
+ PHPStan\Type\Type $type,
+ ): PHPStan\Type\Type
+ {
+ return PHPStan\Type\TypeTraverser::map(
+ $type,
+ function (PHPStan\Type\Type $type, callable $traverse): PHPStan\Type\Type {
+ if ($type instanceof PHPStan\Type\Generic\GenericObjectType && $type->getClassName() === Vojtechdobes\GraphQL\Deferred::class) {
+ return $type->getTemplateType(Vojtechdobes\GraphQL\Deferred::class, 'TValue');
+ }
+
+ return $traverse($type);
+ },
+ );
+ }
+
+
+
+ private function unwrapType(
+ SchemaServiceOraculum $schemaServiceOraculum,
+ string $objectType,
+ string $fieldName,
+ PHPStan\Type\Type $type,
+ ): PHPStan\Type\Type
+ {
+ $schemaType = $schemaServiceOraculum->getFieldSchemaType($objectType, $fieldName);
+
+ foreach ($schemaType as $schemaTypeLevel) {
+ $type = PHPStan\Type\TypeCombinator::removeNull(
+ match ($schemaTypeLevel) {
+ ':list' => $type->getIterableValueType(),
+ default => $type,
+ },
+ );
+ }
+
+ return $type;
+ }
+
+}
diff --git a/src/GraphQL/Helpers.php b/src/GraphQL/Helpers.php
new file mode 100644
index 0000000..a9d21ef
--- /dev/null
+++ b/src/GraphQL/Helpers.php
@@ -0,0 +1,43 @@
+getSchema($schemaName);
+ } catch (Vojtechdobes\GraphQL\Exceptions\InvalidSchemaException $e) {
+ $this->generateInvalidSchemaClass(
+ $invalidClassName,
+ $e,
+ );
+
+ return;
+ }
+
+ Nette\Utils\FileSystem::delete(
+ "{$this->generatedDir}/{$invalidClassName}.php",
+ );
+
+ $this->generateValidSchemaClass(
+ $validClassName,
+ $schema,
+ $adapter->getFieldResolverProvider($schemaName),
+ );
+ }
+
+
+
+ private function generateInvalidSchemaClass(
+ string $invalidClassName,
+ Vojtechdobes\GraphQL\Exceptions\InvalidSchemaException $e,
+ ): void
+ {
+ $file = new Nette\PhpGenerator\PhpFile();
+ $file->setStrictTypes();
+
+ $file->addClass($invalidClassName);
+
+ Nette\Utils\FileSystem::write(
+ "{$this->generatedDir}/{$invalidClassName}.php",
+ (string) $file,
+ );
+ }
+
+
+
+ private function generateValidSchemaClass(
+ string $className,
+ Vojtechdobes\GraphQL\TypeSystem\Schema $schema,
+ Vojtechdobes\GraphQL\FieldResolverProvider $fieldResolverProvider,
+ ): void
+ {
+ $file = new Nette\PhpGenerator\PhpFile();
+ $file->setStrictTypes();
+
+ $class = $file->addClass($className);
+
+ foreach ($schema->rootOperationTypes as $operation => $type) {
+ $class->addProperty(sprintf("root__%s__type", $operation))
+ ->setPublic()
+ ->setValue($type);
+ }
+
+ $fields = [];
+ $objectFields = [];
+
+ foreach ($schema->getTypeDefinitions() as $typeDefinition) {
+ if (!$typeDefinition instanceof Vojtechdobes\GraphQL\TypeSystem\ObjectTypeDefinition) {
+ continue;
+ }
+
+ if (str_starts_with($typeDefinition->name, '__')) {
+ continue;
+ }
+
+ $objectType = $typeDefinition->name;
+
+ foreach ($typeDefinition->fields as $fieldDefinition) {
+ $fieldName = $fieldDefinition->name;
+
+ $fields[] = "{$objectType}.{$fieldName}";
+
+ $class->addProperty('field__' . $objectType . '_' . $fieldName . '__phpType')
+ ->setPublic()
+ ->addComment('@var ' . self::getPhpType($schema, $fieldDefinition->type));
+
+ $schemaType = [];
+
+ $type = $fieldDefinition->type;
+
+ while ($type !== null) {
+ $schemaType[] = match (true) {
+ $type instanceof Vojtechdobes\GraphQL\Types\ListType => ':list',
+ $type instanceof Vojtechdobes\GraphQL\Types\NamedType => $type->name,
+ $type instanceof Vojtechdobes\GraphQL\Types\NonNullType => ':nonNull',
+ default => throw new PHPStan\ShouldNotHappenException(),
+ };
+
+ $type = $type->getWrappedType();
+ }
+
+ $class->addProperty('field__' . $objectType . '_' . $fieldName . '__schemaType')
+ ->setPublic()
+ ->setValue($schemaType);
+
+ $class->addProperty('field__' . $objectType . '_' . $fieldName . '__resolverClass')
+ ->setPublic()
+ ->addComment('@var ' . (
+ $fieldResolverProvider->getFieldResolverClass("{$objectType}.{$fieldName}")
+ ?? $fieldResolverProvider->getFieldResolverClass($objectType)
+ ));
+
+ $class->addProperty('field__' . $objectType . '_' . $fieldName . '__arguments')
+ ->setPublic()
+ ->addComment('@var ' . sprintf(
+ 'array{%s}',
+ implode(', ', array_map(
+ fn ($argumentDefinition) => sprintf(
+ '%s: %s',
+ $argumentDefinition->name,
+ self::getPhpType(
+ $schema,
+ $argumentDefinition->type,
+ ),
+ ),
+ $fieldDefinition->argumentDefinitions,
+ )),
+ ));
+
+ $typeDefinition = $schema->getTypeDefinition($fieldDefinition->type->getNamedType());
+
+ if ($typeDefinition instanceof Vojtechdobes\GraphQL\TypeSystem\ObjectTypeDefinition) {
+ $objectFields[$typeDefinition->name] ??= [];
+ $objectFields[$typeDefinition->name][] = [$objectType, $fieldName];
+ }
+ }
+ }
+
+ $class->addProperty('fields')
+ ->setPublic()
+ ->setValue($fields);
+
+ foreach ($objectFields as $objectType => $fieldNames) {
+ $class->addProperty('objectType__' . $objectType .'__fields')
+ ->setPublic()
+ ->setValue($fieldNames);
+ }
+
+ Nette\Utils\FileSystem::write(
+ "{$this->generatedDir}/{$className}.php",
+ (string) $file,
+ );
+ }
+
+
+
+ private static function getPhpType(
+ Vojtechdobes\GraphQL\TypeSystem\Schema $schema,
+ Vojtechdobes\GraphQL\Types\Type $type,
+ ): string
+ {
+ return match (true) {
+ $type instanceof Vojtechdobes\GraphQL\Types\ListType => 'iterable<' . self::getPhpType($schema, $type->itemType) . '>|null',
+ $type instanceof Vojtechdobes\GraphQL\Types\NamedType => self::getPhpNamedType($schema, $type),
+ $type instanceof Vojtechdobes\GraphQL\Types\NonNullType => substr(self::getPhpType($schema, $type->type), 0, -5), // remove |null
+ default => throw new PHPStan\ShouldNotHappenException(),
+ };
+ }
+
+
+
+ private static function getPhpNamedType(
+ Vojtechdobes\GraphQL\TypeSystem\Schema $schema,
+ Vojtechdobes\GraphQL\Types\NamedType $type,
+ ): string
+ {
+ $typeDefinition = $schema->getTypeDefinitionOrNull($type->name);
+
+ if ($typeDefinition === null) {
+ throw new PHPStan\ShouldNotHappenException("Type definition '{$type->name}' can't be found");
+ }
+
+ if ($typeDefinition instanceof Vojtechdobes\GraphQL\TypeSystem\ScalarTypeDefinition) {
+ $scalarFormatter = $schema->scalarImplementationRegistry->getItem($typeDefinition->name);
+
+ $fieldType = '__GraphQL__Scalar<' . $scalarFormatter::class. '>';
+ } elseif ($typeDefinition instanceof Vojtechdobes\GraphQL\TypeSystem\EnumTypeDefinition) {
+ $enumClass = $schema->getEnumClass($typeDefinition->name);
+
+ if ($enumClass !== null) {
+ $fieldType = $enumClass;
+ } else {
+ $fieldType = implode('|', array_map(
+ static fn ($enumValueDefinition) => '"' . $enumValueDefinition->name . '"',
+ $typeDefinition->enumValues,
+ ));
+ }
+ } elseif ($typeDefinition instanceof Vojtechdobes\GraphQL\TypeSystem\InputObjectTypeDefinition) {
+ $inputFieldTypes = [];
+
+ foreach ($typeDefinition->fields as $inputFieldDefinition) {
+ $inputFieldTypes[] = sprintf(
+ '%s: %s',
+ $inputFieldDefinition->name,
+ self::getPhpType(
+ $schema,
+ $inputFieldDefinition->type,
+ ),
+ );
+ }
+
+ $fieldType = sprintf(
+ 'array{%s}',
+ implode(', ', $inputFieldTypes),
+ );
+ } else {
+ $fieldType = 'mixed';
+ }
+
+ return $fieldType . '|null';
+ }
+
+}
diff --git a/src/GraphQL/SchemaServiceOraculum.php b/src/GraphQL/SchemaServiceOraculum.php
new file mode 100644
index 0000000..5da2d5b
--- /dev/null
+++ b/src/GraphQL/SchemaServiceOraculum.php
@@ -0,0 +1,126 @@
+schemaReflection
+ ->getNativeProperty('root__' . $operationType->value . '__type')
+ ->getNativeReflection()
+ ->getDefaultValue();
+ }
+
+
+
+ /**
+ * @return list
+ */
+ public function listFields(): array
+ {
+ return $this->schemaReflection
+ ->getNativeProperty('fields')
+ ->getNativeReflection()
+ ->getDefaultValue();
+ }
+
+
+
+ /**
+ * @return list
+ */
+ public function listFieldsResolvedToObjectType(string $objectType): array
+ {
+ $propertyName = 'objectType__' . $objectType . '__fields';
+
+ if ($this->schemaReflection->hasProperty($propertyName) === false) {
+ return [];
+ }
+
+ return $this->schemaReflection
+ ->getNativeProperty($propertyName)
+ ->getNativeReflection()
+ ->getDefaultValue();
+ }
+
+
+
+ /**
+ * @return list
+ */
+ public function getFieldSchemaType(string $objectType, string $fieldName): array
+ {
+ $propertyName = 'field__' . $objectType . '_' . $fieldName . '__schemaType';
+
+ if ($this->schemaReflection->hasProperty($propertyName) === false) {
+ return [];
+ }
+
+ return $this->schemaReflection
+ ->getNativeProperty($propertyName)
+ ->getNativeReflection()
+ ->getDefaultValue();
+ }
+
+
+
+ public function getFieldResolverType(string $objectType, string $fieldName): PHPStan\Type\ObjectType
+ {
+ return $this->schemaReflection
+ ->getNativeProperty('field__' . $objectType . '_' . $fieldName . '__resolverClass')
+ ->getPhpDocType();
+ }
+
+
+
+ public function getFieldPhpType(string $objectType, string $fieldName): PHPStan\Type\Type
+ {
+ return $this->resolveEncodedPhpType(
+ $this->schemaReflection
+ ->getNativeProperty('field__' . $objectType . '_' . $fieldName . '__phpType')
+ ->getPhpDocType()
+ );
+ }
+
+
+
+ public function getFieldArgumentsType(string $objectType, string $fieldName): PHPStan\Type\Type
+ {
+ return $this->resolveEncodedPhpType(
+ $this->schemaReflection
+ ->getNativeProperty('field__' . $objectType . '_' . $fieldName . '__arguments')
+ ->getPhpDocType()
+ );
+ }
+
+
+
+ private function resolveEncodedPhpType(PHPStan\Type\Type $encodedType): PHPStan\Type\Type
+ {
+ return PHPStan\Type\TypeTraverser::map(
+ $encodedType,
+ function (PHPStan\Type\Type $type, callable $traverse): PHPStan\Type\Type {
+ if ($type instanceof PHPStan\Type\Generic\GenericObjectType && $type->getClassName() === '__GraphQL__Scalar') {
+ return $type
+ ->getTypes()[0]
+ ->getTemplateType(Vojtechdobes\GraphQL\ScalarImplementation::class, 'TValue');
+ }
+
+ return $traverse($type);
+ },
+ );
+ }
+
+}
diff --git a/src/GraphQL/bootstrap.php b/src/GraphQL/bootstrap.php
new file mode 100644
index 0000000..4629ce7
--- /dev/null
+++ b/src/GraphQL/bootstrap.php
@@ -0,0 +1,20 @@
+getByType(Vojtechdobes\PHPStan\GraphQL\Config::class);
+
+$schemaClassGenerator = new Vojtechdobes\PHPStan\GraphQL\SchemaClassGenerator(
+ $config->generatedDir,
+);
+
+foreach ($config->schemas as $schemaName) {
+ $schemaClassGenerator->generateSchemaClass(
+ $schemaName,
+ Vojtechdobes\PHPStan\GraphQL\Helpers::createValidSchemaClassName($schemaName),
+ Vojtechdobes\PHPStan\GraphQL\Helpers::createInvalidSchemaClassName($schemaName),
+ $container->getByType(Vojtechdobes\PHPStan\GraphQL\Adapter::class),
+ );
+}
diff --git a/tests-shared/AbstractCorrespondanceRuleTest.php b/tests-shared/AbstractCorrespondanceRuleTest.php
new file mode 100644
index 0000000..381d2bc
--- /dev/null
+++ b/tests-shared/AbstractCorrespondanceRuleTest.php
@@ -0,0 +1,61 @@
+
+ */
+abstract class AbstractCorrespondanceRuleTest extends PHPStan\Testing\RuleTestCase
+{
+
+ final protected function getRule(): PHPStan\Rules\Rule
+ {
+ return self::getContainer()->getByType(Vojtechdobes\PHPStan\GraphQL\CorrespondanceRule::class);
+ }
+
+
+
+ final protected function getCollectors(): array
+ {
+ // rule based on CollectedDataNode won't run without any collector
+ return [
+ new DummyCollector(),
+ ];
+ }
+
+
+
+ final public function testRule(): void
+ {
+ $this->analyse([__DIR__ . '/DummyCollector.php'], [
+ [
+ "Type of field Query.invalidStringResolvedAsBool should be string|null but resolver Vojtechdobes\TestsShared\Resolvers\QueryInvalidStringResolvedAsBoolFieldResolver returns bool",
+ -1,
+ ],
+ [
+ "Arguments array{arg1: string|null} of field Query.invalidArgumentsMismatch aren't contravariant with arguments array{} of resolver Vojtechdobes\TestsShared\Resolvers\QueryInvalidArgumentsMismatchFieldResolver",
+ -1,
+ ],
+ [
+ "Resolver Vojtechdobes\TestsShared\Resolvers\InvalidParentTypeNameFieldResolver of field InvalidParentType.name expects parent to be Vojtechdobes\TestsShared\Resolvers\Person, but parent is resolved to array{}",
+ -1,
+ ],
+ ]);
+ }
+
+
+
+ final public static function getAdditionalConfigFiles(): array
+ {
+ return [static::getTestConfigFile()];
+ }
+
+
+
+ abstract public static function getTestConfigFile(): string;
+
+}
diff --git a/tests-shared/DummyCollector.php b/tests-shared/DummyCollector.php
new file mode 100644
index 0000000..b32563d
--- /dev/null
+++ b/tests-shared/DummyCollector.php
@@ -0,0 +1,27 @@
+
+ */
+final class DummyCollector implements PHPStan\Collectors\Collector
+{
+
+ public function getNodeType(): string
+ {
+ return PHPStan\Node\FileNode::class;
+ }
+
+
+
+ public function processNode(PhpParser\Node $node, PHPStan\Analyser\Scope $scope)
+ {
+ return true;
+ }
+
+}
diff --git a/tests-shared/Resolvers/InvalidParentTypeNameFieldResolver.php b/tests-shared/Resolvers/InvalidParentTypeNameFieldResolver.php
new file mode 100644
index 0000000..7754231
--- /dev/null
+++ b/tests-shared/Resolvers/InvalidParentTypeNameFieldResolver.php
@@ -0,0 +1,19 @@
+
+ */
+final class InvalidParentTypeNameFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return $objectValue->name;
+ }
+
+}
diff --git a/tests-shared/Resolvers/ObjectType.php b/tests-shared/Resolvers/ObjectType.php
new file mode 100644
index 0000000..73562c2
--- /dev/null
+++ b/tests-shared/Resolvers/ObjectType.php
@@ -0,0 +1,20 @@
+
+ */
+final class QueryArrayTypeFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return [
+ 'a' => 'Alice',
+ ];
+ }
+
+}
diff --git a/tests-shared/Resolvers/QueryInvalidArgumentsMismatchFieldResolver.php b/tests-shared/Resolvers/QueryInvalidArgumentsMismatchFieldResolver.php
new file mode 100644
index 0000000..728d0d4
--- /dev/null
+++ b/tests-shared/Resolvers/QueryInvalidArgumentsMismatchFieldResolver.php
@@ -0,0 +1,19 @@
+
+ */
+final class QueryInvalidArgumentsMismatchFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return 'Charlie';
+ }
+
+}
diff --git a/tests-shared/Resolvers/QueryInvalidStringResolvedAsBoolFieldResolver.php b/tests-shared/Resolvers/QueryInvalidStringResolvedAsBoolFieldResolver.php
new file mode 100644
index 0000000..9e93e43
--- /dev/null
+++ b/tests-shared/Resolvers/QueryInvalidStringResolvedAsBoolFieldResolver.php
@@ -0,0 +1,19 @@
+
+ */
+final class QueryInvalidStringResolvedAsBoolFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return true;
+ }
+
+}
diff --git a/tests-shared/Resolvers/QueryObjectTypeFieldResolver.php b/tests-shared/Resolvers/QueryObjectTypeFieldResolver.php
new file mode 100644
index 0000000..a9589e4
--- /dev/null
+++ b/tests-shared/Resolvers/QueryObjectTypeFieldResolver.php
@@ -0,0 +1,19 @@
+
+ */
+final class QueryObjectTypeFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return new ObjectType();
+ }
+
+}
diff --git a/tests-shared/Resolvers/QueryProviderOfInvalidParentTypeFieldResolver.php b/tests-shared/Resolvers/QueryProviderOfInvalidParentTypeFieldResolver.php
new file mode 100644
index 0000000..6025872
--- /dev/null
+++ b/tests-shared/Resolvers/QueryProviderOfInvalidParentTypeFieldResolver.php
@@ -0,0 +1,19 @@
+
+ */
+final class QueryProviderOfInvalidParentTypeFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return [];
+ }
+
+}
diff --git a/tests-shared/Resolvers/QueryValidDeferredFieldResolver.php b/tests-shared/Resolvers/QueryValidDeferredFieldResolver.php
new file mode 100644
index 0000000..592478a
--- /dev/null
+++ b/tests-shared/Resolvers/QueryValidDeferredFieldResolver.php
@@ -0,0 +1,19 @@
+>
+ */
+final class QueryValidDeferredFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return new Vojtechdobes\GraphQL\Deferred(static fn () => 'David');
+ }
+
+}
diff --git a/tests-shared/Resolvers/QueryValidNonNullStringFieldResolver.php b/tests-shared/Resolvers/QueryValidNonNullStringFieldResolver.php
new file mode 100644
index 0000000..53b22d9
--- /dev/null
+++ b/tests-shared/Resolvers/QueryValidNonNullStringFieldResolver.php
@@ -0,0 +1,19 @@
+
+ */
+final class QueryValidNonNullStringFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
+{
+
+ public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
+ {
+ return 'Alice';
+ }
+
+}
diff --git a/tests-shared/schema.graphqls b/tests-shared/schema.graphqls
new file mode 100644
index 0000000..4d44dec
--- /dev/null
+++ b/tests-shared/schema.graphqls
@@ -0,0 +1,26 @@
+type Query {
+ validNonNullString: String!
+ invalidStringResolvedAsBool: String
+
+ invalidArgumentsMismatch(arg1: String): String
+
+ validDeferred: String
+
+ arrayType: ArrayType!
+ objectType: ObjectType!
+
+ providerOfInvalidParentType: InvalidParentType!
+}
+
+type ArrayType {
+ a: String!
+}
+
+type InvalidParentType {
+ name: String!
+}
+
+type ObjectType {
+ withGetter: String!
+ withProperty: String!
+}
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..eb5bd07
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1 @@
+generated-graphql
diff --git a/tests/CorrespondanceRuleTest.extension.neon b/tests/CorrespondanceRuleTest.extension.neon
new file mode 100644
index 0000000..c509c36
--- /dev/null
+++ b/tests/CorrespondanceRuleTest.extension.neon
@@ -0,0 +1,10 @@
+includes:
+ - ../extension.neon
+
+parameters:
+ graphql:
+ generatedDir: %rootDir%/../../../tests/generated-graphql
+ schemas: [%rootDir%/../../../tests-shared/schema.graphqls]
+
+services:
+ - Vojtechdobes\Tests\CustomAdapter
diff --git a/tests/CorrespondanceRuleTest.php b/tests/CorrespondanceRuleTest.php
new file mode 100644
index 0000000..9dbfa90
--- /dev/null
+++ b/tests/CorrespondanceRuleTest.php
@@ -0,0 +1,16 @@
+loadSchema(
+ schemaPath: __DIR__ . '/../tests-shared/schema.graphqls',
+ enumClasses: [],
+ scalarImplementations: [],
+ );
+ }
+
+
+
+ public function getFieldResolverProvider(string $schemaName): Vojtechdobes\GraphQL\FieldResolverProvider
+ {
+ return new Vojtechdobes\GraphQL\StaticFieldResolverProvider([
+ 'Query.validNonNullString' => new Vojtechdobes\TestsShared\Resolvers\QueryValidNonNullStringFieldResolver(),
+ 'Query.invalidStringResolvedAsBool' => new Vojtechdobes\TestsShared\Resolvers\QueryInvalidStringResolvedAsBoolFieldResolver(),
+ 'Query.invalidArgumentsMismatch' => new Vojtechdobes\TestsShared\Resolvers\QueryInvalidArgumentsMismatchFieldResolver(),
+ 'Query.validDeferred' => new Vojtechdobes\TestsShared\Resolvers\QueryValidDeferredFieldResolver(),
+
+ 'Query.arrayType' => new Vojtechdobes\TestsShared\Resolvers\QueryArrayTypeFieldResolver(),
+ 'ArrayType' => new Vojtechdobes\GraphQL\ArrayFieldResolver(),
+
+ 'Query.objectType' => new Vojtechdobes\TestsShared\Resolvers\QueryObjectTypeFieldResolver(),
+ 'ObjectType.withGetter' => new Vojtechdobes\GraphQL\GetterFieldResolver(),
+ 'ObjectType.withProperty' => new Vojtechdobes\GraphQL\PropertyFieldResolver(),
+
+ 'Query.providerOfInvalidParentType' => new Vojtechdobes\TestsShared\Resolvers\QueryProviderOfInvalidParentTypeFieldResolver(),
+ 'InvalidParentType.name' => new Vojtechdobes\TestsShared\Resolvers\InvalidParentTypeNameFieldResolver(),
+ ]);
+ }
+
+}