diff --git a/Makefile b/Makefile
index 9b28eb9..4c6372f 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ vendor: composer.json composer.lock
.PHONY: benchmark
benchmark:
- docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project php:7.4-cli tools/phpbench run
+ docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project php:7.4-cli vendor/bin/phpbench run
.PHONY: rector
rector: ## Refactor code using rector
diff --git a/composer.json b/composer.json
index a7ae10f..e69777c 100644
--- a/composer.json
+++ b/composer.json
@@ -11,7 +11,8 @@
],
"require": {
"php": "^7.4 || ^8.0",
- "phpdocumentor/reflection-common": "^2.0"
+ "phpdocumentor/reflection-common": "^2.0",
+ "phpstan/phpdoc-parser": "^1.13"
},
"require-dev": {
"ext-tokenizer": "*",
@@ -20,7 +21,8 @@
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/extension-installer": "^1.1",
"vimeo/psalm": "^4.25",
- "rector/rector": "^0.13.9"
+ "rector/rector": "^0.13.9",
+ "phpbench/phpbench": "^1.2"
},
"autoload": {
"psr-4": {
diff --git a/composer.lock b/composer.lock
index adcb69d..24293cd 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "0995b56862f68800c3c014da7cd2503f",
+ "content-hash": "e012cde7a3d864a1bed7eea7698cf2eb",
"packages": [
{
"name": "phpdocumentor/reflection-common",
@@ -58,6 +58,51 @@
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
},
"time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "1.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "33aefcdab42900e36366d0feab6206e2dd68f947"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/33aefcdab42900e36366d0feab6206e2dd68f947",
+ "reference": "33aefcdab42900e36366d0feab6206e2dd68f947",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^1.5",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.13.0"
+ },
+ "time": "2022-10-21T09:57:39+00:00"
}
],
"packages-dev": [
@@ -555,6 +600,79 @@
},
"time": "2019-12-04T15:06:13+00:00"
},
+ {
+ "name": "doctrine/annotations",
+ "version": "1.13.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/annotations.git",
+ "reference": "648b0343343565c4a056bfc8392201385e8d89f0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0",
+ "reference": "648b0343343565c4a056bfc8392201385e8d89f0",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/lexer": "1.*",
+ "ext-tokenizer": "*",
+ "php": "^7.1 || ^8.0",
+ "psr/cache": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "doctrine/cache": "^1.11 || ^2.0",
+ "doctrine/coding-standard": "^6.0 || ^8.1",
+ "phpstan/phpstan": "^1.4.10 || ^1.8.0",
+ "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5",
+ "symfony/cache": "^4.4 || ^5.2",
+ "vimeo/psalm": "^4.10"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Docblock Annotations Parser",
+ "homepage": "https://www.doctrine-project.org/projects/annotations.html",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "parser"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/annotations/issues",
+ "source": "https://github.com/doctrine/annotations/tree/1.13.3"
+ },
+ "time": "2022-07-02T10:48:51+00:00"
+ },
{
"name": "doctrine/instantiator",
"version": "1.4.1",
@@ -625,6 +743,82 @@
],
"time": "2022-03-03T08:28:38+00:00"
},
+ {
+ "name": "doctrine/lexer",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/lexer.git",
+ "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229",
+ "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9.0",
+ "phpstan/phpstan": "^1.3",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.11"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+ "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "lexer",
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/lexer/issues",
+ "source": "https://github.com/doctrine/lexer/tree/1.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-02-28T11:07:21+00:00"
+ },
{
"name": "felixfbecker/advanced-json-rpc",
"version": "v3.2.1",
@@ -1056,6 +1250,198 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
+ {
+ "name": "phpbench/container",
+ "version": "2.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpbench/container.git",
+ "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpbench/container/zipball/6d555ff7174fca13f9b1ec0b4a089ed41d0ab392",
+ "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392",
+ "shasum": ""
+ },
+ "require": {
+ "psr/container": "^1.0|^2.0",
+ "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.16",
+ "phpstan/phpstan": "^0.12.52",
+ "phpunit/phpunit": "^8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpBench\\DependencyInjection\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Leech",
+ "email": "daniel@dantleech.com"
+ }
+ ],
+ "description": "Simple, configurable, service container.",
+ "support": {
+ "issues": "https://github.com/phpbench/container/issues",
+ "source": "https://github.com/phpbench/container/tree/2.2.1"
+ },
+ "time": "2022-01-25T10:17:35+00:00"
+ },
+ {
+ "name": "phpbench/dom",
+ "version": "0.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpbench/dom.git",
+ "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpbench/dom/zipball/b013b717832ddbaadf2a40984b04bc66af9a7110",
+ "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "php": "^7.2||^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.18",
+ "phpstan/phpstan": "^0.12.83",
+ "phpunit/phpunit": "^8.0||^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpBench\\Dom\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Leech",
+ "email": "daniel@dantleech.com"
+ }
+ ],
+ "description": "DOM wrapper to simplify working with the PHP DOM implementation",
+ "support": {
+ "issues": "https://github.com/phpbench/dom/issues",
+ "source": "https://github.com/phpbench/dom/tree/0.3.2"
+ },
+ "time": "2021-09-24T15:26:07+00:00"
+ },
+ {
+ "name": "phpbench/phpbench",
+ "version": "1.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpbench/phpbench.git",
+ "reference": "dce145304abbb16c8d9af69c19d96f47e9d0e670"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpbench/phpbench/zipball/dce145304abbb16c8d9af69c19d96f47e9d0e670",
+ "reference": "dce145304abbb16c8d9af69c19d96f47e9d0e670",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.13",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "ext-tokenizer": "*",
+ "php": "^7.3 || ^8.0",
+ "phpbench/container": "^2.1",
+ "phpbench/dom": "~0.3.1",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "seld/jsonlint": "^1.1",
+ "symfony/console": "^4.2 || ^5.0 || ^6.0",
+ "symfony/filesystem": "^4.2 || ^5.0 || ^6.0",
+ "symfony/finder": "^4.2 || ^5.0 || ^6.0",
+ "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0",
+ "symfony/process": "^4.2 || ^5.0 || ^6.0",
+ "webmozart/glob": "^4.6"
+ },
+ "require-dev": {
+ "dantleech/invoke": "^2.0",
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "jangregor/phpstan-prophecy": "^1.0",
+ "phpspec/prophecy": "^1.12",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpunit/phpunit": "^8.5.8 || ^9.0",
+ "symfony/error-handler": "^5.2 || ^6.0",
+ "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0"
+ },
+ "suggest": {
+ "ext-xdebug": "For Xdebug profiling extension."
+ },
+ "bin": [
+ "bin/phpbench"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "lib/Report/Func/functions.php"
+ ],
+ "psr-4": {
+ "PhpBench\\": "lib/",
+ "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Leech",
+ "email": "daniel@dantleech.com"
+ }
+ ],
+ "description": "PHP Benchmarking Framework",
+ "support": {
+ "issues": "https://github.com/phpbench/phpbench/issues",
+ "source": "https://github.com/phpbench/phpbench/tree/1.2.7"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/dantleech",
+ "type": "github"
+ }
+ ],
+ "time": "2022-10-15T09:57:51+00:00"
+ },
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.3.0",
@@ -1757,26 +2143,31 @@
"time": "2022-06-19T12:14:25+00:00"
},
{
- "name": "psr/container",
- "version": "1.1.2",
+ "name": "psr/cache",
+ "version": "1.0.1",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/container.git",
- "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
+ "url": "https://github.com/php-fig/cache.git",
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
- "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
+ "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
"shasum": ""
},
"require": {
- "php": ">=7.4.0"
+ "php": ">=5.3.0"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
- "Psr\\Container\\": "src/"
+ "Psr\\Cache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1786,14 +2177,58 @@
"authors": [
{
"name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
+ "homepage": "http://www.php-fig.org/"
}
],
- "description": "Common Container Interface (PHP FIG PSR-11)",
- "homepage": "https://github.com/php-fig/container",
+ "description": "Common interface for caching libraries",
"keywords": [
- "PSR-11",
- "container",
+ "cache",
+ "psr",
+ "psr-6"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/cache/tree/master"
+ },
+ "time": "2016-08-06T20:24:11+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
"container-interface",
"container-interop",
"psr"
@@ -2878,6 +3313,70 @@
],
"time": "2020-09-28T06:39:44+00:00"
},
+ {
+ "name": "seld/jsonlint",
+ "version": "1.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/jsonlint.git",
+ "reference": "4211420d25eba80712bff236a98960ef68b866b7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7",
+ "reference": "4211420d25eba80712bff236a98960ef68b866b7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.5",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13"
+ },
+ "bin": [
+ "bin/jsonlint"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Seld\\JsonLint\\": "src/Seld/JsonLint/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "JSON Linter",
+ "keywords": [
+ "json",
+ "linter",
+ "parser",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/Seldaek/jsonlint/issues",
+ "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-04-01T13:37:23+00:00"
+ },
{
"name": "symfony/console",
"version": "v5.4.11",
@@ -3044,6 +3543,202 @@
],
"time": "2022-01-02T09:53:40+00:00"
},
+ {
+ "name": "symfony/filesystem",
+ "version": "v5.4.13",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "ac09569844a9109a5966b9438fc29113ce77cf51"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51",
+ "reference": "ac09569844a9109a5966b9438fc29113ce77cf51",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v5.4.13"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-09-21T19:53:16+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v5.4.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c",
+ "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v5.4.11"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-07-29T07:37:50+00:00"
+ },
+ {
+ "name": "symfony/options-resolver",
+ "version": "v5.4.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/options-resolver.git",
+ "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/54f14e36aa73cb8f7261d7686691fd4d75ea2690",
+ "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/polyfill-php73": "~1.0",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\OptionsResolver\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an improved replacement for the array_replace PHP function",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "config",
+ "configuration",
+ "options"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/options-resolver/tree/v5.4.11"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-07-20T13:00:38+00:00"
+ },
{
"name": "symfony/polyfill-ctype",
"version": "v1.26.0",
@@ -3536,6 +4231,68 @@
],
"time": "2022-05-10T07:21:04+00:00"
},
+ {
+ "name": "symfony/process",
+ "version": "v5.4.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1",
+ "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v5.4.11"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-06-27T16:58:25+00:00"
+ },
{
"name": "symfony/service-contracts",
"version": "v2.5.2",
@@ -3920,6 +4677,55 @@
},
"time": "2022-06-03T18:03:27+00:00"
},
+ {
+ "name": "webmozart/glob",
+ "version": "4.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/glob.git",
+ "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/glob/zipball/3c17f7dec3d9d0e87b575026011f2e75a56ed655",
+ "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3 || ^8.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5",
+ "symfony/filesystem": "^5.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Glob\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "A PHP implementation of Ant's glob.",
+ "support": {
+ "issues": "https://github.com/webmozarts/glob/issues",
+ "source": "https://github.com/webmozarts/glob/tree/4.6.0"
+ },
+ "time": "2022-05-24T19:45:58+00:00"
+ },
{
"name": "webmozart/path-util",
"version": "2.3.0",
diff --git a/phive.xml b/phive.xml
deleted file mode 100644
index 5be7457..0000000
--- a/phive.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/phpbench.json b/phpbench.json
index 0b8d58b..0e5750e 100644
--- a/phpbench.json
+++ b/phpbench.json
@@ -1,4 +1,6 @@
{
- "bootstrap": "vendor/autoload.php",
- "path": "tests/benchmark"
+ "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json",
+ "runner.bootstrap": "vendor/autoload.php",
+ "runner.path": "tests/benchmark",
+ "runner.file_pattern": "*Bench.php"
}
diff --git a/src/PseudoTypes/ArrayShape.php b/src/PseudoTypes/ArrayShape.php
new file mode 100644
index 0000000..48e66f0
--- /dev/null
+++ b/src/PseudoTypes/ArrayShape.php
@@ -0,0 +1,44 @@
+items = $items;
+ }
+
+ public function underlyingType(): Type
+ {
+ return new Array_(new Mixed_(), new ArrayKey());
+ }
+
+ public function __toString(): string
+ {
+ return 'array{' . implode(', ', $this->items) . '}';
+ }
+}
diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php
new file mode 100644
index 0000000..56ab5dc
--- /dev/null
+++ b/src/PseudoTypes/ArrayShapeItem.php
@@ -0,0 +1,62 @@
+key = $key;
+ $this->value = $value ?? new Mixed_();
+ $this->optional = $optional;
+ }
+
+ public function getKey(): ?string
+ {
+ return $this->key;
+ }
+
+ public function getValue(): Type
+ {
+ return $this->value;
+ }
+
+ public function isOptional(): bool
+ {
+ return $this->optional;
+ }
+
+ public function __toString(): string
+ {
+ if ($this->key !== null) {
+ return sprintf(
+ '%s%s: %s',
+ $this->key,
+ $this->optional ? '?' : '',
+ (string) $this->value
+ );
+ }
+
+ return (string) $this->value;
+ }
+}
diff --git a/src/PseudoTypes/ConstExpression.php b/src/PseudoTypes/ConstExpression.php
new file mode 100644
index 0000000..c2c42bc
--- /dev/null
+++ b/src/PseudoTypes/ConstExpression.php
@@ -0,0 +1,54 @@
+owner = $owner;
+ $this->expression = $expression;
+ }
+
+ public function getOwner(): Fqsen
+ {
+ return $this->owner;
+ }
+
+ public function getExpression(): string
+ {
+ return $this->expression;
+ }
+
+ public function underlyingType(): Type
+ {
+ return new Mixed_();
+ }
+
+ public function __toString(): string
+ {
+ return sprintf('%s::%s', (string) $this->owner, $this->expression);
+ }
+}
diff --git a/src/PseudoTypes/FloatValue.php b/src/PseudoTypes/FloatValue.php
new file mode 100644
index 0000000..778374f
--- /dev/null
+++ b/src/PseudoTypes/FloatValue.php
@@ -0,0 +1,44 @@
+value = $value;
+ }
+
+ public function getValue(): float
+ {
+ return $this->value;
+ }
+
+ public function underlyingType(): Type
+ {
+ return new Float_();
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->value;
+ }
+}
diff --git a/src/PseudoTypes/IntegerValue.php b/src/PseudoTypes/IntegerValue.php
new file mode 100644
index 0000000..e2f3463
--- /dev/null
+++ b/src/PseudoTypes/IntegerValue.php
@@ -0,0 +1,44 @@
+value = $value;
+ }
+
+ public function getValue(): int
+ {
+ return $this->value;
+ }
+
+ public function underlyingType(): Type
+ {
+ return new Integer();
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->value;
+ }
+}
diff --git a/src/PseudoTypes/StringValue.php b/src/PseudoTypes/StringValue.php
new file mode 100644
index 0000000..c22eff0
--- /dev/null
+++ b/src/PseudoTypes/StringValue.php
@@ -0,0 +1,46 @@
+value = $value;
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ public function underlyingType(): Type
+ {
+ return new Float_();
+ }
+
+ public function __toString(): string
+ {
+ return sprintf('"%s"', $this->value);
+ }
+}
diff --git a/src/TypeResolver.php b/src/TypeResolver.php
index e5695b8..6532655 100644
--- a/src/TypeResolver.php
+++ b/src/TypeResolver.php
@@ -13,12 +13,16 @@
namespace phpDocumentor\Reflection;
-use ArrayIterator;
use InvalidArgumentException;
+use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
+use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
use phpDocumentor\Reflection\PseudoTypes\CallableString;
+use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
use phpDocumentor\Reflection\PseudoTypes\False_;
+use phpDocumentor\Reflection\PseudoTypes\FloatValue;
use phpDocumentor\Reflection\PseudoTypes\HtmlEscapedString;
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
+use phpDocumentor\Reflection\PseudoTypes\IntegerValue;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\PseudoTypes\LiteralString;
use phpDocumentor\Reflection\PseudoTypes\LowercaseString;
@@ -28,12 +32,15 @@
use phpDocumentor\Reflection\PseudoTypes\Numeric_;
use phpDocumentor\Reflection\PseudoTypes\NumericString;
use phpDocumentor\Reflection\PseudoTypes\PositiveInteger;
+use phpDocumentor\Reflection\PseudoTypes\StringValue;
use phpDocumentor\Reflection\PseudoTypes\TraitString;
use phpDocumentor\Reflection\PseudoTypes\True_;
+use phpDocumentor\Reflection\Types\AggregatedType;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\ArrayKey;
use phpDocumentor\Reflection\Types\Boolean;
use phpDocumentor\Reflection\Types\Callable_;
+use phpDocumentor\Reflection\Types\CallableParameter;
use phpDocumentor\Reflection\Types\ClassString;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
@@ -57,46 +64,51 @@
use phpDocumentor\Reflection\Types\String_;
use phpDocumentor\Reflection\Types\This;
use phpDocumentor\Reflection\Types\Void_;
+use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
+use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
+use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
+use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
+use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
+use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
+use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
+use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode;
+use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\TypeNode;
+use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
+use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\Parser\ConstExprParser;
+use PHPStan\PhpDocParser\Parser\ParserException;
+use PHPStan\PhpDocParser\Parser\TokenIterator;
+use PHPStan\PhpDocParser\Parser\TypeParser;
use RuntimeException;
+use function array_filter;
use function array_key_exists;
-use function array_key_last;
-use function array_pop;
-use function array_values;
+use function array_map;
+use function array_reverse;
use function class_exists;
use function class_implements;
-use function count;
-use function current;
+use function get_class;
use function in_array;
-use function is_numeric;
-use function preg_split;
+use function sprintf;
use function strpos;
use function strtolower;
use function trim;
-use const PREG_SPLIT_DELIM_CAPTURE;
-use const PREG_SPLIT_NO_EMPTY;
-
final class TypeResolver
{
- /** @var string Definition of the ARRAY operator for types */
- private const OPERATOR_ARRAY = '[]';
-
/** @var string Definition of the NAMESPACE operator in PHP */
private const OPERATOR_NAMESPACE = '\\';
- /** @var int the iterator parser is inside a compound context */
- private const PARSER_IN_COMPOUND = 0;
-
- /** @var int the iterator parser is inside a nullable expression context */
- private const PARSER_IN_NULLABLE = 1;
-
- /** @var int the iterator parser is inside an array expression context */
- private const PARSER_IN_ARRAY_EXPRESSION = 2;
-
- /** @var int the iterator parser is inside a collection expression context */
- private const PARSER_IN_COLLECTION_EXPRESSION = 3;
-
/**
* @var array List of recognized keywords and unto which Value Object they map
* @psalm-var array>
@@ -146,6 +158,10 @@ final class TypeResolver
/** @psalm-readonly */
private FqsenResolver $fqsenResolver;
+ /** @psalm-readonly */
+ private TypeParser $typeParser;
+ /** @psalm-readonly */
+ private Lexer $lexer;
/**
* Initializes this TypeResolver with the means to create and resolve Fqsen objects.
@@ -153,6 +169,8 @@ final class TypeResolver
public function __construct(?FqsenResolver $fqsenResolver = null)
{
$this->fqsenResolver = $fqsenResolver ?: new FqsenResolver();
+ $this->typeParser = new TypeParser(new ConstExprParser());
+ $this->lexer = new Lexer();
}
/**
@@ -165,9 +183,9 @@ public function __construct(?FqsenResolver $fqsenResolver = null)
* This method only works as expected if the namespace and aliases are set;
* no dynamic reflection is being performed here.
*
+ * @uses Context::getNamespace() to determine with what to prefix the type name.
* @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
* replaced with another namespace.
- * @uses Context::getNamespace() to determine with what to prefix the type name.
*
* @param string $type The relative or absolute type.
*/
@@ -182,178 +200,216 @@ public function resolve(string $type, ?Context $context = null): Type
$context = new Context('');
}
- // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names
- $tokens = preg_split(
- '/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/',
- $type,
- -1,
- PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
- );
+ try {
+ $tokens = $this->lexer->tokenize($type);
+ $tokenIterator = new TokenIterator($tokens);
+ $ast = $this->typeParser->parse($tokenIterator);
+ } catch (ParserException $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
- if ($tokens === false) {
- throw new InvalidArgumentException('Unable to split the type string "' . $type . '" into tokens');
+ return $this->createType($ast, $context);
+ }
+
+ public function createType(?TypeNode $type, Context $context): Type
+ {
+ if ($type === null) {
+ return new Mixed_();
}
- /** @var ArrayIterator $tokenIterator */
- $tokenIterator = new ArrayIterator($tokens);
+ switch (get_class($type)) {
+ case ArrayTypeNode::class:
+ return new Array_(
+ $this->createType($type->type, $context)
+ );
+
+ case ArrayShapeNode::class:
+ return new ArrayShape(
+ ...array_map(
+ fn (ArrayShapeItemNode $item) => new ArrayShapeItem(
+ (string) $item->keyName,
+ $this->createType($item->valueType, $context),
+ $item->optional
+ ),
+ $type->items
+ )
+ );
+
+ case CallableTypeNode::class:
+ return $this->createFromCallable($type, $context);
+
+ case ConstTypeNode::class:
+ return $this->createFromConst($type, $context);
+
+ case GenericTypeNode::class:
+ return $this->createFromGeneric($type, $context);
+
+ case IdentifierTypeNode::class:
+ return $this->resolveSingleType($type->name, $context);
+
+ case IntersectionTypeNode::class:
+ return new Intersection(
+ array_filter(
+ array_map(
+ function (TypeNode $nestedType) use ($context) {
+ $type = $this->createType($nestedType, $context);
+ if ($type instanceof AggregatedType) {
+ return new Expression($type);
+ }
+
+ return $type;
+ },
+ $type->types
+ )
+ )
+ );
+
+ case NullableTypeNode::class:
+ $nestedType = $this->createType($type->type, $context);
+
+ return new Nullable($nestedType);
+
+ case UnionTypeNode::class:
+ return new Compound(
+ array_filter(
+ array_map(
+ function (TypeNode $nestedType) use ($context) {
+ $type = $this->createType($nestedType, $context);
+ if ($type instanceof AggregatedType) {
+ return new Expression($type);
+ }
+
+ return $type;
+ },
+ $type->types
+ )
+ )
+ );
+
+ case ThisTypeNode::class:
+ return new This();
- return $this->parseTypes($tokenIterator, $context, self::PARSER_IN_COMPOUND);
+ case ConditionalTypeNode::class:
+ case ConditionalTypeForParameterNode::class:
+ case OffsetAccessTypeNode::class:
+ default:
+ return new Mixed_();
+ }
}
- /**
- * Analyse each tokens and creates types
- *
- * @param ArrayIterator $tokens the iterator on tokens
- * @param int $parserContext on of self::PARSER_* constants, indicating
- * the context where we are in the parsing
- */
- private function parseTypes(ArrayIterator $tokens, Context $context, int $parserContext): Type
+ private function createFromGeneric(GenericTypeNode $type, Context $context): Type
{
- $types = [];
- $token = '';
- $compoundToken = '|';
- while ($tokens->valid()) {
- $token = $tokens->current();
- if ($token === null) {
- throw new RuntimeException(
- 'Unexpected nullable character'
- );
- }
+ switch (strtolower($type->type->name)) {
+ case 'array':
+ return $this->createArray($type->genericTypes, $context);
- if ($token === '|' || $token === '&') {
- if (count($types) === 0) {
+ case 'class-string':
+ $subType = $this->createType($type->genericTypes[0], $context);
+ if (!$subType instanceof Object_ || $subType->getFqsen() === null) {
throw new RuntimeException(
- 'A type is missing before a type separator'
+ $subType . ' is not a class string'
);
}
- if (
- !in_array($parserContext, [
- self::PARSER_IN_COMPOUND,
- self::PARSER_IN_ARRAY_EXPRESSION,
- self::PARSER_IN_COLLECTION_EXPRESSION,
- self::PARSER_IN_NULLABLE,
- ], true)
- ) {
- throw new RuntimeException(
- 'Unexpected type separator'
- );
- }
+ return new ClassString(
+ $subType->getFqsen()
+ );
- $compoundToken = $token;
- $tokens->next();
- } elseif ($token === '?') {
- if (
- !in_array($parserContext, [
- self::PARSER_IN_COMPOUND,
- self::PARSER_IN_ARRAY_EXPRESSION,
- self::PARSER_IN_COLLECTION_EXPRESSION,
- self::PARSER_IN_NULLABLE,
- ], true)
- ) {
+ case 'interface-string':
+ $subType = $this->createType($type->genericTypes[0], $context);
+ if (!$subType instanceof Object_ || $subType->getFqsen() === null) {
throw new RuntimeException(
- 'Unexpected nullable character'
+ $subType . ' is not a class string'
);
}
- $tokens->next();
- $type = $this->parseTypes($tokens, $context, self::PARSER_IN_NULLABLE);
- $types[] = new Nullable($type);
- } elseif ($token === '(') {
- $tokens->next();
- $type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION);
-
- $token = $tokens->current();
- if ($token === null) { // Someone did not properly close their array expression ..
- break;
- }
-
- $tokens->next();
+ return new InterfaceString(
+ $subType->getFqsen()
+ );
- $resolvedType = new Expression($type);
+ case 'list':
+ return new List_(
+ $this->createType($type->genericTypes[0], $context)
+ );
- $types[] = $resolvedType;
- } elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && isset($token[0]) && $token[0] === ')') {
- break;
- } elseif ($token === '<') {
- if (count($types) === 0) {
- throw new RuntimeException(
- 'Unexpected collection operator "<", class name is missing'
- );
+ case 'int':
+ if (isset($type->genericTypes[1]) === false) {
+ throw new RuntimeException('int has not the correct format');
}
- $classType = array_pop($types);
- if ($classType !== null) {
- if ((string) $classType === 'class-string') {
- $types[] = $this->resolveClassString($tokens, $context);
- } elseif ((string) $classType === 'int') {
- $types[] = $this->resolveIntRange($tokens);
- } elseif ((string) $classType === 'interface-string') {
- $types[] = $this->resolveInterfaceString($tokens, $context);
- } else {
- $types[] = $this->resolveCollection($tokens, $classType, $context);
- }
- }
+ return new IntegerRange(
+ (string) $type->genericTypes[0],
+ (string) $type->genericTypes[1],
+ );
- $tokens->next();
- } elseif (
- $parserContext === self::PARSER_IN_COLLECTION_EXPRESSION
- && ($token === '>' || trim($token) === ',')
- ) {
- break;
- } elseif ($token === self::OPERATOR_ARRAY) {
- $last = array_key_last($types);
- if ($last === null) {
- throw new InvalidArgumentException('Unexpected array operator');
- }
+ case 'iterable':
+ return new Iterable_(
+ ...array_reverse(
+ array_map(
+ fn (TypeNode $genericType) => $this->createType($genericType, $context),
+ $type->genericTypes
+ )
+ )
+ );
- $lastItem = $types[$last];
- if ($lastItem instanceof Expression) {
- $lastItem = $lastItem->getValueType();
+ default:
+ $collectionType = $this->createType($type->type, $context);
+ if ($collectionType instanceof Object_ === false) {
+ throw new RuntimeException(sprintf('%s is not a collection', (string) $collectionType));
}
- $types[$last] = new Array_($lastItem);
-
- $tokens->next();
- } else {
- $types[] = $this->resolveSingleType($token, $context);
- $tokens->next();
- }
+ return new Collection(
+ $collectionType->getFqsen(),
+ ...array_reverse(
+ array_map(
+ fn (TypeNode $genericType) => $this->createType($genericType, $context),
+ $type->genericTypes
+ )
+ )
+ );
}
+ }
- if ($token === '|' || $token === '&') {
- throw new RuntimeException(
- 'A type is missing after a type separator'
- );
- }
+ private function createFromCallable(CallableTypeNode $type, Context $context): Callable_
+ {
+ return new Callable_(
+ array_map(
+ function (CallableTypeParameterNode $param) use ($context) {
+ return new CallableParameter(
+ $this->createType($param->type, $context),
+ $param->parameterName !== '' ? trim($param->parameterName, '$') : null,
+ $param->isReference,
+ $param->isVariadic,
+ $param->isOptional
+ );
+ },
+ $type->parameters
+ ),
+ $this->createType($type->returnType, $context),
+ );
+ }
- if (count($types) === 0) {
- if ($parserContext === self::PARSER_IN_NULLABLE) {
- throw new RuntimeException(
- 'A type is missing after a nullable character'
- );
- }
+ private function createFromConst(ConstTypeNode $type, Context $context): Type
+ {
+ switch (true) {
+ case $type->constExpr instanceof ConstExprIntegerNode:
+ return new IntegerValue((int) $type->constExpr->value);
- if ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION) {
- throw new RuntimeException(
- 'A type is missing in an array expression'
- );
- }
+ case $type->constExpr instanceof ConstExprFloatNode:
+ return new FloatValue((float) $type->constExpr->value);
- if ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION) {
- throw new RuntimeException(
- 'A type is missing in a collection expression'
+ case $type->constExpr instanceof ConstExprStringNode:
+ return new StringValue($type->constExpr->value);
+
+ case $type->constExpr instanceof ConstFetchNode:
+ return new ConstExpression(
+ $this->fqsenResolver->resolve($type->constExpr->className, $context),
+ $type->constExpr->name
);
- }
- } elseif (count($types) === 1) {
- return current($types);
- }
- if ($compoundToken === '|') {
- return new Compound(array_values($types));
+ default:
+ throw new RuntimeException(sprintf('Unsupported constant type %s', get_class($type)));
}
-
- return new Intersection(array_values($types));
}
/**
@@ -475,244 +531,24 @@ private function resolveTypedObject(string $type, ?Context $context = null): Obj
return new Object_($this->fqsenResolver->resolve($type, $context));
}
- /**
- * Resolves class string
- *
- * @param ArrayIterator $tokens
- */
- private function resolveClassString(ArrayIterator $tokens, Context $context): Type
+ /** @param TypeNode[] $typeNodes */
+ private function createArray(array $typeNodes, Context $context): Array_
{
- $tokens->next();
-
- $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
-
- if (!$classType instanceof Object_ || $classType->getFqsen() === null) {
- throw new RuntimeException(
- $classType . ' is not a class string'
- );
- }
-
- $token = $tokens->current();
- if ($token !== '>') {
- if (empty($token)) {
- throw new RuntimeException(
- 'class-string: ">" is missing'
- );
- }
-
- throw new RuntimeException(
- 'Unexpected character "' . $token . '", ">" is missing'
- );
- }
-
- return new ClassString($classType->getFqsen());
- }
-
- /**
- * Resolves integer ranges
- *
- * @param ArrayIterator $tokens
- */
- private function resolveIntRange(ArrayIterator $tokens): Type
- {
- $tokens->next();
-
- $token = '';
- $minValue = null;
- $maxValue = null;
- $commaFound = false;
- $tokenCounter = 0;
- while ($tokens->valid()) {
- $tokenCounter++;
- $token = $tokens->current();
- if ($token === null) {
- throw new RuntimeException(
- 'Unexpected nullable character'
- );
- }
-
- $token = trim($token);
-
- if ($token === '>') {
- break;
- }
-
- if ($token === ',') {
- $commaFound = true;
- }
-
- if ($commaFound === false && $minValue === null) {
- if (is_numeric($token) || $token === 'max' || $token === 'min') {
- $minValue = $token;
- }
- }
-
- if ($commaFound === true && $maxValue === null) {
- if (is_numeric($token) || $token === 'max' || $token === 'min') {
- $maxValue = $token;
- }
- }
-
- $tokens->next();
- }
-
- if ($token !== '>') {
- if (empty($token)) {
- throw new RuntimeException(
- 'interface-string: ">" is missing'
- );
- }
-
- throw new RuntimeException(
- 'Unexpected character "' . $token . '", ">" is missing'
- );
- }
-
- if ($minValue === null || $maxValue === null || $tokenCounter > 4) {
- throw new RuntimeException(
- 'int has not the correct format'
- );
- }
-
- return new IntegerRange($minValue, $maxValue);
- }
-
- /**
- * Resolves class string
- *
- * @param ArrayIterator $tokens
- */
- private function resolveInterfaceString(ArrayIterator $tokens, Context $context): Type
- {
- $tokens->next();
-
- $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
-
- if (!$classType instanceof Object_ || $classType->getFqsen() === null) {
- throw new RuntimeException(
- $classType . ' is not a interface string'
- );
- }
-
- $token = $tokens->current();
- if ($token !== '>') {
- if (empty($token)) {
- throw new RuntimeException(
- 'interface-string: ">" is missing'
- );
- }
-
- throw new RuntimeException(
- 'Unexpected character "' . $token . '", ">" is missing'
- );
- }
-
- return new InterfaceString($classType->getFqsen());
- }
-
- /**
- * Resolves the collection values and keys
- *
- * @param ArrayIterator $tokens
- *
- * @return Array_|Iterable_|Collection
- */
- private function resolveCollection(ArrayIterator $tokens, Type $classType, Context $context): Type
- {
- $isArray = ((string) $classType === 'array');
- $isIterable = ((string) $classType === 'iterable');
- $isList = ((string) $classType === 'list');
-
- // allow only "array", "iterable" or class name before "<"
- if (
- !$isArray && !$isIterable && !$isList
- && (!$classType instanceof Object_ || $classType->getFqsen() === null)
- ) {
- throw new RuntimeException(
- $classType . ' is not a collection'
- );
- }
-
- $tokens->next();
-
- $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
- $keyType = null;
-
- $token = $tokens->current();
- if ($token !== null && trim($token) === ',' && !$isList) {
- // if we have a comma, then we just parsed the key type, not the value type
- $keyType = $valueType;
- if ($isArray) {
- // check the key type for an "array" collection. We allow only
- // strings or integers.
- if (
- !$keyType instanceof ArrayKey &&
- !$keyType instanceof String_ &&
- !$keyType instanceof Integer &&
- !$keyType instanceof Compound
- ) {
- throw new RuntimeException(
- 'An array can have only integers or strings as keys'
- );
- }
-
- if ($keyType instanceof Compound) {
- foreach ($keyType->getIterator() as $item) {
- if (
- !$item instanceof ArrayKey &&
- !$item instanceof String_ &&
- !$item instanceof Integer
- ) {
- throw new RuntimeException(
- 'An array can have only integers or strings as keys'
- );
- }
- }
- }
- }
-
- $tokens->next();
- // now let's parse the value type
- $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
- }
-
- $token = $tokens->current();
- if ($token !== '>') {
- if (empty($token)) {
- throw new RuntimeException(
- 'Collection: ">" is missing'
- );
- }
-
- throw new RuntimeException(
- 'Unexpected character "' . $token . '", ">" is missing'
- );
- }
-
- if ($isArray) {
- return new Array_($valueType, $keyType);
- }
-
- if ($isIterable) {
- return new Iterable_($valueType, $keyType);
- }
+ $types = array_reverse(
+ array_map(
+ fn (TypeNode $node) => $this->createType($node, $context),
+ $typeNodes
+ )
+ );
- if ($isList) {
- return new List_($valueType);
+ if (isset($types[1]) === false) {
+ return new Array_(...$types);
}
- if ($classType instanceof Object_) {
- return $this->makeCollectionFromObject($classType, $valueType, $keyType);
+ if ($types[1] instanceof String_ || $types[1] instanceof Integer || $types[1] instanceof ArrayKey) {
+ return new Array_(...$types);
}
- throw new RuntimeException('Invalid $classType provided');
- }
-
- /**
- * @psalm-pure
- */
- private function makeCollectionFromObject(Object_ $object, Type $valueType, ?Type $keyType = null): Collection
- {
- return new Collection($object->getFqsen(), $valueType, $keyType);
+ throw new RuntimeException('An array can have only integers or strings as keys');
}
}
diff --git a/src/Types/AggregatedType.php b/src/Types/AggregatedType.php
index 257ed51..6f9e879 100644
--- a/src/Types/AggregatedType.php
+++ b/src/Types/AggregatedType.php
@@ -106,7 +106,7 @@ public function getIterator(): ArrayIterator
*/
private function add(Type $type): void
{
- if ($type instanceof self) {
+ if ($type instanceof static) {
foreach ($type->getIterator() as $subType) {
$this->add($subType);
}
diff --git a/src/Types/CallableParameter.php b/src/Types/CallableParameter.php
new file mode 100644
index 0000000..1a36d23
--- /dev/null
+++ b/src/Types/CallableParameter.php
@@ -0,0 +1,72 @@
+type = $type;
+ $this->isReference = $isReference;
+ $this->isVariadic = $isVariadic;
+ $this->isOptional = $isOptional;
+ $this->name = $name;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function getType(): Type
+ {
+ return $this->type;
+ }
+
+ public function isReference(): bool
+ {
+ return $this->isReference;
+ }
+
+ public function isVariadic(): bool
+ {
+ return $this->isVariadic;
+ }
+
+ public function isOptional(): bool
+ {
+ return $this->isOptional;
+ }
+}
diff --git a/src/Types/Callable_.php b/src/Types/Callable_.php
index 4e67aa4..20c5726 100644
--- a/src/Types/Callable_.php
+++ b/src/Types/Callable_.php
@@ -22,6 +22,30 @@
*/
final class Callable_ implements Type
{
+ private ?Type $returnType;
+ /** @var CallableParameter[] */
+ private array $parameters;
+
+ /**
+ * @param CallableParameter[] $parameters
+ */
+ public function __construct(array $parameters = [], ?Type $returnType = null)
+ {
+ $this->parameters = $parameters;
+ $this->returnType = $returnType;
+ }
+
+ /** @return CallableParameter[] */
+ public function getParameters(): array
+ {
+ return $this->parameters;
+ }
+
+ public function getReturnType(): ?Type
+ {
+ return $this->returnType;
+ }
+
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
diff --git a/tests/benchmark/ContextFactoryBench.php b/tests/benchmark/ContextFactoryBench.php
index bc28065..b19cb8e 100644
--- a/tests/benchmark/ContextFactoryBench.php
+++ b/tests/benchmark/ContextFactoryBench.php
@@ -4,6 +4,7 @@
namespace benchmark;
+use PhpBench\Benchmark\Metadata\Annotations\Warmup;
use phpDocumentor\Reflection\Types\ContextFactory;
/**
@@ -20,13 +21,6 @@ public function setup()
/**
* @Warmup(1)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 120Mb", "title"="memory peak"},
- * "main.wall_time < 3S"
- * }
- * )
*/
public function benchCreateContextForNamespace()
{
diff --git a/tests/benchmark/TypeResolverBench.php b/tests/benchmark/TypeResolverBench.php
index dc91903..5f823d5 100644
--- a/tests/benchmark/TypeResolverBench.php
+++ b/tests/benchmark/TypeResolverBench.php
@@ -4,6 +4,8 @@
namespace benchmark;
+use PhpBench\Benchmark\Metadata\Annotations\Revs;
+use PhpBench\Benchmark\Metadata\Annotations\Warmup;
use phpDocumentor\Reflection\TypeResolver;
/**
@@ -21,13 +23,6 @@ public function setup()
/**
* @Warmup(2)
* @Revs(10000)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 11kb", "title"="memory peak"},
- * "main.wall_time < 300us"
- * }
- * )
*/
public function benchResolveSingleType() : void
{
@@ -37,13 +32,6 @@ public function benchResolveSingleType() : void
/**
* @Warmup(2)
* @Revs(10000)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 11kb", "title"="memory peak"},
- * "main.wall_time < 0.5ms"
- * }
- * )
*/
public function benchResolveCompoundType() : void
{
@@ -53,13 +41,6 @@ public function benchResolveCompoundType() : void
/**
* @Warmup(2)
* @Revs(10000)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 11kb", "title"="memory peak"},
- * "main.wall_time < 300us"
- * }
- * )
*/
public function benchResolveArrayType() : void
{
@@ -69,13 +50,6 @@ public function benchResolveArrayType() : void
/**
* @Warmup(2)
* @Revs(10000)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 11kb", "title"="memory peak"},
- * "main.wall_time < 300us"
- * }
- * )
*/
public function benchResolveCompoundArrayType() : void
{
@@ -85,13 +59,6 @@ public function benchResolveCompoundArrayType() : void
/**
* @Warmup(2)
* @Revs(10000)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 11kb", "title"="memory peak"},
- * "main.wall_time < 1ms"
- * }
- * )
*/
public function benchResolveCompoundArrayWithDefinedTypes() : void
{
diff --git a/tests/benchmark/TypeResolverWithContextBench.php b/tests/benchmark/TypeResolverWithContextBench.php
index 49626e8..ee1163f 100644
--- a/tests/benchmark/TypeResolverWithContextBench.php
+++ b/tests/benchmark/TypeResolverWithContextBench.php
@@ -4,6 +4,7 @@
namespace benchmark;
+use PhpBench\Benchmark\Metadata\Annotations\Warmup;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\ContextFactory;
@@ -32,13 +33,6 @@ public function setup()
/**
* @Warmup(2)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 11kb", "title"="memory peak"},
- * "main.wall_time < 1ms"
- * }
- * )
*/
public function benchResolveCompoundArrayWithDefinedTypes() : void
{
@@ -47,13 +41,6 @@ public function benchResolveCompoundArrayWithDefinedTypes() : void
/**
* @Warmup(2)
- * @Executor(
- * "blackfire",
- * assertions={
- * {"expression"="main.peak_memory < 11kb", "title"="memory peak"},
- * "main.wall_time < 1ms"
- * }
- * )
*/
public function benchArrayOfClass() : void
{
diff --git a/tests/unit/CollectionResolverTest.php b/tests/unit/CollectionResolverTest.php
index eb25b04..3468d94 100644
--- a/tests/unit/CollectionResolverTest.php
+++ b/tests/unit/CollectionResolverTest.php
@@ -13,7 +13,6 @@
namespace phpDocumentor\Reflection;
-use InvalidArgumentException;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Collection;
@@ -169,23 +168,6 @@ public function testResolvingArrayCollectionWithKeyAndWhitespace(): void
$this->assertInstanceOf(Compound::class, $valueType);
}
- /**
- * @uses \phpDocumentor\Reflection\Types\Context
- * @uses \phpDocumentor\Reflection\Types\Compound
- * @uses \phpDocumentor\Reflection\Types\Collection
- * @uses \phpDocumentor\Reflection\Types\String_
- *
- * @covers ::__construct
- * @covers ::resolve
- */
- public function testResolvingArrayCollectionWithKeyAndTooManyWhitespace(): void
- {
- $this->expectException(InvalidArgumentException::class);
- $fixture = new TypeResolver();
-
- $fixture->resolve('array', new Context(''));
- }
-
/**
* @uses \phpDocumentor\Reflection\Types\Context
* @uses \phpDocumentor\Reflection\Types\Compound
@@ -260,7 +242,7 @@ public function testGoodArrayCollectionKey(): void
public function testMissingStartCollection(): void
{
$this->expectException(RuntimeException::class);
- $this->expectExceptionMessage('Unexpected collection operator "<", class name is missing');
+ $this->expectExceptionMessage('Unexpected token "<", expected type at offset 0');
$fixture = new TypeResolver();
$fixture->resolve('', new Context(''));
}
@@ -272,7 +254,7 @@ public function testMissingStartCollection(): void
public function testMissingEndCollection(): void
{
$this->expectException(RuntimeException::class);
- $this->expectExceptionMessage('Collection: ">" is missing');
+ $this->expectExceptionMessage('Unexpected token "", expected \'>\' at offset 25');
$fixture = new TypeResolver();
$fixture->resolve('ArrayObject