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('ArrayObjectexpectException(RuntimeException::class); $this->expectExceptionMessage('int has not the correct format'); @@ -104,7 +104,7 @@ public function testResolvingIntRangeErrorMisingMaxValue(): void public function testResolvingIntRangeErrorMisingMinValue(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('int has not the correct format'); + $this->expectExceptionMessage('Unexpected token ",", expected type at offset 4'); $fixture = new TypeResolver(); $resolvedType = $fixture->resolve('int<,max>', new Context('')); @@ -140,27 +140,9 @@ public function testResolvingIntRangeErrorMisingComma(): void public function testResolvingIntRangeErrorMissingEnd(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unexpected character "max", ">" is missing'); + $this->expectExceptionMessage('Unexpected token "", expected \'>\' at offset 11'); $fixture = new TypeResolver(); $resolvedType = $fixture->resolve('intexpectException(RuntimeException::class); - $this->expectExceptionMessage('int has not the correct format'); - - $fixture = new TypeResolver(); - $resolvedType = $fixture->resolve('int', new Context('')); - } } diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index be168f0..52e3666 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -15,8 +15,12 @@ use InvalidArgumentException; 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; @@ -26,13 +30,16 @@ 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\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; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; @@ -433,6 +440,7 @@ public function testResolvingCompoundTypedArrayTypes(): void */ public function testResolvingNullableCompoundTypes(): void { + $this->markTestSkipped('Invalid type definition'); $fixture = new TypeResolver(); // Note that in PHP types it is illegal to use shorthand nullable @@ -560,12 +568,9 @@ public function testResolvingArrayOfArrayExpressionTypes(): void */ public function testReturnEmptyCompoundOnAnUnclosedArrayExpressionType(): void { + $this->expectException(RuntimeException::class); $fixture = new TypeResolver(); - - $resolvedType = $fixture->resolve('(string|\stdClass', new Context('')); - - $this->assertInstanceOf(Compound::class, $resolvedType); - $this->assertSame('', (string) $resolvedType); + $fixture->resolve('(string|\stdClass', new Context('')); } /** @@ -752,7 +757,7 @@ public function testExceptionIsThrownIfTypeIsEmpty(): void */ public function testInvalidArrayOperator(): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(RuntimeException::class); $fixture = new TypeResolver(); $fixture->resolve('[]', new Context('')); } @@ -863,4 +868,237 @@ public function testArrayKeyValueSpecification(): void $this->assertEquals(new Array_(new Array_(new String_(), new Integer()), new String_()), $type); } + + /** + * @covers ::__construct + * @covers ::resolve + * @dataProvider typeProvider + * @dataProvider genericsProvider + * @dataProvider callableProvider + * @dataProvider constExpressions + * @testdox create type from $type + */ + public function testTypeBuilding(string $type, Type $expected): void + { + $fixture = new TypeResolver(); + $actual = $fixture->resolve($type, new Context('phpDocumentor')); + + self::assertEquals($expected, $actual); + } + + /** + * @return array + */ + public function typeProvider(): array + { + return [ + [ + 'string', + new String_(), + ], + [ + '( string )', + new String_(), + ], + [ + '\\Foo\Bar\\Baz', + new Object_(new Fqsen('\\Foo\Bar\\Baz')), + ], + [ + 'string|int', + new Compound( + [ + new String_(), + new Integer(), + ] + ), + ], + [ + 'string&int', + new Intersection( + [ + new String_(), + new Integer(), + ] + ), + ], + [ + 'string & (int | float)', + new Intersection( + [ + new String_(), + new Expression( + new Compound( + [ + new Integer(), + new Float_(), + ] + ) + ), + ] + ), + ], + [ + 'string[]', + new Array_( + new String_() + ), + ], + [ + '$this', + new This(), + ], + [ + '?int', + new Nullable( + new Integer() + ), + ], + [ + 'self', + new Self_(), + ], + ]; + } + + /** + * @return array + */ + public function genericsProvider(): array + { + return [ + [ + 'array', + new Array_( + new Object_(new Fqsen('\\phpDocumentor\\Foo\\Bar')), + new Integer() + ), + ], + [ + 'Collection[]', + new Array_( + new Collection( + new Fqsen('\\phpDocumentor\\Collection'), + new Integer(), + new ArrayKey() + ) + ), + ], + [ + 'class-string', + new ClassString(null), + ], + [ + 'class-string', + new ClassString(new Fqsen('\\phpDocumentor\\Foo')), + ], + [ + 'interface-string', + new InterfaceString(new Fqsen('\\phpDocumentor\\Foo')), + ], + [ + 'List', + new List_(new Object_(new Fqsen('\\phpDocumentor\\Foo'))), + ], + [ + 'int<1, 100>', + new IntegerRange('1', '100'), + ], + ]; + } + + /** + * @return array + */ + public function callableProvider(): array + { + return [ + [ + 'callable', + new Callable_(), + ], + [ + 'callable()', + new Callable_(), + ], + [ + 'callable(): Foo', + new Callable_([], new Object_(new Fqsen('\\phpDocumentor\\Foo'))), + ], + [ + 'callable(): (Foo&Bar)', + new Callable_( + [], + new Intersection( + [ + new Object_(new Fqsen('\\phpDocumentor\\Foo')), + new Object_(new Fqsen('\\phpDocumentor\\Bar')), + ] + ) + ), + ], + [ + 'callable(A&...$a=, B&...=, C): Foo', + new Callable_( + [ + new CallableParameter( + new Object_(new Fqsen('\\phpDocumentor\\A')), + 'a', + true, + true, + true + ), + new CallableParameter( + new Object_(new Fqsen('\\phpDocumentor\\B')), + null, + true, + true, + true + ), + new CallableParameter( + new Object_(new Fqsen('\\phpDocumentor\\C')), + null, + false, + false, + false + ), + ], + new Object_(new Fqsen('\\phpDocumentor\\Foo')) + ), + ], + ]; + } + + /** + * @return array + */ + public function constExpressions(): array + { + return [ + [ + '123', + new IntegerValue(123), + ], + [ + 'true', + new True_(), + ], + [ + '123.2', + new FloatValue(123.2), + ], + [ + '"bar"', + new StringValue('bar'), + ], + [ + 'Foo::FOO_CONSTANT', + new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_CONSTANT'), + ], + [ + 'Foo::FOO_*', + new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_*'), + ], + ]; + } } diff --git a/tests/unit/Types/CompoundTest.php b/tests/unit/Types/CompoundTest.php index d5e08af..494d423 100644 --- a/tests/unit/Types/CompoundTest.php +++ b/tests/unit/Types/CompoundTest.php @@ -143,4 +143,34 @@ public function testCompoundCanBeIterated(): void $this->assertSame($types[$index], $type); } } + + /** + * @uses \phpDocumentor\Reflection\Types\Integer + * @uses \phpDocumentor\Reflection\Types\Boolean + * + * @covers ::__construct + * @covers ::__toString + */ + public function testCompoundIsMergedWithCompound(): void + { + $compound = new Compound([new Integer(), new Compound([new Integer(), new Boolean()])]); + + $this->assertCount(2, iterator_to_array($compound)); + $this->assertSame('int|bool', (string) $compound); + } + + /** + * @uses \phpDocumentor\Reflection\Types\Integer + * @uses \phpDocumentor\Reflection\Types\Boolean + * + * @covers ::__construct + * @covers ::__toString + */ + public function testCompoundIsMergedOnMergedWithIntersection(): void + { + $compound = new Compound([new Integer(), new Intersection([new Integer(), new Boolean()])]); + + $this->assertCount(2, iterator_to_array($compound)); + $this->assertSame('int|int&bool', (string) $compound); + } } diff --git a/tests/unit/Types/ContextFactoryTest.php b/tests/unit/Types/ContextFactoryTest.php index 376fc8c..28a3d5d 100644 --- a/tests/unit/Types/ContextFactoryTest.php +++ b/tests/unit/Types/ContextFactoryTest.php @@ -180,7 +180,7 @@ public function assertNamespaceAliasesFrom(Context $context) 'Assert' => Assert::class, 'e' => e::class, ReflectionClass::class => ReflectionClass::class, - 'stdClass' => 'stdClass', + \stdClass::class => \stdClass::class, ]; $actual = $context->getNamespaceAliases();