Composer Workspace global packages

This commit is contained in:
Clemens Schwaighofer
2023-08-02 14:52:33 +09:00
parent c383a7b7b7
commit 1fc144e178
239 changed files with 5659 additions and 2712 deletions

178
composer.lock generated
View File

@@ -481,25 +481,29 @@
}, },
{ {
"name": "doctrine/deprecations", "name": "doctrine/deprecations",
"version": "v1.0.0", "version": "v1.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/deprecations.git", "url": "https://github.com/doctrine/deprecations.git",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.1|^8.0" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^9", "doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5|^8.5|^9.5", "phpstan/phpstan": "1.4.10 || 1.10.15",
"psr/log": "^1|^2|^3" "phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
}, },
"suggest": { "suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation" "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
@@ -518,9 +522,9 @@
"homepage": "https://www.doctrine-project.org/", "homepage": "https://www.doctrine-project.org/",
"support": { "support": {
"issues": "https://github.com/doctrine/deprecations/issues", "issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0" "source": "https://github.com/doctrine/deprecations/tree/v1.1.1"
}, },
"time": "2022-05-02T15:47:09+00:00" "time": "2023-06-03T09:27:29+00:00"
}, },
{ {
"name": "felixfbecker/advanced-json-rpc", "name": "felixfbecker/advanced-json-rpc",
@@ -782,16 +786,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v4.15.5", "version": "v4.16.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" "reference": "19526a33fb561ef417e822e85f08a00db4059c17"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", "reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -832,9 +836,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
}, },
"time": "2023-05-19T20:20:00+00:00" "time": "2023-06-25T14:52:30+00:00"
}, },
{ {
"name": "phan/phan", "name": "phan/phan",
@@ -1027,16 +1031,16 @@
}, },
{ {
"name": "phpdocumentor/type-resolver", "name": "phpdocumentor/type-resolver",
"version": "1.7.1", "version": "1.7.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git", "url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714" "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714", "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714", "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1079,9 +1083,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": { "support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues", "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1" "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2"
}, },
"time": "2023-03-27T19:02:04+00:00" "time": "2023-05-30T18:13:47+00:00"
}, },
{ {
"name": "phpstan/extension-installer", "name": "phpstan/extension-installer",
@@ -1129,22 +1133,23 @@
}, },
{ {
"name": "phpstan/phpdoc-parser", "name": "phpstan/phpdoc-parser",
"version": "1.21.0", "version": "1.23.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git", "url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c" "reference": "a2b24135c35852b348894320d47b3902a94bc494"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6df62b08faef4f899772bc7c3bbabb93d2b7a21c", "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a2b24135c35852b348894320d47b3902a94bc494",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c", "reference": "a2b24135c35852b348894320d47b3902a94bc494",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0" "php": "^7.2 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/annotations": "^2.0",
"nikic/php-parser": "^4.15", "nikic/php-parser": "^4.15",
"php-parallel-lint/php-parallel-lint": "^1.2", "php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0", "phpstan/extension-installer": "^1.0",
@@ -1169,22 +1174,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types", "description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": { "support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues", "issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.21.0" "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.0"
}, },
"time": "2023-05-17T13:13:44+00:00" "time": "2023-07-23T22:17:56+00:00"
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.10.15", "version": "1.10.26",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd" "reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd", "reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1233,7 +1238,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-09T15:28:01+00:00" "time": "2023-07-19T12:44:37+00:00"
}, },
{ {
"name": "phpstan/phpstan-deprecation-rules", "name": "phpstan/phpstan-deprecation-rules",
@@ -1471,16 +1476,16 @@
}, },
{ {
"name": "spatie/array-to-xml", "name": "spatie/array-to-xml",
"version": "3.1.6", "version": "3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/array-to-xml.git", "url": "https://github.com/spatie/array-to-xml.git",
"reference": "e210b98957987c755372465be105d32113f339a4" "reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/e210b98957987c755372465be105d32113f339a4", "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"reference": "e210b98957987c755372465be105d32113f339a4", "reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1518,7 +1523,7 @@
"xml" "xml"
], ],
"support": { "support": {
"source": "https://github.com/spatie/array-to-xml/tree/3.1.6" "source": "https://github.com/spatie/array-to-xml/tree/3.2.0"
}, },
"funding": [ "funding": [
{ {
@@ -1530,27 +1535,27 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-05-11T14:04:07+00:00" "time": "2023-07-19T18:30:26+00:00"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v6.2.11", "version": "v6.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77" "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5aa03db8ef0a5457c316ec580e69562d97734c77", "url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77", "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.1", "php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3", "symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^1.1|^2|^3", "symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0" "symfony/string": "^5.4|^6.0"
}, },
"conflict": { "conflict": {
@@ -1572,12 +1577,6 @@
"symfony/process": "^5.4|^6.0", "symfony/process": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0" "symfony/var-dumper": "^5.4|^6.0"
}, },
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@@ -1610,7 +1609,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v6.2.11" "source": "https://github.com/symfony/console/tree/v6.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -1626,20 +1625,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-26T08:16:21+00:00" "time": "2023-07-19T20:17:28+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.2.1", "version": "v3.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1648,7 +1647,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.3-dev" "dev-main": "3.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",
@@ -1677,7 +1676,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
}, },
"funding": [ "funding": [
{ {
@@ -1693,20 +1692,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-03-01T10:25:55+00:00" "time": "2023-05-23T14:45:45+00:00"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v6.2.10", "version": "v6.3.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894" "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894", "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894", "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1740,7 +1739,7 @@
"description": "Provides basic utilities for the filesystem", "description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/filesystem/tree/v6.2.10" "source": "https://github.com/symfony/filesystem/tree/v6.3.1"
}, },
"funding": [ "funding": [
{ {
@@ -1756,7 +1755,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-04-18T13:46:08+00:00" "time": "2023-06-01T08:30:39+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
@@ -2173,16 +2172,16 @@
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.2.1", "version": "v3.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a" "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a", "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2192,13 +2191,10 @@
"conflict": { "conflict": {
"ext-psr": "<1.1|>=2" "ext-psr": "<1.1|>=2"
}, },
"suggest": {
"symfony/service-implementation": ""
},
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.3-dev" "dev-main": "3.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",
@@ -2238,7 +2234,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.2.1" "source": "https://github.com/symfony/service-contracts/tree/v3.3.0"
}, },
"funding": [ "funding": [
{ {
@@ -2254,20 +2250,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-03-01T10:32:47+00:00" "time": "2023-05-23T14:45:45+00:00"
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v6.2.8", "version": "v6.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" "reference": "53d1a83225002635bca3482fcbf963001313fb68"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", "reference": "53d1a83225002635bca3482fcbf963001313fb68",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2278,13 +2274,13 @@
"symfony/polyfill-mbstring": "~1.0" "symfony/polyfill-mbstring": "~1.0"
}, },
"conflict": { "conflict": {
"symfony/translation-contracts": "<2.0" "symfony/translation-contracts": "<2.5"
}, },
"require-dev": { "require-dev": {
"symfony/error-handler": "^5.4|^6.0", "symfony/error-handler": "^5.4|^6.0",
"symfony/http-client": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0",
"symfony/intl": "^6.2", "symfony/intl": "^6.2",
"symfony/translation-contracts": "^2.0|^3.0", "symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^5.4|^6.0" "symfony/var-exporter": "^5.4|^6.0"
}, },
"type": "library", "type": "library",
@@ -2324,7 +2320,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v6.2.8" "source": "https://github.com/symfony/string/tree/v6.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -2340,7 +2336,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-03-20T16:06:02+00:00" "time": "2023-07-05T08:41:27+00:00"
}, },
{ {
"name": "tysonandre/var_representation_polyfill", "name": "tysonandre/var_representation_polyfill",
@@ -2406,16 +2402,16 @@
}, },
{ {
"name": "vimeo/psalm", "name": "vimeo/psalm",
"version": "5.12.0", "version": "5.14.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/vimeo/psalm.git", "url": "https://github.com/vimeo/psalm.git",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176" "reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/f90118cdeacd0088e7215e64c0c99ceca819e176", "url": "https://api.github.com/repos/vimeo/psalm/zipball/b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176", "reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2436,8 +2432,8 @@
"felixfbecker/language-server-protocol": "^1.5.2", "felixfbecker/language-server-protocol": "^1.5.2",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"nikic/php-parser": "^4.14", "nikic/php-parser": "^4.16",
"php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0", "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
"sebastian/diff": "^4.0 || ^5.0", "sebastian/diff": "^4.0 || ^5.0",
"spatie/array-to-xml": "^2.17.0 || ^3.0", "spatie/array-to-xml": "^2.17.0 || ^3.0",
"symfony/console": "^4.1.6 || ^5.0 || ^6.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0",
@@ -2506,9 +2502,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/vimeo/psalm/issues", "issues": "https://github.com/vimeo/psalm/issues",
"source": "https://github.com/vimeo/psalm/tree/5.12.0" "source": "https://github.com/vimeo/psalm/tree/5.14.1"
}, },
"time": "2023-05-22T21:19:03+00:00" "time": "2023-08-01T05:16:55+00:00"
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",

View File

@@ -441,31 +441,35 @@
}, },
{ {
"name": "doctrine/deprecations", "name": "doctrine/deprecations",
"version": "v1.0.0", "version": "v1.1.1",
"version_normalized": "1.0.0.0", "version_normalized": "1.1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/deprecations.git", "url": "https://github.com/doctrine/deprecations.git",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.1|^8.0" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^9", "doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5|^8.5|^9.5", "phpstan/phpstan": "1.4.10 || 1.10.15",
"psr/log": "^1|^2|^3" "phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
}, },
"suggest": { "suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation" "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
}, },
"time": "2022-05-02T15:47:09+00:00", "time": "2023-06-03T09:27:29+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -481,7 +485,7 @@
"homepage": "https://www.doctrine-project.org/", "homepage": "https://www.doctrine-project.org/",
"support": { "support": {
"issues": "https://github.com/doctrine/deprecations/issues", "issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0" "source": "https://github.com/doctrine/deprecations/tree/v1.1.1"
}, },
"install-path": "../doctrine/deprecations" "install-path": "../doctrine/deprecations"
}, },
@@ -760,17 +764,17 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v4.15.5", "version": "v4.16.0",
"version_normalized": "4.15.5.0", "version_normalized": "4.16.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" "reference": "19526a33fb561ef417e822e85f08a00db4059c17"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", "reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -781,7 +785,7 @@
"ircmaxell/php-yacc": "^0.0.7", "ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
}, },
"time": "2023-05-19T20:20:00+00:00", "time": "2023-06-25T14:52:30+00:00",
"bin": [ "bin": [
"bin/php-parse" "bin/php-parse"
], ],
@@ -813,7 +817,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
}, },
"install-path": "../nikic/php-parser" "install-path": "../nikic/php-parser"
}, },
@@ -1017,17 +1021,17 @@
}, },
{ {
"name": "phpdocumentor/type-resolver", "name": "phpdocumentor/type-resolver",
"version": "1.7.1", "version": "1.7.2",
"version_normalized": "1.7.1.0", "version_normalized": "1.7.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git", "url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714" "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714", "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714", "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1046,7 +1050,7 @@
"rector/rector": "^0.13.9", "rector/rector": "^0.13.9",
"vimeo/psalm": "^4.25" "vimeo/psalm": "^4.25"
}, },
"time": "2023-03-27T19:02:04+00:00", "time": "2023-05-30T18:13:47+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@@ -1072,7 +1076,7 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": { "support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues", "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1" "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2"
}, },
"install-path": "../phpdocumentor/type-resolver" "install-path": "../phpdocumentor/type-resolver"
}, },
@@ -1125,23 +1129,24 @@
}, },
{ {
"name": "phpstan/phpdoc-parser", "name": "phpstan/phpdoc-parser",
"version": "1.21.0", "version": "1.23.0",
"version_normalized": "1.21.0.0", "version_normalized": "1.23.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git", "url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c" "reference": "a2b24135c35852b348894320d47b3902a94bc494"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6df62b08faef4f899772bc7c3bbabb93d2b7a21c", "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a2b24135c35852b348894320d47b3902a94bc494",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c", "reference": "a2b24135c35852b348894320d47b3902a94bc494",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0" "php": "^7.2 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/annotations": "^2.0",
"nikic/php-parser": "^4.15", "nikic/php-parser": "^4.15",
"php-parallel-lint/php-parallel-lint": "^1.2", "php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0", "phpstan/extension-installer": "^1.0",
@@ -1151,7 +1156,7 @@
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^9.5",
"symfony/process": "^5.2" "symfony/process": "^5.2"
}, },
"time": "2023-05-17T13:13:44+00:00", "time": "2023-07-23T22:17:56+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -1168,23 +1173,23 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types", "description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": { "support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues", "issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.21.0" "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.0"
}, },
"install-path": "../phpstan/phpdoc-parser" "install-path": "../phpstan/phpdoc-parser"
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.10.15", "version": "1.10.26",
"version_normalized": "1.10.15.0", "version_normalized": "1.10.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd" "reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd", "reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1193,7 +1198,7 @@
"conflict": { "conflict": {
"phpstan/phpstan-shim": "*" "phpstan/phpstan-shim": "*"
}, },
"time": "2023-05-09T15:28:01+00:00", "time": "2023-07-19T12:44:37+00:00",
"bin": [ "bin": [
"phpstan", "phpstan",
"phpstan.phar" "phpstan.phar"
@@ -1538,17 +1543,17 @@
}, },
{ {
"name": "spatie/array-to-xml", "name": "spatie/array-to-xml",
"version": "3.1.6", "version": "3.2.0",
"version_normalized": "3.1.6.0", "version_normalized": "3.2.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/array-to-xml.git", "url": "https://github.com/spatie/array-to-xml.git",
"reference": "e210b98957987c755372465be105d32113f339a4" "reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/e210b98957987c755372465be105d32113f339a4", "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"reference": "e210b98957987c755372465be105d32113f339a4", "reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1560,7 +1565,7 @@
"pestphp/pest": "^1.21", "pestphp/pest": "^1.21",
"spatie/pest-plugin-snapshots": "^1.1" "spatie/pest-plugin-snapshots": "^1.1"
}, },
"time": "2023-05-11T14:04:07+00:00", "time": "2023-07-19T18:30:26+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -1588,7 +1593,7 @@
"xml" "xml"
], ],
"support": { "support": {
"source": "https://github.com/spatie/array-to-xml/tree/3.1.6" "source": "https://github.com/spatie/array-to-xml/tree/3.2.0"
}, },
"funding": [ "funding": [
{ {
@@ -1604,24 +1609,24 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v6.2.11", "version": "v6.3.2",
"version_normalized": "6.2.11.0", "version_normalized": "6.3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77" "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5aa03db8ef0a5457c316ec580e69562d97734c77", "url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77", "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.1", "php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3", "symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^1.1|^2|^3", "symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0" "symfony/string": "^5.4|^6.0"
}, },
"conflict": { "conflict": {
@@ -1643,13 +1648,7 @@
"symfony/process": "^5.4|^6.0", "symfony/process": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0" "symfony/var-dumper": "^5.4|^6.0"
}, },
"suggest": { "time": "2023-07-19T20:17:28+00:00",
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"time": "2023-05-26T08:16:21+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -1683,7 +1682,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v6.2.11" "source": "https://github.com/symfony/console/tree/v6.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -1703,27 +1702,27 @@
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.2.1", "version": "v3.3.0",
"version_normalized": "3.2.1.0", "version_normalized": "3.3.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.1" "php": ">=8.1"
}, },
"time": "2023-03-01T10:25:55+00:00", "time": "2023-05-23T14:45:45+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.3-dev" "dev-main": "3.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",
@@ -1753,7 +1752,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
}, },
"funding": [ "funding": [
{ {
@@ -1773,17 +1772,17 @@
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v6.2.10", "version": "v6.3.1",
"version_normalized": "6.2.10.0", "version_normalized": "6.3.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894" "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894", "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894", "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1791,7 +1790,7 @@
"symfony/polyfill-ctype": "~1.8", "symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8" "symfony/polyfill-mbstring": "~1.8"
}, },
"time": "2023-04-18T13:46:08+00:00", "time": "2023-06-01T08:30:39+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -1819,7 +1818,7 @@
"description": "Provides basic utilities for the filesystem", "description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/filesystem/tree/v6.2.10" "source": "https://github.com/symfony/filesystem/tree/v6.3.1"
}, },
"funding": [ "funding": [
{ {
@@ -2267,17 +2266,17 @@
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.2.1", "version": "v3.3.0",
"version_normalized": "3.2.1.0", "version_normalized": "3.3.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a" "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a", "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2287,14 +2286,11 @@
"conflict": { "conflict": {
"ext-psr": "<1.1|>=2" "ext-psr": "<1.1|>=2"
}, },
"suggest": { "time": "2023-05-23T14:45:45+00:00",
"symfony/service-implementation": ""
},
"time": "2023-03-01T10:32:47+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.3-dev" "dev-main": "3.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",
@@ -2335,7 +2331,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.2.1" "source": "https://github.com/symfony/service-contracts/tree/v3.3.0"
}, },
"funding": [ "funding": [
{ {
@@ -2355,17 +2351,17 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v6.2.8", "version": "v6.3.2",
"version_normalized": "6.2.8.0", "version_normalized": "6.3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" "reference": "53d1a83225002635bca3482fcbf963001313fb68"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", "reference": "53d1a83225002635bca3482fcbf963001313fb68",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2376,16 +2372,16 @@
"symfony/polyfill-mbstring": "~1.0" "symfony/polyfill-mbstring": "~1.0"
}, },
"conflict": { "conflict": {
"symfony/translation-contracts": "<2.0" "symfony/translation-contracts": "<2.5"
}, },
"require-dev": { "require-dev": {
"symfony/error-handler": "^5.4|^6.0", "symfony/error-handler": "^5.4|^6.0",
"symfony/http-client": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0",
"symfony/intl": "^6.2", "symfony/intl": "^6.2",
"symfony/translation-contracts": "^2.0|^3.0", "symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^5.4|^6.0" "symfony/var-exporter": "^5.4|^6.0"
}, },
"time": "2023-03-20T16:06:02+00:00", "time": "2023-07-05T08:41:27+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -2424,7 +2420,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v6.2.8" "source": "https://github.com/symfony/string/tree/v6.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -2509,17 +2505,17 @@
}, },
{ {
"name": "vimeo/psalm", "name": "vimeo/psalm",
"version": "5.12.0", "version": "5.14.1",
"version_normalized": "5.12.0.0", "version_normalized": "5.14.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/vimeo/psalm.git", "url": "https://github.com/vimeo/psalm.git",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176" "reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/f90118cdeacd0088e7215e64c0c99ceca819e176", "url": "https://api.github.com/repos/vimeo/psalm/zipball/b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176", "reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2540,8 +2536,8 @@
"felixfbecker/language-server-protocol": "^1.5.2", "felixfbecker/language-server-protocol": "^1.5.2",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"nikic/php-parser": "^4.14", "nikic/php-parser": "^4.16",
"php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0", "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
"sebastian/diff": "^4.0 || ^5.0", "sebastian/diff": "^4.0 || ^5.0",
"spatie/array-to-xml": "^2.17.0 || ^3.0", "spatie/array-to-xml": "^2.17.0 || ^3.0",
"symfony/console": "^4.1.6 || ^5.0 || ^6.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0",
@@ -2570,7 +2566,7 @@
"ext-curl": "In order to send data to shepherd", "ext-curl": "In order to send data to shepherd",
"ext-igbinary": "^2.0.5 is required, used to serialize caching data" "ext-igbinary": "^2.0.5 is required, used to serialize caching data"
}, },
"time": "2023-05-22T21:19:03+00:00", "time": "2023-08-01T05:16:55+00:00",
"bin": [ "bin": [
"psalm", "psalm",
"psalm-language-server", "psalm-language-server",
@@ -2612,7 +2608,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/vimeo/psalm/issues", "issues": "https://github.com/vimeo/psalm/issues",
"source": "https://github.com/vimeo/psalm/tree/5.12.0" "source": "https://github.com/vimeo/psalm/tree/5.14.1"
}, },
"install-path": "../vimeo/psalm" "install-path": "../vimeo/psalm"
}, },

View File

@@ -65,9 +65,9 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'doctrine/deprecations' => array( 'doctrine/deprecations' => array(
'pretty_version' => 'v1.0.0', 'pretty_version' => 'v1.1.1',
'version' => '1.0.0.0', 'version' => '1.1.1.0',
'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de', 'reference' => '612a3ee5ab0d5dd97b7cf3874a6efe24325efac3',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/deprecations', 'install_path' => __DIR__ . '/../doctrine/deprecations',
'aliases' => array(), 'aliases' => array(),
@@ -128,9 +128,9 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'nikic/php-parser' => array( 'nikic/php-parser' => array(
'pretty_version' => 'v4.15.5', 'pretty_version' => 'v4.16.0',
'version' => '4.15.5.0', 'version' => '4.16.0.0',
'reference' => '11e2663a5bc9db5d714eedb4277ee300403b4a9e', 'reference' => '19526a33fb561ef417e822e85f08a00db4059c17',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../nikic/php-parser', 'install_path' => __DIR__ . '/../nikic/php-parser',
'aliases' => array(), 'aliases' => array(),
@@ -164,9 +164,9 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpdocumentor/type-resolver' => array( 'phpdocumentor/type-resolver' => array(
'pretty_version' => '1.7.1', 'pretty_version' => '1.7.2',
'version' => '1.7.1.0', 'version' => '1.7.2.0',
'reference' => 'dfc078e8af9c99210337325ff5aa152872c98714', 'reference' => 'b2fe4d22a5426f38e014855322200b97b5362c0d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpdocumentor/type-resolver', 'install_path' => __DIR__ . '/../phpdocumentor/type-resolver',
'aliases' => array(), 'aliases' => array(),
@@ -182,18 +182,18 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpstan/phpdoc-parser' => array( 'phpstan/phpdoc-parser' => array(
'pretty_version' => '1.21.0', 'pretty_version' => '1.23.0',
'version' => '1.21.0.0', 'version' => '1.23.0.0',
'reference' => '6df62b08faef4f899772bc7c3bbabb93d2b7a21c', 'reference' => 'a2b24135c35852b348894320d47b3902a94bc494',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpstan/phpdoc-parser', 'install_path' => __DIR__ . '/../phpstan/phpdoc-parser',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpstan/phpstan' => array( 'phpstan/phpstan' => array(
'pretty_version' => '1.10.15', 'pretty_version' => '1.10.26',
'version' => '1.10.15.0', 'version' => '1.10.26.0',
'reference' => '762c4dac4da6f8756eebb80e528c3a47855da9bd', 'reference' => '5d660cbb7e1b89253a47147ae44044f49832351f',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpstan/phpstan', 'install_path' => __DIR__ . '/../phpstan/phpstan',
'aliases' => array(), 'aliases' => array(),
@@ -211,7 +211,7 @@
'psalm/psalm' => array( 'psalm/psalm' => array(
'dev_requirement' => true, 'dev_requirement' => true,
'provided' => array( 'provided' => array(
0 => '5.12.0', 0 => '5.14.1',
), ),
), ),
'psr/container' => array( 'psr/container' => array(
@@ -257,36 +257,36 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'spatie/array-to-xml' => array( 'spatie/array-to-xml' => array(
'pretty_version' => '3.1.6', 'pretty_version' => '3.2.0',
'version' => '3.1.6.0', 'version' => '3.2.0.0',
'reference' => 'e210b98957987c755372465be105d32113f339a4', 'reference' => 'f9ab39c808500c347d5a8b6b13310bd5221e39e7',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../spatie/array-to-xml', 'install_path' => __DIR__ . '/../spatie/array-to-xml',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'symfony/console' => array( 'symfony/console' => array(
'pretty_version' => 'v6.2.11', 'pretty_version' => 'v6.3.2',
'version' => '6.2.11.0', 'version' => '6.3.2.0',
'reference' => '5aa03db8ef0a5457c316ec580e69562d97734c77', 'reference' => 'aa5d64ad3f63f2e48964fc81ee45cb318a723898',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console', 'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'symfony/deprecation-contracts' => array( 'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.2.1', 'pretty_version' => 'v3.3.0',
'version' => '3.2.1.0', 'version' => '3.3.0.0',
'reference' => 'e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e', 'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'symfony/filesystem' => array( 'symfony/filesystem' => array(
'pretty_version' => 'v6.2.10', 'pretty_version' => 'v6.3.1',
'version' => '6.2.10.0', 'version' => '6.3.1.0',
'reference' => 'fd588debf7d1bc16a2c84b4b3b71145d9946b894', 'reference' => 'edd36776956f2a6fcf577edb5b05eb0e3bdc52ae',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem', 'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(), 'aliases' => array(),
@@ -338,18 +338,18 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'symfony/service-contracts' => array( 'symfony/service-contracts' => array(
'pretty_version' => 'v3.2.1', 'pretty_version' => 'v3.3.0',
'version' => '3.2.1.0', 'version' => '3.3.0.0',
'reference' => 'a8c9cedf55f314f3a186041d19537303766df09a', 'reference' => '40da9cc13ec349d9e4966ce18b5fbcd724ab10a4',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts', 'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'symfony/string' => array( 'symfony/string' => array(
'pretty_version' => 'v6.2.8', 'pretty_version' => 'v6.3.2',
'version' => '6.2.8.0', 'version' => '6.3.2.0',
'reference' => '193e83bbd6617d6b2151c37fff10fa7168ebddef', 'reference' => '53d1a83225002635bca3482fcbf963001313fb68',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string', 'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(), 'aliases' => array(),
@@ -365,9 +365,9 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'vimeo/psalm' => array( 'vimeo/psalm' => array(
'pretty_version' => '5.12.0', 'pretty_version' => '5.14.1',
'version' => '5.12.0.0', 'version' => '5.14.1.0',
'reference' => 'f90118cdeacd0088e7215e64c0c99ceca819e176', 'reference' => 'b9d355e0829c397b9b3b47d0c0ed042a8a70284d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../vimeo/psalm', 'install_path' => __DIR__ . '/../vimeo/psalm',
'aliases' => array(), 'aliases' => array(),

View File

@@ -19,13 +19,16 @@ Enable Doctrine deprecations to be sent to a PSR3 logger:
``` ```
Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)`
messages. messages by setting the `DOCTRINE_DEPRECATIONS` environment variable to `trigger`.
Alternatively, call:
```php ```php
\Doctrine\Deprecations\Deprecation::enableWithTriggerError(); \Doctrine\Deprecations\Deprecation::enableWithTriggerError();
``` ```
If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call: If you only want to enable deprecation tracking, without logging or calling `trigger_error`
then set the `DOCTRINE_DEPRECATIONS` environment variable to `track`.
Alternatively, call:
```php ```php
\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();

View File

@@ -1,22 +1,28 @@
{ {
"name": "doctrine/deprecations", "name": "doctrine/deprecations",
"type": "library",
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"license": "MIT", "license": "MIT",
"type": "library",
"homepage": "https://www.doctrine-project.org/",
"require": { "require": {
"php": "^7.1|^8.0" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^7.5|^8.5|^9.5", "doctrine/coding-standard": "^9",
"psr/log": "^1|^2|^3", "phpstan/phpstan": "1.4.10 || 1.10.15",
"doctrine/coding-standard": "^9" "phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
}, },
"suggest": { "suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation" "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
}, },
"autoload": { "autoload": {
"psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"} "psr-4": {
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
}
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {

View File

@@ -8,6 +8,7 @@ use Psr\Log\LoggerInterface;
use function array_key_exists; use function array_key_exists;
use function array_reduce; use function array_reduce;
use function assert;
use function debug_backtrace; use function debug_backtrace;
use function sprintf; use function sprintf;
use function strpos; use function strpos;
@@ -46,8 +47,8 @@ class Deprecation
private const TYPE_TRIGGER_ERROR = 2; private const TYPE_TRIGGER_ERROR = 2;
private const TYPE_PSR_LOGGER = 4; private const TYPE_PSR_LOGGER = 4;
/** @var int */ /** @var int-mask-of<self::TYPE_*>|null */
private static $type = self::TYPE_NONE; private static $type;
/** @var LoggerInterface|null */ /** @var LoggerInterface|null */
private static $logger; private static $logger;
@@ -56,6 +57,9 @@ class Deprecation
private static $ignoredPackages = []; private static $ignoredPackages = [];
/** @var array<string,int> */ /** @var array<string,int> */
private static $triggeredDeprecations = [];
/** @var array<string,bool> */
private static $ignoredLinks = []; private static $ignoredLinks = [];
/** @var bool */ /** @var bool */
@@ -68,21 +72,27 @@ class Deprecation
* deprecation. It is additionally used to de-duplicate the trigger of the * deprecation. It is additionally used to de-duplicate the trigger of the
* same deprecation during a request. * same deprecation during a request.
* *
* @param mixed $args * @param float|int|string $args
*/ */
public static function trigger(string $package, string $link, string $message, ...$args): void public static function trigger(string $package, string $link, string $message, ...$args): void
{ {
if (self::$type === self::TYPE_NONE) { $type = self::$type ?? self::getTypeFromEnv();
if ($type === self::TYPE_NONE) {
return; return;
} }
if (array_key_exists($link, self::$ignoredLinks)) { if (isset(self::$ignoredLinks[$link])) {
self::$ignoredLinks[$link]++; return;
} else {
self::$ignoredLinks[$link] = 1;
} }
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { if (array_key_exists($link, self::$triggeredDeprecations)) {
self::$triggeredDeprecations[$link]++;
} else {
self::$triggeredDeprecations[$link] = 1;
}
if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) {
return; return;
} }
@@ -114,18 +124,20 @@ class Deprecation
* deprecation tracking is enabled even during deduplication, because it * deprecation tracking is enabled even during deduplication, because it
* needs to call {@link debug_backtrace()} * needs to call {@link debug_backtrace()}
* *
* @param mixed $args * @param float|int|string $args
*/ */
public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
{ {
if (self::$type === self::TYPE_NONE) { $type = self::$type ?? self::getTypeFromEnv();
if ($type === self::TYPE_NONE) {
return; return;
} }
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
// first check that the caller is not from a tests folder, in which case we always let deprecations pass // first check that the caller is not from a tests folder, in which case we always let deprecations pass
if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
$path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR; $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR;
if (strpos($backtrace[0]['file'], $path) === false) { if (strpos($backtrace[0]['file'], $path) === false) {
@@ -137,13 +149,17 @@ class Deprecation
} }
} }
if (array_key_exists($link, self::$ignoredLinks)) { if (isset(self::$ignoredLinks[$link])) {
self::$ignoredLinks[$link]++; return;
} else {
self::$ignoredLinks[$link] = 1;
} }
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { if (array_key_exists($link, self::$triggeredDeprecations)) {
self::$triggeredDeprecations[$link]++;
} else {
self::$triggeredDeprecations[$link] = 1;
}
if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) {
return; return;
} }
@@ -157,31 +173,35 @@ class Deprecation
} }
/** /**
* @param array<mixed> $backtrace * @param list<array{function: string, line?: int, file?: string, class?: class-string, type?: string, args?: mixed[], object?: object}> $backtrace
*/ */
private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
{ {
if ((self::$type & self::TYPE_PSR_LOGGER) > 0) { $type = self::$type ?? self::getTypeFromEnv();
if (($type & self::TYPE_PSR_LOGGER) > 0) {
$context = [ $context = [
'file' => $backtrace[0]['file'], 'file' => $backtrace[0]['file'] ?? null,
'line' => $backtrace[0]['line'], 'line' => $backtrace[0]['line'] ?? null,
'package' => $package, 'package' => $package,
'link' => $link, 'link' => $link,
]; ];
assert(self::$logger !== null);
self::$logger->notice($message, $context); self::$logger->notice($message, $context);
} }
if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) { if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) {
return; return;
} }
$message .= sprintf( $message .= sprintf(
' (%s:%d called by %s:%d, %s, package %s)', ' (%s:%d called by %s:%d, %s, package %s)',
self::basename($backtrace[0]['file']), self::basename($backtrace[0]['file'] ?? 'native code'),
$backtrace[0]['line'], $backtrace[0]['line'] ?? 0,
self::basename($backtrace[1]['file']), self::basename($backtrace[1]['file'] ?? 'native code'),
$backtrace[1]['line'], $backtrace[1]['line'] ?? 0,
$link, $link,
$package $package
); );
@@ -205,16 +225,19 @@ class Deprecation
public static function enableTrackingDeprecations(): void public static function enableTrackingDeprecations(): void
{ {
self::$type = self::$type ?? 0;
self::$type |= self::TYPE_TRACK_DEPRECATIONS; self::$type |= self::TYPE_TRACK_DEPRECATIONS;
} }
public static function enableWithTriggerError(): void public static function enableWithTriggerError(): void
{ {
self::$type = self::$type ?? 0;
self::$type |= self::TYPE_TRIGGER_ERROR; self::$type |= self::TYPE_TRIGGER_ERROR;
} }
public static function enableWithPsrLogger(LoggerInterface $logger): void public static function enableWithPsrLogger(LoggerInterface $logger): void
{ {
self::$type = self::$type ?? 0;
self::$type |= self::TYPE_PSR_LOGGER; self::$type |= self::TYPE_PSR_LOGGER;
self::$logger = $logger; self::$logger = $logger;
} }
@@ -229,9 +252,10 @@ class Deprecation
self::$type = self::TYPE_NONE; self::$type = self::TYPE_NONE;
self::$logger = null; self::$logger = null;
self::$deduplication = true; self::$deduplication = true;
self::$ignoredLinks = [];
foreach (self::$ignoredLinks as $link => $count) { foreach (self::$triggeredDeprecations as $link => $count) {
self::$ignoredLinks[$link] = 0; self::$triggeredDeprecations[$link] = 0;
} }
} }
@@ -243,13 +267,13 @@ class Deprecation
public static function ignoreDeprecations(string ...$links): void public static function ignoreDeprecations(string ...$links): void
{ {
foreach ($links as $link) { foreach ($links as $link) {
self::$ignoredLinks[$link] = 0; self::$ignoredLinks[$link] = true;
} }
} }
public static function getUniqueTriggeredDeprecationsCount(): int public static function getUniqueTriggeredDeprecationsCount(): int
{ {
return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) { return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) {
return $carry + $count; return $carry + $count;
}, 0); }, 0);
} }
@@ -261,6 +285,28 @@ class Deprecation
*/ */
public static function getTriggeredDeprecations(): array public static function getTriggeredDeprecations(): array
{ {
return self::$ignoredLinks; return self::$triggeredDeprecations;
}
/**
* @return int-mask-of<self::TYPE_*>
*/
private static function getTypeFromEnv(): int
{
switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) {
case 'trigger':
self::$type = self::TYPE_TRIGGER_ERROR;
break;
case 'track':
self::$type = self::TYPE_TRACK_DEPRECATIONS;
break;
default:
self::$type = self::TYPE_NONE;
break;
}
return self::$type;
} }
} }

View File

@@ -0,0 +1,9 @@
parameters:
level: 6
paths:
- lib
- tests
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon

30
vendor/doctrine/deprecations/psalm.xml vendored Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="false"
>
<projectFiles>
<directory name="lib/Doctrine/Deprecations" />
<directory name="tests/Doctrine/Deprecations" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
<issueHandlers>
<DeprecatedMethod>
<errorLevel type="suppress">
<!-- Remove when dropping support for PHPUnit 9.6 -->
<referencedMethod name="PHPUnit\Framework\TestCase::expectDeprecation"/>
<referencedMethod name="PHPUnit\Framework\TestCase::expectDeprecationMessage"/>
</errorLevel>
</DeprecatedMethod>
</issueHandlers>
</psalm>

View File

@@ -1008,7 +1008,7 @@ array_pair:
| expr { $$ = Expr\ArrayItem[$1, null, false]; } | expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; } | expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; } | ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; } | T_ELLIPSIS expr { $$ = new Expr\ArrayItem($2, null, false, attributes(), true); }
; ;
encaps_list: encaps_list:

View File

@@ -1194,7 +1194,7 @@ array_pair:
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; } | expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; } | expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; } | expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; } | T_ELLIPSIS expr { $$ = new Expr\ArrayItem($2, null, false, attributes(), true); }
| /* empty */ { $$ = null; } | /* empty */ { $$ = null; }
; ;

View File

@@ -6,7 +6,10 @@ use PhpParser\NodeAbstract;
class Name extends NodeAbstract class Name extends NodeAbstract
{ {
/** @var string[] Parts of the name */ /**
* @var string[] Parts of the name
* @deprecated Use getParts() instead
*/
public $parts; public $parts;
private static $specialClassNames = [ private static $specialClassNames = [
@@ -30,6 +33,15 @@ class Name extends NodeAbstract
return ['parts']; return ['parts'];
} }
/**
* Get parts of name (split by the namespace separator).
*
* @return string[] Parts of name
*/
public function getParts(): array {
return $this->parts;
}
/** /**
* Gets the first part of the name, i.e. everything before the first namespace separator. * Gets the first part of the name, i.e. everything before the first namespace separator.
* *

View File

@@ -2627,7 +2627,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
}, },
552 => function ($stackPos) { 552 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true);
}, },
553 => function ($stackPos) { 553 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];

View File

@@ -2818,7 +2818,7 @@ class Php7 extends \PhpParser\ParserAbstract
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
}, },
592 => function ($stackPos) { 592 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true);
}, },
593 => function ($stackPos) { 593 => function ($stackPos) {
$this->semValue = null; $this->semValue = null;

View File

@@ -15,7 +15,7 @@ namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\PseudoType; use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Float_; use phpDocumentor\Reflection\Types\String_;
use function sprintf; use function sprintf;
@@ -36,7 +36,7 @@ class StringValue implements PseudoType
public function underlyingType(): Type public function underlyingType(): Type
{ {
return new Float_(); return new String_();
} }
public function __toString(): string public function __toString(): string

View File

@@ -15,6 +15,8 @@ For the complete list of supported PHPDoc features check out PHPStan documentati
* [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types) (list of PHPDoc types) * [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types) (list of PHPDoc types)
* [phpdoc-parser API Reference](https://phpstan.github.io/phpdoc-parser/namespace-PHPStan.PhpDocParser.html) with all the AST node types etc. * [phpdoc-parser API Reference](https://phpstan.github.io/phpdoc-parser/namespace-PHPStan.PhpDocParser.html) with all the AST node types etc.
This parser also supports parsing [Doctrine Annotations](https://github.com/doctrine/annotations). The AST nodes live in the [PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine namespace](https://phpstan.github.io/phpdoc-parser/namespace-PHPStan.PhpDocParser.Ast.PhpDoc.Doctrine.html). The support needs to be turned on by setting `bool $parseDoctrineAnnotations` to `true` in `Lexer` and `PhpDocParser` class constructors.
## Installation ## Installation
``` ```
@@ -91,12 +93,13 @@ $phpDocNode = $phpDocParser->parse($tokens); // PhpDocNode
$cloningTraverser = new NodeTraverser([new CloningVisitor()]); $cloningTraverser = new NodeTraverser([new CloningVisitor()]);
/** @var PhpDocNode $newPhpDocNode */ /** @var PhpDocNode $newPhpDocNode */
$printer = new Printer();
[$newPhpDocNode] = $cloningTraverser->traverse([$phpDocNode]); [$newPhpDocNode] = $cloningTraverser->traverse([$phpDocNode]);
// change something in $newPhpDocNode // change something in $newPhpDocNode
$newPhpDocNode->getParamTagValues()[0]->type = new IdentifierTypeNode('Ipsum'); $newPhpDocNode->getParamTagValues()[0]->type = new IdentifierTypeNode('Ipsum');
// print changed PHPDoc
$printer = new Printer();
$newPhpDoc = $printer->printFormatPreserving($newPhpDocNode, $phpDocNode, $tokens); $newPhpDoc = $printer->printFormatPreserving($newPhpDocNode, $phpDocNode, $tokens);
echo $newPhpDoc; // '/** @param Ipsum $a */' echo $newPhpDoc; // '/** @param Ipsum $a */'
``` ```

View File

@@ -6,6 +6,7 @@
"php": "^7.2 || ^8.0" "php": "^7.2 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/annotations": "^2.0",
"nikic/php-parser": "^4.15", "nikic/php-parser": "^4.15",
"php-parallel-lint/php-parallel-lint": "^1.2", "php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0", "phpstan/extension-installer": "^1.0",

View File

@@ -8,7 +8,7 @@ namespace PHPStan\PhpDocParser\Ast;
* Copyright (c) 2011, Nikita Popov * Copyright (c) 2011, Nikita Popov
* All rights reserved. * All rights reserved.
*/ */
abstract class AbstractNodeVisitor implements NodeVisitor abstract class AbstractNodeVisitor implements NodeVisitor // phpcs:ignore SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix
{ {
public function beforeTraverse(array $nodes): ?array public function beforeTraverse(array $nodes): ?array

View File

@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use function implode;
class DoctrineAnnotation implements Node
{
use NodeAttributes;
/** @var string */
public $name;
/** @var list<DoctrineArgument> */
public $arguments;
/**
* @param list<DoctrineArgument> $arguments
*/
public function __construct(string $name, array $arguments)
{
$this->name = $name;
$this->arguments = $arguments;
}
public function __toString(): string
{
$arguments = implode(', ', $this->arguments);
return $this->name . '(' . $arguments . ')';
}
}

View File

@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
/**
* @phpstan-type ValueType = DoctrineAnnotation|IdentifierTypeNode|DoctrineArray|ConstExprNode
*/
class DoctrineArgument implements Node
{
use NodeAttributes;
/** @var IdentifierTypeNode|null */
public $key;
/** @var ValueType */
public $value;
/**
* @param ValueType $value
*/
public function __construct(?IdentifierTypeNode $key, $value)
{
$this->key = $key;
$this->value = $value;
}
public function __toString(): string
{
if ($this->key === null) {
return (string) $this->value;
}
return $this->key . '=' . $this->value;
}
}

View File

@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use function implode;
class DoctrineArray implements Node
{
use NodeAttributes;
/** @var list<DoctrineArrayItem> */
public $items;
/**
* @param list<DoctrineArrayItem> $items
*/
public function __construct(array $items)
{
$this->items = $items;
}
public function __toString(): string
{
$items = implode(', ', $this->items);
return '{' . $items . '}';
}
}

View File

@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
/**
* @phpstan-import-type ValueType from DoctrineArgument
* @phpstan-type KeyType = ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode|null
*/
class DoctrineArrayItem implements Node
{
use NodeAttributes;
/** @var KeyType */
public $key;
/** @var ValueType */
public $value;
/**
* @param KeyType $key
* @param ValueType $value
*/
public function __construct($key, $value)
{
$this->key = $key;
$this->value = $value;
}
public function __toString(): string
{
if ($this->key === null) {
return (string) $this->value;
}
return $this->key . '=' . $this->value;
}
}

View File

@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use function trim;
class DoctrineTagValueNode implements PhpDocTagValueNode
{
use NodeAttributes;
/** @var DoctrineAnnotation */
public $annotation;
/** @var string (may be empty) */
public $description;
public function __construct(
DoctrineAnnotation $annotation,
string $description
)
{
$this->annotation = $annotation;
$this->description = $description;
}
public function __toString(): string
{
return trim("{$this->annotation} {$this->description}");
}
}

View File

@@ -3,6 +3,7 @@
namespace PHPStan\PhpDocParser\Ast\PhpDoc; namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode;
use function trim; use function trim;
class PhpDocTagNode implements PhpDocChildNode class PhpDocTagNode implements PhpDocChildNode
@@ -25,6 +26,10 @@ class PhpDocTagNode implements PhpDocChildNode
public function __toString(): string public function __toString(): string
{ {
if ($this->value instanceof DoctrineTagValueNode) {
return (string) $this->value;
}
return trim("{$this->name} {$this->value}"); return trim("{$this->name} {$this->value}");
} }

View File

@@ -30,23 +30,24 @@ class Lexer
public const TOKEN_OPEN_PHPDOC = 15; public const TOKEN_OPEN_PHPDOC = 15;
public const TOKEN_CLOSE_PHPDOC = 16; public const TOKEN_CLOSE_PHPDOC = 16;
public const TOKEN_PHPDOC_TAG = 17; public const TOKEN_PHPDOC_TAG = 17;
public const TOKEN_FLOAT = 18; public const TOKEN_DOCTRINE_TAG = 18;
public const TOKEN_INTEGER = 19; public const TOKEN_FLOAT = 19;
public const TOKEN_SINGLE_QUOTED_STRING = 20; public const TOKEN_INTEGER = 20;
public const TOKEN_DOUBLE_QUOTED_STRING = 21; public const TOKEN_SINGLE_QUOTED_STRING = 21;
public const TOKEN_IDENTIFIER = 22; public const TOKEN_DOUBLE_QUOTED_STRING = 22;
public const TOKEN_THIS_VARIABLE = 23; public const TOKEN_IDENTIFIER = 23;
public const TOKEN_VARIABLE = 24; public const TOKEN_THIS_VARIABLE = 24;
public const TOKEN_HORIZONTAL_WS = 25; public const TOKEN_VARIABLE = 25;
public const TOKEN_PHPDOC_EOL = 26; public const TOKEN_HORIZONTAL_WS = 26;
public const TOKEN_OTHER = 27; public const TOKEN_PHPDOC_EOL = 27;
public const TOKEN_END = 28; public const TOKEN_OTHER = 28;
public const TOKEN_COLON = 29; public const TOKEN_END = 29;
public const TOKEN_WILDCARD = 30; public const TOKEN_COLON = 30;
public const TOKEN_OPEN_CURLY_BRACKET = 31; public const TOKEN_WILDCARD = 31;
public const TOKEN_CLOSE_CURLY_BRACKET = 32; public const TOKEN_OPEN_CURLY_BRACKET = 32;
public const TOKEN_NEGATED = 33; public const TOKEN_CLOSE_CURLY_BRACKET = 33;
public const TOKEN_ARROW = 34; public const TOKEN_NEGATED = 34;
public const TOKEN_ARROW = 35;
public const TOKEN_LABELS = [ public const TOKEN_LABELS = [
self::TOKEN_REFERENCE => '\'&\'', self::TOKEN_REFERENCE => '\'&\'',
@@ -72,6 +73,7 @@ class Lexer
self::TOKEN_OPEN_PHPDOC => '\'/**\'', self::TOKEN_OPEN_PHPDOC => '\'/**\'',
self::TOKEN_CLOSE_PHPDOC => '\'*/\'', self::TOKEN_CLOSE_PHPDOC => '\'*/\'',
self::TOKEN_PHPDOC_TAG => 'TOKEN_PHPDOC_TAG', self::TOKEN_PHPDOC_TAG => 'TOKEN_PHPDOC_TAG',
self::TOKEN_DOCTRINE_TAG => 'TOKEN_DOCTRINE_TAG',
self::TOKEN_PHPDOC_EOL => 'TOKEN_PHPDOC_EOL', self::TOKEN_PHPDOC_EOL => 'TOKEN_PHPDOC_EOL',
self::TOKEN_FLOAT => 'TOKEN_FLOAT', self::TOKEN_FLOAT => 'TOKEN_FLOAT',
self::TOKEN_INTEGER => 'TOKEN_INTEGER', self::TOKEN_INTEGER => 'TOKEN_INTEGER',
@@ -90,9 +92,17 @@ class Lexer
public const TYPE_OFFSET = 1; public const TYPE_OFFSET = 1;
public const LINE_OFFSET = 2; public const LINE_OFFSET = 2;
/** @var bool */
private $parseDoctrineAnnotations;
/** @var string|null */ /** @var string|null */
private $regexp; private $regexp;
public function __construct(bool $parseDoctrineAnnotations = false)
{
$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
}
/** /**
* @return list<array{string, int, int}> * @return list<array{string, int, int}>
*/ */
@@ -160,17 +170,21 @@ class Lexer
self::TOKEN_PHPDOC_TAG => '@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9-\\\\]*+', self::TOKEN_PHPDOC_TAG => '@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9-\\\\]*+',
self::TOKEN_PHPDOC_EOL => '\\r?+\\n[\\x09\\x20]*+(?:\\*(?!/)\\x20?+)?', self::TOKEN_PHPDOC_EOL => '\\r?+\\n[\\x09\\x20]*+(?:\\*(?!/)\\x20?+)?',
self::TOKEN_FLOAT => '(?:-?[0-9]++(_[0-9]++)*\\.[0-9]*(_[0-9]++)*+(?:e-?[0-9]++(_[0-9]++)*)?)|(?:-?[0-9]*+(_[0-9]++)*\\.[0-9]++(_[0-9]++)*(?:e-?[0-9]++(_[0-9]++)*)?)|(?:-?[0-9]++(_[0-9]++)*e-?[0-9]++(_[0-9]++)*)', self::TOKEN_FLOAT => '[+\-]?(?:(?:[0-9]++(_[0-9]++)*\\.[0-9]*+(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]*+(_[0-9]++)*\\.[0-9]++(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]++(_[0-9]++)*e[+\-]?[0-9]++(_[0-9]++)*))',
self::TOKEN_INTEGER => '-?(?:(?:0b[0-1]++(_[0-1]++)*)|(?:0o[0-7]++(_[0-7]++)*)|(?:0x[0-9a-f]++(_[0-9a-f]++)*)|(?:[0-9]++(_[0-9]++)*))', self::TOKEN_INTEGER => '[+\-]?(?:(?:0b[0-1]++(_[0-1]++)*)|(?:0o[0-7]++(_[0-7]++)*)|(?:0x[0-9a-f]++(_[0-9a-f]++)*)|(?:[0-9]++(_[0-9]++)*))',
self::TOKEN_SINGLE_QUOTED_STRING => '\'(?:\\\\[^\\r\\n]|[^\'\\r\\n\\\\])*+\'', self::TOKEN_SINGLE_QUOTED_STRING => '\'(?:\\\\[^\\r\\n]|[^\'\\r\\n\\\\])*+\'',
self::TOKEN_DOUBLE_QUOTED_STRING => '"(?:\\\\[^\\r\\n]|[^"\\r\\n\\\\])*+"', self::TOKEN_DOUBLE_QUOTED_STRING => '"(?:\\\\[^\\r\\n]|[^"\\r\\n\\\\])*+"',
self::TOKEN_WILDCARD => '\\*', self::TOKEN_WILDCARD => '\\*',
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
self::TOKEN_OTHER => '(?:(?!\\*/)[^\\s])++',
]; ];
if ($this->parseDoctrineAnnotations) {
$patterns[self::TOKEN_DOCTRINE_TAG] = '@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*';
}
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
$patterns[self::TOKEN_OTHER] = '(?:(?!\\*/)[^\\s])++';
foreach ($patterns as $type => &$pattern) { foreach ($patterns as $type => &$pattern) {
$pattern = '(?:' . $pattern . ')(*MARK:' . $type . ')'; $pattern = '(?:' . $pattern . ')(*MARK:' . $type . ')';
} }

View File

@@ -245,22 +245,14 @@ class ConstExprParser
*/ */
private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex): Ast\ConstExpr\ConstExprNode private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex): Ast\ConstExpr\ConstExprNode
{ {
$endLine = $tokens->currentTokenLine();
$endIndex = $tokens->currentTokenIndex();
if ($this->useLinesAttributes) { if ($this->useLinesAttributes) {
$node->setAttribute(Ast\Attribute::START_LINE, $startLine); $node->setAttribute(Ast\Attribute::START_LINE, $startLine);
$node->setAttribute(Ast\Attribute::END_LINE, $endLine); $node->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
} }
if ($this->useIndexAttributes) { if ($this->useIndexAttributes) {
$tokensArray = $tokens->getTokens();
$endIndex--;
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$endIndex--;
}
$node->setAttribute(Ast\Attribute::START_INDEX, $startIndex); $node->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
$node->setAttribute(Ast\Attribute::END_INDEX, $endIndex); $node->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
} }
return $node; return $node;

View File

@@ -2,15 +2,25 @@
namespace PHPStan\PhpDocParser\Parser; namespace PHPStan\PhpDocParser\Parser;
use LogicException;
use PHPStan\PhpDocParser\Ast; use PHPStan\PhpDocParser\Ast;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\ShouldNotHappenException; use PHPStan\ShouldNotHappenException;
use function array_key_exists; use function array_key_exists;
use function array_values; use function array_values;
use function count; use function count;
use function rtrim;
use function str_replace;
use function trim; use function trim;
/**
* @phpstan-import-type ValueType from Doctrine\DoctrineArgument as DoctrineValueType
*/
class PhpDocParser class PhpDocParser
{ {
@@ -31,12 +41,18 @@ class PhpDocParser
/** @var bool */ /** @var bool */
private $preserveTypeAliasesWithInvalidTypes; private $preserveTypeAliasesWithInvalidTypes;
/** @var bool */
private $parseDoctrineAnnotations;
/** @var bool */ /** @var bool */
private $useLinesAttributes; private $useLinesAttributes;
/** @var bool */ /** @var bool */
private $useIndexAttributes; private $useIndexAttributes;
/** @var bool */
private $textBetweenTagsBelongsToDescription;
/** /**
* @param array{lines?: bool, indexes?: bool} $usedAttributes * @param array{lines?: bool, indexes?: bool} $usedAttributes
*/ */
@@ -45,15 +61,19 @@ class PhpDocParser
ConstExprParser $constantExprParser, ConstExprParser $constantExprParser,
bool $requireWhitespaceBeforeDescription = false, bool $requireWhitespaceBeforeDescription = false,
bool $preserveTypeAliasesWithInvalidTypes = false, bool $preserveTypeAliasesWithInvalidTypes = false,
array $usedAttributes = [] array $usedAttributes = [],
bool $parseDoctrineAnnotations = false,
bool $textBetweenTagsBelongsToDescription = false
) )
{ {
$this->typeParser = $typeParser; $this->typeParser = $typeParser;
$this->constantExprParser = $constantExprParser; $this->constantExprParser = $constantExprParser;
$this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription; $this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription;
$this->preserveTypeAliasesWithInvalidTypes = $preserveTypeAliasesWithInvalidTypes; $this->preserveTypeAliasesWithInvalidTypes = $preserveTypeAliasesWithInvalidTypes;
$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
$this->useLinesAttributes = $usedAttributes['lines'] ?? false; $this->useLinesAttributes = $usedAttributes['lines'] ?? false;
$this->useIndexAttributes = $usedAttributes['indexes'] ?? false; $this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
$this->textBetweenTagsBelongsToDescription = $textBetweenTagsBelongsToDescription;
} }
@@ -64,10 +84,44 @@ class PhpDocParser
$children = []; $children = [];
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { if ($this->parseDoctrineAnnotations) {
$children[] = $this->parseChild($tokens); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { $lastChild = $this->parseChild($tokens);
$children[] = $lastChild;
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
if (
$lastChild instanceof Ast\PhpDoc\PhpDocTagNode
&& (
$lastChild->value instanceof Doctrine\DoctrineTagValueNode
|| $lastChild->value instanceof Ast\PhpDoc\GenericTagValueNode
)
) {
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
break;
}
$lastChild = $this->parseChild($tokens);
$children[] = $lastChild;
continue;
}
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
break;
}
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
break;
}
$lastChild = $this->parseChild($tokens);
$children[] = $lastChild;
}
}
} else {
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChild($tokens); $children[] = $this->parseChild($tokens);
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChild($tokens);
}
} }
} }
@@ -105,6 +159,7 @@ class PhpDocParser
} }
/** @phpstan-impure */
private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode
{ {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) { if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
@@ -113,6 +168,26 @@ class PhpDocParser
return $this->enrichWithAttributes($tokens, $this->parseTag($tokens), $startLine, $startIndex); return $this->enrichWithAttributes($tokens, $this->parseTag($tokens), $startLine, $startIndex);
} }
if ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_TAG)) {
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
$tag = $tokens->currentTokenValue();
$tokens->next();
$tagStartLine = $tokens->currentTokenLine();
$tagStartIndex = $tokens->currentTokenIndex();
return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocTagNode(
$tag,
$this->enrichWithAttributes(
$tokens,
$this->parseDoctrineTagValue($tokens, $tag),
$tagStartLine,
$tagStartIndex
)
), $startLine, $startIndex);
}
$startLine = $tokens->currentTokenLine(); $startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex(); $startIndex = $tokens->currentTokenIndex();
$text = $this->parseText($tokens); $text = $this->parseText($tokens);
@@ -127,27 +202,14 @@ class PhpDocParser
*/ */
private function enrichWithAttributes(TokenIterator $tokens, Ast\Node $tag, int $startLine, int $startIndex): Ast\Node private function enrichWithAttributes(TokenIterator $tokens, Ast\Node $tag, int $startLine, int $startIndex): Ast\Node
{ {
$endLine = $tokens->currentTokenLine();
$endIndex = $tokens->currentTokenIndex();
if ($this->useLinesAttributes) { if ($this->useLinesAttributes) {
$tag->setAttribute(Ast\Attribute::START_LINE, $startLine); $tag->setAttribute(Ast\Attribute::START_LINE, $startLine);
$tag->setAttribute(Ast\Attribute::END_LINE, $endLine); $tag->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
} }
if ($this->useIndexAttributes) { if ($this->useIndexAttributes) {
$tokensArray = $tokens->getTokens();
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_CLOSE_PHPDOC) {
$endIndex--;
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$endIndex--;
}
} elseif ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_PHPDOC_EOL) {
$endIndex--;
}
$tag->setAttribute(Ast\Attribute::START_INDEX, $startIndex); $tag->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
$tag->setAttribute(Ast\Attribute::END_INDEX, $endIndex); $tag->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
} }
return $tag; return $tag;
@@ -158,29 +220,144 @@ class PhpDocParser
{ {
$text = ''; $text = '';
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { $endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
$text .= $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END); if ($this->textBetweenTagsBelongsToDescription) {
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
}
$savepoint = false;
// if the next token is EOL, everything below is skipped and empty string is returned
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
$text .= $tmpText;
// stop if we're not at EOL - meaning it's the end of PHPDoc
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
break; break;
} }
if ($this->textBetweenTagsBelongsToDescription) {
if (!$savepoint) {
$tokens->pushSavePoint();
$savepoint = true;
} elseif ($tmpText !== '') {
$tokens->dropSavePoint();
$tokens->pushSavePoint();
}
}
$tokens->pushSavePoint(); $tokens->pushSavePoint();
$tokens->next(); $tokens->next();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END)) { // if we're at EOL, check what's next
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) {
$tokens->rollback(); $tokens->rollback();
break; break;
} }
// otherwise if the next is text, continue building the description string
$tokens->dropSavePoint(); $tokens->dropSavePoint();
$text .= "\n"; $text .= $tokens->getDetectedNewline() ?? "\n";
}
if ($savepoint) {
$tokens->rollback();
$text = rtrim($text, $tokens->getDetectedNewline() ?? "\n");
} }
return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t")); return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t"));
} }
private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens): string
{
$text = '';
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
if ($this->textBetweenTagsBelongsToDescription) {
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
}
$savepoint = false;
// if the next token is EOL, everything below is skipped and empty string is returned
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
$text .= $tmpText;
// stop if we're not at EOL - meaning it's the end of PHPDoc
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
if (!$tokens->isPrecededByHorizontalWhitespace()) {
return trim($text . $this->parseText($tokens)->text, " \t");
}
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
$tokens->pushSavePoint();
$child = $this->parseChild($tokens);
if ($child instanceof Ast\PhpDoc\PhpDocTagNode) {
if (
$child->value instanceof Ast\PhpDoc\GenericTagValueNode
|| $child->value instanceof Doctrine\DoctrineTagValueNode
) {
$tokens->rollback();
break;
}
if ($child->value instanceof Ast\PhpDoc\InvalidTagValueNode) {
$tokens->rollback();
$tokens->pushSavePoint();
$tokens->next();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$tokens->rollback();
break;
}
$tokens->rollback();
return trim($text . $this->parseText($tokens)->text, " \t");
}
}
$tokens->rollback();
return trim($text . $this->parseText($tokens)->text, " \t");
}
break;
}
if ($this->textBetweenTagsBelongsToDescription) {
if (!$savepoint) {
$tokens->pushSavePoint();
$savepoint = true;
} elseif ($tmpText !== '') {
$tokens->dropSavePoint();
$tokens->pushSavePoint();
}
}
$tokens->pushSavePoint();
$tokens->next();
// if we're at EOL, check what's next
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) {
$tokens->rollback();
break;
}
// otherwise if the next is text, continue building the description string
$tokens->dropSavePoint();
$text .= $tokens->getDetectedNewline() ?? "\n";
}
if ($savepoint) {
$tokens->rollback();
$text = rtrim($text, $tokens->getDetectedNewline() ?? "\n");
}
return trim($text, " \t");
}
public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode
{ {
$tag = $tokens->currentTokenValue(); $tag = $tokens->currentTokenValue();
@@ -312,7 +489,17 @@ class PhpDocParser
break; break;
default: default:
if ($this->parseDoctrineAnnotations) {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$tagValue = $this->parseDoctrineTagValue($tokens, $tag);
} else {
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescriptionAfterDoctrineTag($tokens));
}
break;
}
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens)); $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
break; break;
} }
@@ -327,6 +514,297 @@ class PhpDocParser
} }
private function parseDoctrineTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
return new Doctrine\DoctrineTagValueNode(
$this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineAnnotation($tag, $this->parseDoctrineArguments($tokens, false)),
$startLine,
$startIndex
),
$this->parseOptionalDescriptionAfterDoctrineTag($tokens)
);
}
/**
* @return list<Doctrine\DoctrineArgument>
*/
private function parseDoctrineArguments(TokenIterator $tokens, bool $deep): array
{
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
return [];
}
if (!$deep) {
$tokens->addEndOfLineToSkippedTokens();
}
$arguments = [];
try {
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
do {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
break;
}
$arguments[] = $this->parseDoctrineArgument($tokens);
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
} finally {
if (!$deep) {
$tokens->removeEndOfLineFromSkippedTokens();
}
}
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
return $arguments;
}
private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\DoctrineArgument
{
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)),
$startLine,
$startIndex
);
}
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
try {
$tokens->pushSavePoint();
$currentValue = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
$key = $this->enrichWithAttributes(
$tokens,
new IdentifierTypeNode($currentValue),
$startLine,
$startIndex
);
$tokens->consumeTokenType(Lexer::TOKEN_EQUAL);
$value = $this->parseDoctrineArgumentValue($tokens);
$tokens->dropSavePoint();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArgument($key, $value),
$startLine,
$startIndex
);
} catch (ParserException $e) {
$tokens->rollback();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)),
$startLine,
$startIndex
);
}
}
/**
* @return DoctrineValueType
*/
private function parseDoctrineArgumentValue(TokenIterator $tokens)
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG)) {
$name = $tokens->currentTokenValue();
$tokens->next();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineAnnotation($name, $this->parseDoctrineArguments($tokens, true)),
$startLine,
$startIndex
);
}
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) {
$items = [];
do {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
break;
}
$items[] = $this->parseDoctrineArrayItem($tokens);
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArray($items),
$startLine,
$startIndex
);
}
$currentTokenValue = $tokens->currentTokenValue();
$tokens->pushSavePoint(); // because of ConstFetchNode
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
$identifier = $this->enrichWithAttributes(
$tokens,
new Ast\Type\IdentifierTypeNode($currentTokenValue),
$startLine,
$startIndex
);
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
$tokens->dropSavePoint();
return $identifier;
}
$tokens->rollback(); // because of ConstFetchNode
} else {
$tokens->dropSavePoint(); // because of ConstFetchNode
}
$exception = new ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER,
null,
$tokens->currentTokenLine()
);
try {
$constExpr = $this->constantExprParser->parse($tokens, true);
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
throw $exception;
}
return $constExpr;
} catch (LogicException $e) {
throw $exception;
}
}
private function parseDoctrineArrayItem(TokenIterator $tokens): Doctrine\DoctrineArrayItem
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
try {
$tokens->pushSavePoint();
$key = $this->parseDoctrineArrayKey($tokens);
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) {
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_COLON)) {
$tokens->consumeTokenType(Lexer::TOKEN_EQUAL); // will throw exception
}
}
$value = $this->parseDoctrineArgumentValue($tokens);
$tokens->dropSavePoint();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArrayItem($key, $value),
$startLine,
$startIndex
);
} catch (ParserException $e) {
$tokens->rollback();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArrayItem(null, $this->parseDoctrineArgumentValue($tokens)),
$startLine,
$startIndex
);
}
}
/**
* @return ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode
*/
private function parseDoctrineArrayKey(TokenIterator $tokens)
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
$key = new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $tokens->currentTokenValue()));
$tokens->next();
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED);
$tokens->next();
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED);
$tokens->next();
} else {
$currentTokenValue = $tokens->currentTokenValue();
$tokens->pushSavePoint(); // because of ConstFetchNode
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
$tokens->dropSavePoint();
throw new ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER,
null,
$tokens->currentTokenLine()
);
}
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
$tokens->dropSavePoint();
return $this->enrichWithAttributes(
$tokens,
new IdentifierTypeNode($currentTokenValue),
$startLine,
$startIndex
);
}
$tokens->rollback();
$constExpr = $this->constantExprParser->parse($tokens, true);
if (!$constExpr instanceof Ast\ConstExpr\ConstFetchNode) {
throw new ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER,
null,
$tokens->currentTokenLine()
);
}
return $constExpr;
}
return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex);
}
/** /**
* @return Ast\PhpDoc\ParamTagValueNode|Ast\PhpDoc\TypelessParamTagValueNode * @return Ast\PhpDoc\ParamTagValueNode|Ast\PhpDoc\TypelessParamTagValueNode
*/ */

View File

@@ -9,6 +9,7 @@ use function assert;
use function count; use function count;
use function in_array; use function in_array;
use function strlen; use function strlen;
use function substr;
class TokenIterator class TokenIterator
{ {
@@ -22,6 +23,12 @@ class TokenIterator
/** @var int[] */ /** @var int[] */
private $savePoints = []; private $savePoints = [];
/** @var list<int> */
private $skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
/** @var string|null */
private $newline = null;
/** /**
* @param list<array{string, int, int}> $tokens * @param list<array{string, int, int}> $tokens
*/ */
@@ -30,11 +37,7 @@ class TokenIterator
$this->tokens = $tokens; $this->tokens = $tokens;
$this->index = $index; $this->index = $index;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_HORIZONTAL_WS) { $this->skipIrrelevantTokens();
return;
}
$this->index++;
} }
@@ -103,6 +106,21 @@ class TokenIterator
} }
public function endIndexOfLastRelevantToken(): int
{
$endIndex = $this->currentTokenIndex();
$endIndex--;
while (in_array($this->tokens[$endIndex][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, true)) {
if (!isset($this->tokens[$endIndex - 1])) {
break;
}
$endIndex--;
}
return $endIndex;
}
public function isCurrentTokenValue(string $tokenValue): bool public function isCurrentTokenValue(string $tokenValue): bool
{ {
return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue; return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue;
@@ -130,13 +148,14 @@ class TokenIterator
$this->throwError($tokenType); $this->throwError($tokenType);
} }
$this->index++; if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) {
if ($this->newline === null) {
if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) !== Lexer::TOKEN_HORIZONTAL_WS) { $this->detectNewline();
return; }
} }
$this->index++; $this->index++;
$this->skipIrrelevantTokens();
} }
@@ -150,12 +169,7 @@ class TokenIterator
} }
$this->index++; $this->index++;
$this->skipIrrelevantTokens();
if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) !== Lexer::TOKEN_HORIZONTAL_WS) {
return;
}
$this->index++;
} }
@@ -167,10 +181,7 @@ class TokenIterator
} }
$this->index++; $this->index++;
$this->skipIrrelevantTokens();
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
}
return true; return true;
} }
@@ -183,16 +194,30 @@ class TokenIterator
return false; return false;
} }
$this->index++; if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) {
if ($this->newline === null) {
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) { $this->detectNewline();
$this->index++; }
} }
$this->index++;
$this->skipIrrelevantTokens();
return true; return true;
} }
private function detectNewline(): void
{
$value = $this->currentTokenValue();
if (substr($value, 0, 2) === "\r\n") {
$this->newline = "\r\n";
} elseif (substr($value, 0, 1) === "\n") {
$this->newline = "\n";
}
}
public function getSkippedHorizontalWhiteSpaceIfAny(): string public function getSkippedHorizontalWhiteSpaceIfAny(): string
{ {
if ($this->index > 0 && $this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) { if ($this->index > 0 && $this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
@@ -217,12 +242,34 @@ class TokenIterator
public function next(): void public function next(): void
{ {
$this->index++; $this->index++;
$this->skipIrrelevantTokens();
}
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_HORIZONTAL_WS) {
private function skipIrrelevantTokens(): void
{
if (!isset($this->tokens[$this->index])) {
return; return;
} }
$this->index++; while (in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, true)) {
if (!isset($this->tokens[$this->index + 1])) {
break;
}
$this->index++;
}
}
public function addEndOfLineToSkippedTokens(): void
{
$this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL];
}
public function removeEndOfLineFromSkippedTokens(): void
{
$this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
} }
/** @phpstan-impure */ /** @phpstan-impure */
@@ -319,6 +366,11 @@ class TokenIterator
return false; return false;
} }
public function getDetectedNewline(): ?string
{
return $this->newline;
}
/** /**
* Whether the given position is immediately surrounded by parenthesis. * Whether the given position is immediately surrounded by parenthesis.
*/ */

View File

@@ -70,23 +70,14 @@ class TypeParser
*/ */
public function enrichWithAttributes(TokenIterator $tokens, Ast\Node $type, int $startLine, int $startIndex): Ast\Node public function enrichWithAttributes(TokenIterator $tokens, Ast\Node $type, int $startLine, int $startIndex): Ast\Node
{ {
$endLine = $tokens->currentTokenLine();
$endIndex = $tokens->currentTokenIndex();
if ($this->useLinesAttributes) { if ($this->useLinesAttributes) {
$type->setAttribute(Ast\Attribute::START_LINE, $startLine); $type->setAttribute(Ast\Attribute::START_LINE, $startLine);
$type->setAttribute(Ast\Attribute::END_LINE, $endLine); $type->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
} }
if ($this->useIndexAttributes) { if ($this->useIndexAttributes) {
$tokensArray = $tokens->getTokens();
$endIndex--;
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$endIndex--;
}
$type->setAttribute(Ast\Attribute::START_INDEX, $startIndex); $type->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
$type->setAttribute(Ast\Attribute::END_INDEX, $endIndex); $type->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
} }
return $type; return $type;

View File

@@ -10,6 +10,11 @@ use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagMethodValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagMethodValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagPropertyValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagPropertyValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineAnnotation;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArgument;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArray;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArrayItem;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
@@ -95,6 +100,8 @@ final class Printer
GenericTypeNode::class . '->genericTypes' => ', ', GenericTypeNode::class . '->genericTypes' => ', ',
ConstExprArrayNode::class . '->items' => ', ', ConstExprArrayNode::class . '->items' => ', ',
MethodTagValueNode::class . '->parameters' => ', ', MethodTagValueNode::class . '->parameters' => ', ',
DoctrineArray::class . '->items' => ', ',
DoctrineAnnotation::class . '->arguments' => ', ',
]; ];
/** /**
@@ -106,6 +113,8 @@ final class Printer
CallableTypeNode::class . '->parameters' => ['(', '', ''], CallableTypeNode::class . '->parameters' => ['(', '', ''],
ArrayShapeNode::class . '->items' => ['{', '', ''], ArrayShapeNode::class . '->items' => ['{', '', ''],
ObjectShapeNode::class . '->items' => ['{', '', ''], ObjectShapeNode::class . '->items' => ['{', '', ''],
DoctrineArray::class . '->items' => ['{', '', ''],
DoctrineAnnotation::class . '->arguments' => ['(', '', ''],
]; ];
/** @var array<string, list<class-string<TypeNode>>> */ /** @var array<string, list<class-string<TypeNode>>> */
@@ -186,6 +195,10 @@ final class Printer
return $node->text; return $node->text;
} }
if ($node instanceof PhpDocTagNode) { if ($node instanceof PhpDocTagNode) {
if ($node->value instanceof DoctrineTagValueNode) {
return $this->print($node->value);
}
return trim(sprintf('%s %s', $node->name, $this->print($node->value))); return trim(sprintf('%s %s', $node->name, $this->print($node->value)));
} }
if ($node instanceof PhpDocTagValueNode) { if ($node instanceof PhpDocTagValueNode) {
@@ -211,6 +224,18 @@ final class Printer
$isOptional = $node->isOptional ? '=' : ''; $isOptional = $node->isOptional ? '=' : '';
return trim("{$type}{$isReference}{$isVariadic}{$node->parameterName}") . $isOptional; return trim("{$type}{$isReference}{$isVariadic}{$node->parameterName}") . $isOptional;
} }
if ($node instanceof DoctrineAnnotation) {
return (string) $node;
}
if ($node instanceof DoctrineArgument) {
return (string) $node;
}
if ($node instanceof DoctrineArray) {
return (string) $node;
}
if ($node instanceof DoctrineArrayItem) {
return (string) $node;
}
throw new LogicException(sprintf('Unknown node type %s', get_class($node))); throw new LogicException(sprintf('Unknown node type %s', get_class($node)));
} }
@@ -491,7 +516,7 @@ final class Printer
[$isMultiline, $beforeAsteriskIndent, $afterAsteriskIndent] = $this->isMultiline($tokenIndex, $originalNodes, $originalTokens); [$isMultiline, $beforeAsteriskIndent, $afterAsteriskIndent] = $this->isMultiline($tokenIndex, $originalNodes, $originalTokens);
if ($insertStr === "\n * ") { if ($insertStr === "\n * ") {
$insertStr = sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent); $insertStr = sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
} }
foreach ($diff as $i => $diffElem) { foreach ($diff as $i => $diffElem) {
@@ -524,7 +549,7 @@ final class Printer
} }
if ($insertNewline) { if ($insertNewline) {
$result .= $insertStr . sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent); $result .= $insertStr . sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
} else { } else {
$result .= $insertStr; $result .= $insertStr;
} }
@@ -534,7 +559,8 @@ final class Printer
} }
$parenthesesNeeded = isset($this->parenthesesListMap[$mapKey]) $parenthesesNeeded = isset($this->parenthesesListMap[$mapKey])
&& in_array(get_class($newNode), $this->parenthesesListMap[$mapKey], true); && in_array(get_class($newNode), $this->parenthesesListMap[$mapKey], true)
&& !in_array(get_class($originalNode), $this->parenthesesListMap[$mapKey], true);
$addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($itemStartPos, $itemEndPos); $addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($itemStartPos, $itemEndPos);
if ($addParentheses) { if ($addParentheses) {
$result .= '('; $result .= '(';
@@ -567,7 +593,7 @@ final class Printer
$itemEndPos = $tokenIndex - 1; $itemEndPos = $tokenIndex - 1;
if ($insertNewline) { if ($insertNewline) {
$result .= $insertStr . sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent); $result .= $insertStr . sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
} else { } else {
$result .= $insertStr; $result .= $insertStr;
} }
@@ -636,7 +662,7 @@ final class Printer
if (!$first) { if (!$first) {
$result .= $insertStr; $result .= $insertStr;
if ($insertNewline) { if ($insertNewline) {
$result .= sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent); $result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
} }
} }
@@ -777,6 +803,12 @@ final class Printer
$mapKey = get_class($node) . '->' . $subNodeName; $mapKey = get_class($node) . '->' . $subNodeName;
$parenthesesNeeded = isset($this->parenthesesMap[$mapKey]) $parenthesesNeeded = isset($this->parenthesesMap[$mapKey])
&& in_array(get_class($subNode), $this->parenthesesMap[$mapKey], true); && in_array(get_class($subNode), $this->parenthesesMap[$mapKey], true);
if ($subNode->getAttribute(Attribute::ORIGINAL_NODE) !== null) {
$parenthesesNeeded = $parenthesesNeeded
&& !in_array(get_class($subNode->getAttribute(Attribute::ORIGINAL_NODE)), $this->parenthesesMap[$mapKey], true);
}
$addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($subStartPos, $subEndPos); $addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($subStartPos, $subEndPos);
if ($addParentheses) { if ($addParentheses) {
$result .= '('; $result .= '(';

Binary file not shown.

View File

@@ -1,16 +1,16 @@
-----BEGIN PGP SIGNATURE----- -----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmRaZmcACgkQUcZzBf/C iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmS32poACgkQUcZzBf/C
5cAn/Q//fbWiR/qaSvlHpk73KH7iDfoHwNvRrHSQODZdMa4PGiEbL+SXsKnRxFDo 5cCvmg/+JJmyX663fa+FHy7ED2SexVuChivpbp82dyLx1gRAl15rtNG4zjxNRfnW
kEJZwgU5qi3WMflt7Ml3dYDlQDgoDerdaiySYFoBcv1NXDWKoF7+Egy1AHxpfNq+ 6GpsysMhKqrN7p6xur18ZkLqdFKAjeNnpTunnh/ADetcrs8wzLNyAy7luQtyXAuj
FMCkZNR2ulSaYUCofM4GkTNap4yVkPCy289ZU6yUmRnJxF+hh/CFfdVPAPbwh/a6 SOv5f/Yitg9yvZ2GHrbzchQuSjkbUR2KroBYsRhwVTH7pMIgdvysRBYiENfbz350
UqV3R2ENJZSbtA1pzSTBpUPQGQ9qcsqngKyNyxk1hEd9opdMg2eSFvO1e1ZZm/Tk n91WOCApDnVCygzEhBbhkwA/xklJnUxkRJX3AlbbCwES9K64ELyGd0BqJ1Ohy2a7
Kgh5wCbsbSJuRPGO4vbiybTeO/qXPDlHV6oA5SHnjJ4H24phCsHdyJHHvLQmrUeR 4cFjwRJq9/tXf99fyncamN8xyBdvYBXSNRNMPYcjKqKIZCOePlR8Q3b7nt154w+e
BKHgnH1y/b5J9cgr9OgEQJK9TMHHd6dii9//Qp+0rUZIDZ4Ym2lDSA/Vn/D9GoV3 w2qnAevOB4dYzJaSjwJlaVQYR1YIQ7NlYkGboONq/lrtJlEejDdiRmGvgHZ8nSYW
zo4QYzW3TvE3QMdnLcX/ZtaLliPdDYIaYUXOiyaYwLFGVxSWZWOC5IN0G0bLJb39 Ob2JwqgYDfUPfsnXAwXM+whpUNJi30MDB7MSw3SiDlyw690HheT/DCKOJ9yNUiOB
Ca/z839nkWdMqg68q/oHC2Nk/v/KZnKg1RlRjYhj53T6nr0JDEiaYMyETSOIFsVX TSGkbIGW/ASett78gowjwniYdryE5ufUPwZbkSaFC3CDysHfs6Jgc+lxe3wnOHtD
AcCQnLLwMndUAibJAyORDnTk+ipg0SecFoPvvhea1BtlTfhSDIlrT4OPKZ5nExzd WyPl1TqDRNuLOZ26TgxI3gGEYqMcVDYQfmuiOakoebHx6j0bpvyEaP51j0/JFpu6
nR/zGbIH8lCvsBc+hq+Kgodtfs5nauwEOwlVUwet26xL1YKOd0jxz+Zp6tgk0wba okKulXgC1DUluKFWMPhobPQRZ8zC29macnU74JvmJIiUhfiP2Pl16D+XcjFW++zH
cMf5L9fm85j83DQYr7Ukaaj81kmMujRWDo/dRojKhUlJUrNnjXA= EDEghcCdgz0pIF6UI5j02rbNAfu7Oo685pnYeXq0DexgXjqoFOE=
=jTtX =NF4z
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----

View File

@@ -2,6 +2,21 @@
All notable changes to `array-to-xml` will be documented in this file All notable changes to `array-to-xml` will be documented in this file
## 3.1.6 - 2023-05-11
### What's Changed
- V3 - Code smell ('incorrect' method call) by @ExeQue in https://github.com/spatie/array-to-xml/pull/208
- Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/array-to-xml/pull/210
- Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/array-to-xml/pull/214
- Add addXmlDeclaration parameter by @silnex in https://github.com/spatie/array-to-xml/pull/216
### New Contributors
- @silnex made their first contribution in https://github.com/spatie/array-to-xml/pull/216
**Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.5...3.1.6
## 3.1.5 - 2022-12-24 ## 3.1.5 - 2022-12-24
### What's Changed ### What's Changed

View File

@@ -239,6 +239,58 @@ This will result in:
</helloyouluckypeople> </helloyouluckypeople>
``` ```
### Using Closure values
The package can use Closure values:
```php
$users = [
[
'name' => 'one',
'age' => 10,
],
[
'name' => 'two',
'age' => 12,
],
];
$array = [
'users' => function () use ($users) {
$new_users = [];
foreach ($users as $user) {
$new_users[] = array_merge(
$user,
[
'double_age' => $user['age'] * 2,
]
);
}
return $new_users;
},
];
ArrayToXml::convert($array)
```
This will result in:
```xml
<?xml version="1.0"?>
<root>
<users>
<name>one</name>
<age>10</age>
<double_age>20</double_age>
</users>
<users>
<name>two</name>
<age>12</age>
<double_age>24</double_age>
</users>
</root>
```
### Handling numeric keys ### Handling numeric keys
The package can also can handle numeric keys: The package can also can handle numeric keys:

View File

@@ -2,6 +2,7 @@
namespace Spatie\ArrayToXml; namespace Spatie\ArrayToXml;
use Closure;
use DOMDocument; use DOMDocument;
use DOMElement; use DOMElement;
use DOMException; use DOMException;
@@ -143,6 +144,10 @@ class ArrayToXml
protected function convertElement(DOMElement $element, mixed $value): void protected function convertElement(DOMElement $element, mixed $value): void
{ {
if ($value instanceof Closure) {
$value = $value();
}
$sequential = $this->isArrayAllKeySequential($value); $sequential = $this->isArrayAllKeySequential($value);
if (! is_array($value)) { if (! is_array($value)) {

View File

@@ -105,11 +105,14 @@ class Application implements ResetInterface
/** /**
* @final * @final
*/ */
public function setDispatcher(EventDispatcherInterface $dispatcher) public function setDispatcher(EventDispatcherInterface $dispatcher): void
{ {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
} }
/**
* @return void
*/
public function setCommandLoader(CommandLoaderInterface $commandLoader) public function setCommandLoader(CommandLoaderInterface $commandLoader)
{ {
$this->commandLoader = $commandLoader; $this->commandLoader = $commandLoader;
@@ -118,12 +121,15 @@ class Application implements ResetInterface
public function getSignalRegistry(): SignalRegistry public function getSignalRegistry(): SignalRegistry
{ {
if (!$this->signalRegistry) { if (!$this->signalRegistry) {
throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); throw new RuntimeException('Signals are not supported. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
} }
return $this->signalRegistry; return $this->signalRegistry;
} }
/**
* @return void
*/
public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent) public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
{ {
$this->signalsToDispatchEvent = $signalsToDispatchEvent; $this->signalsToDispatchEvent = $signalsToDispatchEvent;
@@ -317,10 +323,16 @@ class Application implements ResetInterface
return $exitCode; return $exitCode;
} }
/**
* @return void
*/
public function reset() public function reset()
{ {
} }
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet) public function setHelperSet(HelperSet $helperSet)
{ {
$this->helperSet = $helperSet; $this->helperSet = $helperSet;
@@ -334,6 +346,9 @@ class Application implements ResetInterface
return $this->helperSet ??= $this->getDefaultHelperSet(); return $this->helperSet ??= $this->getDefaultHelperSet();
} }
/**
* @return void
*/
public function setDefinition(InputDefinition $definition) public function setDefinition(InputDefinition $definition)
{ {
$this->definition = $definition; $this->definition = $definition;
@@ -404,6 +419,8 @@ class Application implements ResetInterface
/** /**
* Sets whether to catch exceptions or not during commands execution. * Sets whether to catch exceptions or not during commands execution.
*
* @return void
*/ */
public function setCatchExceptions(bool $boolean) public function setCatchExceptions(bool $boolean)
{ {
@@ -420,6 +437,8 @@ class Application implements ResetInterface
/** /**
* Sets whether to automatically exit after a command execution or not. * Sets whether to automatically exit after a command execution or not.
*
* @return void
*/ */
public function setAutoExit(bool $boolean) public function setAutoExit(bool $boolean)
{ {
@@ -436,7 +455,9 @@ class Application implements ResetInterface
/** /**
* Sets the application name. * Sets the application name.
**/ *
* @return void
*/
public function setName(string $name) public function setName(string $name)
{ {
$this->name = $name; $this->name = $name;
@@ -452,6 +473,8 @@ class Application implements ResetInterface
/** /**
* Sets the application version. * Sets the application version.
*
* @return void
*/ */
public function setVersion(string $version) public function setVersion(string $version)
{ {
@@ -490,6 +513,8 @@ class Application implements ResetInterface
* If a Command is not enabled it will not be added. * If a Command is not enabled it will not be added.
* *
* @param Command[] $commands An array of commands * @param Command[] $commands An array of commands
*
* @return void
*/ */
public function addCommands(array $commands) public function addCommands(array $commands)
{ {
@@ -687,9 +712,7 @@ class Application implements ResetInterface
if ($alternatives = $this->findAlternatives($name, $allCommands)) { if ($alternatives = $this->findAlternatives($name, $allCommands)) {
// remove hidden commands // remove hidden commands
$alternatives = array_filter($alternatives, function ($name) { $alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden());
return !$this->get($name)->isHidden();
});
if (1 == \count($alternatives)) { if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n "; $message .= "\n\nDid you mean this?\n ";
@@ -840,9 +863,7 @@ class Application implements ResetInterface
} }
if (str_contains($message, "@anonymous\0")) { if (str_contains($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $message);
} }
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
@@ -903,6 +924,8 @@ class Application implements ResetInterface
/** /**
* Configures the input and output instances based on the user arguments and options. * Configures the input and output instances based on the user arguments and options.
*
* @return void
*/ */
protected function configureIO(InputInterface $input, OutputInterface $output) protected function configureIO(InputInterface $input, OutputInterface $output)
{ {
@@ -977,44 +1000,62 @@ class Application implements ResetInterface
} }
} }
if ($this->signalsToDispatchEvent) { $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
if (!$this->signalRegistry) {
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
if ($commandSignals || null !== $this->dispatcher) { if (Terminal::hasSttyAvailable()) {
if (!$this->signalRegistry) { $sttyMode = shell_exec('stty -g');
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
if (Terminal::hasSttyAvailable()) { foreach ([\SIGINT, \SIGTERM] as $signal) {
$sttyMode = shell_exec('stty -g'); $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode));
foreach ([\SIGINT, \SIGTERM] as $signal) {
$this->signalRegistry->register($signal, static function () use ($sttyMode) {
shell_exec('stty '.$sttyMode);
});
}
} }
} }
if (null !== $this->dispatcher) { if ($this->dispatcher) {
// We register application signals, so that we can dispatch the event
foreach ($this->signalsToDispatchEvent as $signal) { foreach ($this->signalsToDispatchEvent as $signal) {
$event = new ConsoleSignalEvent($command, $input, $output, $signal); $event = new ConsoleSignalEvent($command, $input, $output, $signal);
$this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) { $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
$exitCode = $event->getExitCode();
// No more handlers, we try to simulate PHP default behavior // If the command is signalable, we call the handleSignal() method
if (!$hasNext) { if (\in_array($signal, $commandSignals, true)) {
if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) { $exitCode = $command->handleSignal($signal, $exitCode);
exit(0); // BC layer for Symfony <= 5
if (null === $exitCode) {
trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
$exitCode = 0;
} }
} }
if (false !== $exitCode) {
exit($exitCode);
}
}); });
} }
// then we register command signals, but not if already handled after the dispatcher
$commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent);
} }
foreach ($commandSignals as $signal) { foreach ($commandSignals as $signal) {
$this->signalRegistry->register($signal, [$command, 'handleSignal']); $this->signalRegistry->register($signal, function (int $signal) use ($command): void {
$exitCode = $command->handleSignal($signal);
// BC layer for Symfony <= 5
if (null === $exitCode) {
trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
$exitCode = 0;
}
if (false !== $exitCode) {
exit($exitCode);
}
});
} }
} }
@@ -1170,7 +1211,7 @@ class Application implements ResetInterface
} }
} }
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold);
ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
return array_keys($alternatives); return array_keys($alternatives);
@@ -1261,7 +1302,7 @@ class Application implements ResetInterface
return $namespaces; return $namespaces;
} }
private function init() private function init(): void
{ {
if ($this->initialized) { if ($this->initialized) {
return; return;

View File

@@ -1,6 +1,13 @@
CHANGELOG CHANGELOG
========= =========
6.3
---
* Add support for choosing exit code while handling signal, or to not exit at all
* Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global.
* Add `ReStructuredTextDescriptor`
6.2 6.2
--- ---

View File

@@ -141,12 +141,17 @@ class Command
* Ignores validation errors. * Ignores validation errors.
* *
* This is mainly useful for the help command. * This is mainly useful for the help command.
*
* @return void
*/ */
public function ignoreValidationErrors() public function ignoreValidationErrors()
{ {
$this->ignoreValidationErrors = true; $this->ignoreValidationErrors = true;
} }
/**
* @return void
*/
public function setApplication(Application $application = null) public function setApplication(Application $application = null)
{ {
if (1 > \func_num_args()) { if (1 > \func_num_args()) {
@@ -162,6 +167,9 @@ class Command
$this->fullDefinition = null; $this->fullDefinition = null;
} }
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet) public function setHelperSet(HelperSet $helperSet)
{ {
$this->helperSet = $helperSet; $this->helperSet = $helperSet;
@@ -198,6 +206,8 @@ class Command
/** /**
* Configures the current command. * Configures the current command.
*
* @return void
*/ */
protected function configure() protected function configure()
{ {
@@ -228,6 +238,8 @@ class Command
* This method is executed before the InputDefinition is validated. * This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can * This means that this is the only place where the command can
* interactively ask for values of missing required arguments. * interactively ask for values of missing required arguments.
*
* @return void
*/ */
protected function interact(InputInterface $input, OutputInterface $output) protected function interact(InputInterface $input, OutputInterface $output)
{ {
@@ -242,6 +254,8 @@ class Command
* *
* @see InputInterface::bind() * @see InputInterface::bind()
* @see InputInterface::validate() * @see InputInterface::validate()
*
* @return void
*/ */
protected function initialize(InputInterface $input, OutputInterface $output) protected function initialize(InputInterface $input, OutputInterface $output)
{ {
@@ -378,7 +392,7 @@ class Command
* *
* @internal * @internal
*/ */
public function mergeApplicationDefinition(bool $mergeArgs = true) public function mergeApplicationDefinition(bool $mergeArgs = true): void
{ {
if (null === $this->application) { if (null === $this->application) {
return; return;
@@ -702,7 +716,7 @@ class Command
* *
* @throws InvalidArgumentException When the name is invalid * @throws InvalidArgumentException When the name is invalid
*/ */
private function validateName(string $name) private function validateName(string $name): void
{ {
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));

View File

@@ -74,7 +74,7 @@ final class CompleteCommand extends Command
; ;
} }
protected function initialize(InputInterface $input, OutputInterface $output) protected function initialize(InputInterface $input, OutputInterface $output): void
{ {
$this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL); $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL);
} }
@@ -134,12 +134,12 @@ final class CompleteCommand extends Command
$completionInput->bind($command->getDefinition()); $completionInput->bind($command->getDefinition());
if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
$this->log(' Completing option names for the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> command.'); $this->log(' Completing option names for the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> command.');
$suggestions->suggestOptions($command->getDefinition()->getOptions()); $suggestions->suggestOptions($command->getDefinition()->getOptions());
} else { } else {
$this->log([ $this->log([
' Completing using the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> class.', ' Completing using the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> class.',
' Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>', ' Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>',
]); ]);
if (null !== $compval = $completionInput->getCompletionValue()) { if (null !== $compval = $completionInput->getCompletionValue()) {
@@ -155,7 +155,7 @@ final class CompleteCommand extends Command
$this->log('<info>Suggestions:</>'); $this->log('<info>Suggestions:</>');
if ($options = $suggestions->getOptionSuggestions()) { if ($options = $suggestions->getOptionSuggestions()) {
$this->log(' --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options))); $this->log(' --'.implode(' --', array_map(fn ($o) => $o->getName(), $options)));
} elseif ($values = $suggestions->getValueSuggestions()) { } elseif ($values = $suggestions->getValueSuggestions()) {
$this->log(' '.implode(' ', $values)); $this->log(' '.implode(' ', $values));
} else { } else {

View File

@@ -39,7 +39,7 @@ final class DumpCompletionCommand extends Command
private array $supportedShells; private array $supportedShells;
protected function configure() protected function configure(): void
{ {
$fullCommand = $_SERVER['PHP_SELF']; $fullCommand = $_SERVER['PHP_SELF'];
$commandName = basename($fullCommand); $commandName = basename($fullCommand);

View File

@@ -27,6 +27,9 @@ class HelpCommand extends Command
{ {
private Command $command; private Command $command;
/**
* @return void
*/
protected function configure() protected function configure()
{ {
$this->ignoreValidationErrors(); $this->ignoreValidationErrors();
@@ -34,12 +37,8 @@ class HelpCommand extends Command
$this $this
->setName('help') ->setName('help')
->setDefinition([ ->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', function () { new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())),
return array_keys((new ApplicationDescription($this->getApplication()))->getCommands()); new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
}),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
return (new DescriptorHelper())->getFormats();
}),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
]) ])
->setDescription('Display help for a command') ->setDescription('Display help for a command')
@@ -58,6 +57,9 @@ EOF
; ;
} }
/**
* @return void
*/
public function setCommand(Command $command) public function setCommand(Command $command)
{ {
$this->command = $command; $this->command = $command;

View File

@@ -25,18 +25,17 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class ListCommand extends Command class ListCommand extends Command
{ {
/**
* @return void
*/
protected function configure() protected function configure()
{ {
$this $this
->setName('list') ->setName('list')
->setDefinition([ ->setDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () { new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())),
return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces());
}),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () { new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
return (new DescriptorHelper())->getFormats();
}),
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
]) ])
->setDescription('List commands') ->setDescription('List commands')

View File

@@ -32,7 +32,7 @@ trait LockableTrait
private function lock(string $name = null, bool $blocking = false): bool private function lock(string $name = null, bool $blocking = false): bool
{ {
if (!class_exists(SemaphoreStore::class)) { if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.'); throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".');
} }
if (null !== $this->lock) { if (null !== $this->lock) {
@@ -58,7 +58,7 @@ trait LockableTrait
/** /**
* Releases the command lock if there is one. * Releases the command lock if there is one.
*/ */
private function release() private function release(): void
{ {
if ($this->lock) { if ($this->lock) {
$this->lock->release(); $this->lock->release();

View File

@@ -25,6 +25,10 @@ interface SignalableCommandInterface
/** /**
* The method will be called when the application is signaled. * The method will be called when the application is signaled.
*
* @param int|false $previousExitCode
* @return int|false The exit code to return or false to continue the normal execution
*/ */
public function handleSignal(int $signal): void; public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */);
} }

View File

@@ -34,7 +34,7 @@ final class CompletionInput extends ArgvInput
private $tokens; private $tokens;
private $currentIndex; private $currentIndex;
private $completionType; private $completionType;
private $completionName = null; private $completionName;
private $completionValue = ''; private $completionValue = '';
/** /**

View File

@@ -29,6 +29,9 @@ use Symfony\Component\DependencyInjection\TypedReference;
*/ */
class AddConsoleCommandPass implements CompilerPassInterface class AddConsoleCommandPass implements CompilerPassInterface
{ {
/**
* @return void
*/
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$commandServices = $container->findTaggedServiceIds('console.command', true); $commandServices = $container->findTaggedServiceIds('console.command', true);

View File

@@ -79,7 +79,7 @@ class ApplicationDescription
return $this->commands[$name] ?? $this->aliases[$name]; return $this->commands[$name] ?? $this->aliases[$name];
} }
private function inspectApplication() private function inspectApplication(): void
{ {
$this->commands = []; $this->commands = [];
$this->namespaces = []; $this->namespaces = [];

View File

@@ -26,12 +26,9 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
abstract class Descriptor implements DescriptorInterface abstract class Descriptor implements DescriptorInterface
{ {
/** protected OutputInterface $output;
* @var OutputInterface
*/
protected $output;
public function describe(OutputInterface $output, object $object, array $options = []) public function describe(OutputInterface $output, object $object, array $options = []): void
{ {
$this->output = $output; $this->output = $output;
@@ -45,10 +42,7 @@ abstract class Descriptor implements DescriptorInterface
}; };
} }
/** protected function write(string $content, bool $decorated = false): void
* Writes content to output.
*/
protected function write(string $content, bool $decorated = false)
{ {
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
} }
@@ -56,25 +50,25 @@ abstract class Descriptor implements DescriptorInterface
/** /**
* Describes an InputArgument instance. * Describes an InputArgument instance.
*/ */
abstract protected function describeInputArgument(InputArgument $argument, array $options = []); abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void;
/** /**
* Describes an InputOption instance. * Describes an InputOption instance.
*/ */
abstract protected function describeInputOption(InputOption $option, array $options = []); abstract protected function describeInputOption(InputOption $option, array $options = []): void;
/** /**
* Describes an InputDefinition instance. * Describes an InputDefinition instance.
*/ */
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void;
/** /**
* Describes a Command instance. * Describes a Command instance.
*/ */
abstract protected function describeCommand(Command $command, array $options = []); abstract protected function describeCommand(Command $command, array $options = []): void;
/** /**
* Describes an Application instance. * Describes an Application instance.
*/ */
abstract protected function describeApplication(Application $application, array $options = []); abstract protected function describeApplication(Application $application, array $options = []): void;
} }

View File

@@ -20,5 +20,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
interface DescriptorInterface interface DescriptorInterface
{ {
/**
* @return void
*/
public function describe(OutputInterface $output, object $object, array $options = []); public function describe(OutputInterface $output, object $object, array $options = []);
} }

View File

@@ -26,12 +26,12 @@ use Symfony\Component\Console\Input\InputOption;
*/ */
class JsonDescriptor extends Descriptor class JsonDescriptor extends Descriptor
{ {
protected function describeInputArgument(InputArgument $argument, array $options = []) protected function describeInputArgument(InputArgument $argument, array $options = []): void
{ {
$this->writeData($this->getInputArgumentData($argument), $options); $this->writeData($this->getInputArgumentData($argument), $options);
} }
protected function describeInputOption(InputOption $option, array $options = []) protected function describeInputOption(InputOption $option, array $options = []): void
{ {
$this->writeData($this->getInputOptionData($option), $options); $this->writeData($this->getInputOptionData($option), $options);
if ($option->isNegatable()) { if ($option->isNegatable()) {
@@ -39,17 +39,17 @@ class JsonDescriptor extends Descriptor
} }
} }
protected function describeInputDefinition(InputDefinition $definition, array $options = []) protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{ {
$this->writeData($this->getInputDefinitionData($definition), $options); $this->writeData($this->getInputDefinitionData($definition), $options);
} }
protected function describeCommand(Command $command, array $options = []) protected function describeCommand(Command $command, array $options = []): void
{ {
$this->writeData($this->getCommandData($command, $options['short'] ?? false), $options); $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
} }
protected function describeApplication(Application $application, array $options = []) protected function describeApplication(Application $application, array $options = []): void
{ {
$describedNamespace = $options['namespace'] ?? null; $describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace, true); $description = new ApplicationDescription($application, $describedNamespace, true);
@@ -81,7 +81,7 @@ class JsonDescriptor extends Descriptor
/** /**
* Writes data as json. * Writes data as json.
*/ */
private function writeData(array $data, array $options) private function writeData(array $data, array $options): void
{ {
$flags = $options['json_encoding'] ?? 0; $flags = $options['json_encoding'] ?? 0;

View File

@@ -28,7 +28,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class MarkdownDescriptor extends Descriptor class MarkdownDescriptor extends Descriptor
{ {
public function describe(OutputInterface $output, object $object, array $options = []) public function describe(OutputInterface $output, object $object, array $options = []): void
{ {
$decorated = $output->isDecorated(); $decorated = $output->isDecorated();
$output->setDecorated(false); $output->setDecorated(false);
@@ -38,12 +38,12 @@ class MarkdownDescriptor extends Descriptor
$output->setDecorated($decorated); $output->setDecorated($decorated);
} }
protected function write(string $content, bool $decorated = true) protected function write(string $content, bool $decorated = true): void
{ {
parent::write($content, $decorated); parent::write($content, $decorated);
} }
protected function describeInputArgument(InputArgument $argument, array $options = []) protected function describeInputArgument(InputArgument $argument, array $options = []): void
{ {
$this->write( $this->write(
'#### `'.($argument->getName() ?: '<none>')."`\n\n" '#### `'.($argument->getName() ?: '<none>')."`\n\n"
@@ -54,7 +54,7 @@ class MarkdownDescriptor extends Descriptor
); );
} }
protected function describeInputOption(InputOption $option, array $options = []) protected function describeInputOption(InputOption $option, array $options = []): void
{ {
$name = '--'.$option->getName(); $name = '--'.$option->getName();
if ($option->isNegatable()) { if ($option->isNegatable()) {
@@ -75,15 +75,13 @@ class MarkdownDescriptor extends Descriptor
); );
} }
protected function describeInputDefinition(InputDefinition $definition, array $options = []) protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{ {
if ($showArguments = \count($definition->getArguments()) > 0) { if ($showArguments = \count($definition->getArguments()) > 0) {
$this->write('### Arguments'); $this->write('### Arguments');
foreach ($definition->getArguments() as $argument) { foreach ($definition->getArguments() as $argument) {
$this->write("\n\n"); $this->write("\n\n");
if (null !== $describeInputArgument = $this->describeInputArgument($argument)) { $this->describeInputArgument($argument);
$this->write($describeInputArgument);
}
} }
} }
@@ -95,14 +93,12 @@ class MarkdownDescriptor extends Descriptor
$this->write('### Options'); $this->write('### Options');
foreach ($definition->getOptions() as $option) { foreach ($definition->getOptions() as $option) {
$this->write("\n\n"); $this->write("\n\n");
if (null !== $describeInputOption = $this->describeInputOption($option)) { $this->describeInputOption($option);
$this->write($describeInputOption);
}
} }
} }
} }
protected function describeCommand(Command $command, array $options = []) protected function describeCommand(Command $command, array $options = []): void
{ {
if ($options['short'] ?? false) { if ($options['short'] ?? false) {
$this->write( $this->write(
@@ -110,9 +106,7 @@ class MarkdownDescriptor extends Descriptor
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '') .($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n" .'### Usage'."\n\n"
.array_reduce($command->getAliases(), function ($carry, $usage) { .array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
return $carry.'* `'.$usage.'`'."\n";
})
); );
return; return;
@@ -125,9 +119,7 @@ class MarkdownDescriptor extends Descriptor
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '') .($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n" .'### Usage'."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
return $carry.'* `'.$usage.'`'."\n";
})
); );
if ($help = $command->getProcessedHelp()) { if ($help = $command->getProcessedHelp()) {
@@ -142,7 +134,7 @@ class MarkdownDescriptor extends Descriptor
} }
} }
protected function describeApplication(Application $application, array $options = []) protected function describeApplication(Application $application, array $options = []): void
{ {
$describedNamespace = $options['namespace'] ?? null; $describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace); $description = new ApplicationDescription($application, $describedNamespace);
@@ -157,16 +149,12 @@ class MarkdownDescriptor extends Descriptor
} }
$this->write("\n\n"); $this->write("\n\n");
$this->write(implode("\n", array_map(function ($commandName) use ($description) { $this->write(implode("\n", array_map(fn ($commandName) => sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands'])));
return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
}, $namespace['commands'])));
} }
foreach ($description->getCommands() as $command) { foreach ($description->getCommands() as $command) {
$this->write("\n\n"); $this->write("\n\n");
if (null !== $describeCommand = $this->describeCommand($command, $options)) { $this->describeCommand($command, $options);
$this->write($describeCommand);
}
} }
} }

View File

@@ -0,0 +1,272 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\String\UnicodeString;
class ReStructuredTextDescriptor extends Descriptor
{
// <h1>
private string $partChar = '=';
// <h2>
private string $chapterChar = '-';
// <h3>
private string $sectionChar = '~';
// <h4>
private string $subsectionChar = '.';
// <h5>
private string $subsubsectionChar = '^';
// <h6>
private string $paragraphsChar = '"';
private array $visibleNamespaces = [];
public function describe(OutputInterface $output, object $object, array $options = []): void
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
parent::describe($output, $object, $options);
$output->setDecorated($decorated);
}
/**
* Override parent method to set $decorated = true.
*/
protected function write(string $content, bool $decorated = true): void
{
parent::write($content, $decorated);
}
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->write(
$argument->getName() ?: '<none>'."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n"
.($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
.'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``'
);
}
protected function describeInputOption(InputOption $option, array $options = []): void
{
$name = '\-\-'.$option->getName();
if ($option->isNegatable()) {
$name .= '|\-\-no-'.$option->getName();
}
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut());
}
$optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : '';
$optionDescription = (new UnicodeString($optionDescription))->ascii();
$this->write(
$name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n"
.$optionDescription
.'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n"
.'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n"
.'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n"
);
}
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
if ($showArguments = ((bool) $definition->getArguments())) {
$this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n";
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
$this->describeInputArgument($argument);
}
}
if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) {
if ($showArguments) {
$this->write("\n\n");
}
$this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n");
foreach ($nonDefaultOptions as $option) {
$this->describeInputOption($option);
$this->write("\n");
}
}
}
protected function describeCommand(Command $command, array $options = []): void
{
if ($options['short'] ?? false) {
$this->write(
'``'.$command->getName()."``\n"
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n"
.array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
);
return;
}
$command->mergeApplicationDefinition(false);
foreach ($command->getAliases() as $alias) {
$this->write('.. _'.$alias.":\n\n");
}
$this->write(
$command->getName()."\n"
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
);
if ($help = $command->getProcessedHelp()) {
$this->write("\n");
$this->write($help);
}
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->write("\n\n");
$this->describeInputDefinition($definition);
}
}
protected function describeApplication(Application $application, array $options = []): void
{
$description = new ApplicationDescription($application, $options['namespace'] ?? null);
$title = $this->getApplicationTitle($application);
$this->write($title."\n".str_repeat($this->partChar, Helper::width($title)));
$this->createTableOfContents($description, $application);
$this->describeCommands($application, $options);
}
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' === $application->getName()) {
return 'Console Tool';
}
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
private function describeCommands($application, array $options): void
{
$title = 'Commands';
$this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n");
foreach ($this->visibleNamespaces as $namespace) {
if ('_global' === $namespace) {
$commands = $application->all('');
$this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n");
} else {
$commands = $application->all($namespace);
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
}
foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) {
$this->describeCommand($command, $options);
$this->write("\n\n");
}
}
}
private function createTableOfContents(ApplicationDescription $description, Application $application): void
{
$this->setVisibleNamespaces($description);
$chapterTitle = 'Table of Contents';
$this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n");
foreach ($this->visibleNamespaces as $namespace) {
if ('_global' === $namespace) {
$commands = $application->all('');
} else {
$commands = $application->all($namespace);
$this->write("\n\n");
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
}
$commands = $this->removeAliasesAndHiddenCommands($commands);
$this->write("\n\n");
$this->write(implode("\n", array_map(static fn ($commandName) => sprintf('- `%s`_', $commandName), array_keys($commands))));
}
}
private function getNonDefaultOptions(InputDefinition $definition): array
{
$globalOptions = [
'help',
'quiet',
'verbose',
'version',
'ansi',
'no-interaction',
];
$nonDefaultOptions = [];
foreach ($definition->getOptions() as $option) {
// Skip global options.
if (!\in_array($option->getName(), $globalOptions)) {
$nonDefaultOptions[] = $option;
}
}
return $nonDefaultOptions;
}
private function setVisibleNamespaces(ApplicationDescription $description): void
{
$commands = $description->getCommands();
foreach ($description->getNamespaces() as $namespace) {
try {
$namespaceCommands = $namespace['commands'];
foreach ($namespaceCommands as $key => $commandName) {
if (!\array_key_exists($commandName, $commands)) {
// If the array key does not exist, then this is an alias.
unset($namespaceCommands[$key]);
} elseif ($commands[$commandName]->isHidden()) {
unset($namespaceCommands[$key]);
}
}
if (!$namespaceCommands) {
// If the namespace contained only aliases or hidden commands, skip the namespace.
continue;
}
} catch (\Exception) {
}
$this->visibleNamespaces[] = $namespace['id'];
}
}
private function removeAliasesAndHiddenCommands(array $commands): array
{
foreach ($commands as $key => $command) {
if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) {
unset($commands[$key]);
}
}
unset($commands['completion']);
return $commands;
}
}

View File

@@ -28,7 +28,7 @@ use Symfony\Component\Console\Input\InputOption;
*/ */
class TextDescriptor extends Descriptor class TextDescriptor extends Descriptor
{ {
protected function describeInputArgument(InputArgument $argument, array $options = []) protected function describeInputArgument(InputArgument $argument, array $options = []): void
{ {
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault())); $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
@@ -48,7 +48,7 @@ class TextDescriptor extends Descriptor
), $options); ), $options);
} }
protected function describeInputOption(InputOption $option, array $options = []) protected function describeInputOption(InputOption $option, array $options = []): void
{ {
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault())); $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
@@ -83,7 +83,7 @@ class TextDescriptor extends Descriptor
), $options); ), $options);
} }
protected function describeInputDefinition(InputDefinition $definition, array $options = []) protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{ {
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) { foreach ($definition->getArguments() as $argument) {
@@ -122,7 +122,7 @@ class TextDescriptor extends Descriptor
} }
} }
protected function describeCommand(Command $command, array $options = []) protected function describeCommand(Command $command, array $options = []): void
{ {
$command->mergeApplicationDefinition(false); $command->mergeApplicationDefinition(false);
@@ -157,7 +157,7 @@ class TextDescriptor extends Descriptor
} }
} }
protected function describeApplication(Application $application, array $options = []) protected function describeApplication(Application $application, array $options = []): void
{ {
$describedNamespace = $options['namespace'] ?? null; $describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace); $description = new ApplicationDescription($application, $describedNamespace);
@@ -193,9 +193,7 @@ class TextDescriptor extends Descriptor
} }
// calculate max. width based on available commands per namespace // calculate max. width based on available commands per namespace
$width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) { $width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));
return array_intersect($namespace['commands'], array_keys($commands));
}, array_values($namespaces)))));
if ($describedNamespace) { if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options); $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
@@ -204,9 +202,7 @@ class TextDescriptor extends Descriptor
} }
foreach ($namespaces as $namespace) { foreach ($namespaces as $namespace) {
$namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { $namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name]));
return isset($commands[$name]);
});
if (!$namespace['commands']) { if (!$namespace['commands']) {
continue; continue;
@@ -230,7 +226,7 @@ class TextDescriptor extends Descriptor
} }
} }
private function writeText(string $content, array $options = []) private function writeText(string $content, array $options = []): void
{ {
$this->write( $this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,

View File

@@ -120,27 +120,27 @@ class XmlDescriptor extends Descriptor
return $dom; return $dom;
} }
protected function describeInputArgument(InputArgument $argument, array $options = []) protected function describeInputArgument(InputArgument $argument, array $options = []): void
{ {
$this->writeDocument($this->getInputArgumentDocument($argument)); $this->writeDocument($this->getInputArgumentDocument($argument));
} }
protected function describeInputOption(InputOption $option, array $options = []) protected function describeInputOption(InputOption $option, array $options = []): void
{ {
$this->writeDocument($this->getInputOptionDocument($option)); $this->writeDocument($this->getInputOptionDocument($option));
} }
protected function describeInputDefinition(InputDefinition $definition, array $options = []) protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{ {
$this->writeDocument($this->getInputDefinitionDocument($definition)); $this->writeDocument($this->getInputDefinitionDocument($definition));
} }
protected function describeCommand(Command $command, array $options = []) protected function describeCommand(Command $command, array $options = []): void
{ {
$this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false)); $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
} }
protected function describeApplication(Application $application, array $options = []) protected function describeApplication(Application $application, array $options = []): void
{ {
$this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false)); $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
} }
@@ -148,7 +148,7 @@ class XmlDescriptor extends Descriptor
/** /**
* Appends document children to parent node. * Appends document children to parent node.
*/ */
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void
{ {
foreach ($importedParent->childNodes as $childNode) { foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
@@ -158,7 +158,7 @@ class XmlDescriptor extends Descriptor
/** /**
* Writes DOM document. * Writes DOM document.
*/ */
private function writeDocument(\DOMDocument $dom) private function writeDocument(\DOMDocument $dom): void
{ {
$dom->formatOutput = true; $dom->formatOutput = true;
$this->write($dom->saveXML()); $this->write($dom->saveXML());

View File

@@ -21,15 +21,36 @@ use Symfony\Component\Console\Output\OutputInterface;
final class ConsoleSignalEvent extends ConsoleEvent final class ConsoleSignalEvent extends ConsoleEvent
{ {
private int $handlingSignal; private int $handlingSignal;
private int|false $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal) public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0)
{ {
parent::__construct($command, $input, $output); parent::__construct($command, $input, $output);
$this->handlingSignal = $handlingSignal; $this->handlingSignal = $handlingSignal;
$this->exitCode = $exitCode;
} }
public function getHandlingSignal(): int public function getHandlingSignal(): int
{ {
return $this->handlingSignal; return $this->handlingSignal;
} }
public function setExitCode(int $exitCode): void
{
if ($exitCode < 0 || $exitCode > 255) {
throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
}
$this->exitCode = $exitCode;
}
public function abortExit(): void
{
$this->exitCode = false;
}
public function getExitCode(): int|false
{
return $this->exitCode;
}
} }

View File

@@ -31,6 +31,9 @@ class ErrorListener implements EventSubscriberInterface
$this->logger = $logger; $this->logger = $logger;
} }
/**
* @return void
*/
public function onConsoleError(ConsoleErrorEvent $event) public function onConsoleError(ConsoleErrorEvent $event)
{ {
if (null === $this->logger) { if (null === $this->logger) {
@@ -48,6 +51,9 @@ class ErrorListener implements EventSubscriberInterface
$this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
} }
/**
* @return void
*/
public function onConsoleTerminate(ConsoleTerminateEvent $event) public function onConsoleTerminate(ConsoleTerminateEvent $event)
{ {
if (null === $this->logger) { if (null === $this->logger) {

View File

@@ -81,6 +81,9 @@ class OutputFormatter implements WrappableOutputFormatterInterface
$this->styleStack = new OutputFormatterStyleStack(); $this->styleStack = new OutputFormatterStyleStack();
} }
/**
* @return void
*/
public function setDecorated(bool $decorated) public function setDecorated(bool $decorated)
{ {
$this->decorated = $decorated; $this->decorated = $decorated;
@@ -91,6 +94,9 @@ class OutputFormatter implements WrappableOutputFormatterInterface
return $this->decorated; return $this->decorated;
} }
/**
* @return void
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style) public function setStyle(string $name, OutputFormatterStyleInterface $style)
{ {
$this->styles[strtolower($name)] = $style; $this->styles[strtolower($name)] = $style;
@@ -115,6 +121,9 @@ class OutputFormatter implements WrappableOutputFormatterInterface
return $this->formatAndWrap($message, 0); return $this->formatAndWrap($message, 0);
} }
/**
* @return string
*/
public function formatAndWrap(?string $message, int $width) public function formatAndWrap(?string $message, int $width)
{ {
if (null === $message) { if (null === $message) {

View File

@@ -20,6 +20,8 @@ interface OutputFormatterInterface
{ {
/** /**
* Sets the decorated flag. * Sets the decorated flag.
*
* @return void
*/ */
public function setDecorated(bool $decorated); public function setDecorated(bool $decorated);
@@ -30,6 +32,8 @@ interface OutputFormatterInterface
/** /**
* Sets a new style. * Sets a new style.
*
* @return void
*/ */
public function setStyle(string $name, OutputFormatterStyleInterface $style); public function setStyle(string $name, OutputFormatterStyleInterface $style);

View File

@@ -38,6 +38,9 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
} }
/**
* @return void
*/
public function setForeground(string $color = null) public function setForeground(string $color = null)
{ {
if (1 > \func_num_args()) { if (1 > \func_num_args()) {
@@ -46,6 +49,9 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
} }
/**
* @return void
*/
public function setBackground(string $color = null) public function setBackground(string $color = null)
{ {
if (1 > \func_num_args()) { if (1 > \func_num_args()) {
@@ -59,12 +65,18 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->href = $url; $this->href = $url;
} }
/**
* @return void
*/
public function setOption(string $option) public function setOption(string $option)
{ {
$this->options[] = $option; $this->options[] = $option;
$this->color = new Color($this->foreground, $this->background, $this->options); $this->color = new Color($this->foreground, $this->background, $this->options);
} }
/**
* @return void
*/
public function unsetOption(string $option) public function unsetOption(string $option)
{ {
$pos = array_search($option, $this->options); $pos = array_search($option, $this->options);
@@ -75,6 +87,9 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground, $this->background, $this->options); $this->color = new Color($this->foreground, $this->background, $this->options);
} }
/**
* @return void
*/
public function setOptions(array $options) public function setOptions(array $options)
{ {
$this->color = new Color($this->foreground, $this->background, $this->options = $options); $this->color = new Color($this->foreground, $this->background, $this->options = $options);

View File

@@ -20,26 +20,36 @@ interface OutputFormatterStyleInterface
{ {
/** /**
* Sets style foreground color. * Sets style foreground color.
*
* @return void
*/ */
public function setForeground(?string $color); public function setForeground(?string $color);
/** /**
* Sets style background color. * Sets style background color.
*
* @return void
*/ */
public function setBackground(?string $color); public function setBackground(?string $color);
/** /**
* Sets some specific style option. * Sets some specific style option.
*
* @return void
*/ */
public function setOption(string $option); public function setOption(string $option);
/** /**
* Unsets some specific style option. * Unsets some specific style option.
*
* @return void
*/ */
public function unsetOption(string $option); public function unsetOption(string $option);
/** /**
* Sets multiple style options at once. * Sets multiple style options at once.
*
* @return void
*/ */
public function setOptions(array $options); public function setOptions(array $options);

View File

@@ -34,6 +34,8 @@ class OutputFormatterStyleStack implements ResetInterface
/** /**
* Resets stack (ie. empty internal arrays). * Resets stack (ie. empty internal arrays).
*
* @return void
*/ */
public function reset() public function reset()
{ {
@@ -42,6 +44,8 @@ class OutputFormatterStyleStack implements ResetInterface
/** /**
* Pushes a style in the stack. * Pushes a style in the stack.
*
* @return void
*/ */
public function push(OutputFormatterStyleInterface $style) public function push(OutputFormatterStyleInterface $style)
{ {

View File

@@ -14,6 +14,7 @@ namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor; use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor; use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\InvalidArgumentException;
@@ -38,6 +39,7 @@ class DescriptorHelper extends Helper
->register('xml', new XmlDescriptor()) ->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor()) ->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor()) ->register('md', new MarkdownDescriptor())
->register('rst', new ReStructuredTextDescriptor())
; ;
} }
@@ -48,6 +50,8 @@ class DescriptorHelper extends Helper
* * format: string, the output format name * * format: string, the output format name
* * raw_text: boolean, sets output type as raw * * raw_text: boolean, sets output type as raw
* *
* @return void
*
* @throws InvalidArgumentException when the given format is not supported * @throws InvalidArgumentException when the given format is not supported
*/ */
public function describe(OutputInterface $output, ?object $object, array $options = []) public function describe(OutputInterface $output, ?object $object, array $options = [])

View File

@@ -40,14 +40,12 @@ final class Dumper
return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true)); return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
}; };
} else { } else {
$this->handler = function ($var): string { $this->handler = fn ($var): string => match (true) {
return match (true) { null === $var => 'null',
null === $var => 'null', true === $var => 'true',
true === $var => 'true', false === $var => 'false',
false === $var => 'false', \is_string($var) => '"'.$var.'"',
\is_string($var) => '"'.$var.'"', default => rtrim(print_r($var, true)),
default => rtrim(print_r($var, true)),
};
}; };
} }
} }

View File

@@ -21,8 +21,11 @@ use Symfony\Component\String\UnicodeString;
*/ */
abstract class Helper implements HelperInterface abstract class Helper implements HelperInterface
{ {
protected $helperSet = null; protected $helperSet;
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet = null) public function setHelperSet(HelperSet $helperSet = null)
{ {
if (1 > \func_num_args()) { if (1 > \func_num_args()) {
@@ -88,6 +91,9 @@ abstract class Helper implements HelperInterface
return mb_substr($string, $from, $length, $encoding); return mb_substr($string, $from, $length, $encoding);
} }
/**
* @return string
*/
public static function formatTime(int|float $secs) public static function formatTime(int|float $secs)
{ {
static $timeFormats = [ static $timeFormats = [
@@ -117,6 +123,9 @@ abstract class Helper implements HelperInterface
} }
} }
/**
* @return string
*/
public static function formatMemory(int $memory) public static function formatMemory(int $memory)
{ {
if ($memory >= 1024 * 1024 * 1024) { if ($memory >= 1024 * 1024 * 1024) {
@@ -134,6 +143,9 @@ abstract class Helper implements HelperInterface
return sprintf('%d B', $memory); return sprintf('%d B', $memory);
} }
/**
* @return string
*/
public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string) public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
{ {
$isDecorated = $formatter->isDecorated(); $isDecorated = $formatter->isDecorated();

View File

@@ -20,6 +20,8 @@ interface HelperInterface
{ {
/** /**
* Sets the helper set associated with this helper. * Sets the helper set associated with this helper.
*
* @return void
*/ */
public function setHelperSet(?HelperSet $helperSet); public function setHelperSet(?HelperSet $helperSet);

View File

@@ -35,6 +35,9 @@ class HelperSet implements \IteratorAggregate
} }
} }
/**
* @return void
*/
public function set(HelperInterface $helper, string $alias = null) public function set(HelperInterface $helper, string $alias = null)
{ {
$this->helpers[$helper->getName()] = $helper; $this->helpers[$helper->getName()] = $helper;

View File

@@ -23,6 +23,9 @@ abstract class InputAwareHelper extends Helper implements InputAwareInterface
{ {
protected $input; protected $input;
/**
* @return void
*/
public function setInput(InputInterface $input) public function setInput(InputInterface $input)
{ {
$this->input = $input; $this->input = $input;

View File

@@ -59,6 +59,7 @@ final class ProgressBar
private Terminal $terminal; private Terminal $terminal;
private ?string $previousMessage = null; private ?string $previousMessage = null;
private Cursor $cursor; private Cursor $cursor;
private array $placeholders = [];
private static array $formatters; private static array $formatters;
private static array $formats; private static array $formats;
@@ -94,12 +95,12 @@ final class ProgressBar
} }
/** /**
* Sets a placeholder formatter for a given name. * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar.
* *
* This method also allow you to override an existing placeholder. * This method also allow you to override an existing placeholder.
* *
* @param string $name The placeholder name (including the delimiter char like %) * @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable * @param callable(ProgressBar):string $callable A PHP callable
*/ */
public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
{ {
@@ -120,6 +121,26 @@ final class ProgressBar
return self::$formatters[$name] ?? null; return self::$formatters[$name] ?? null;
} }
/**
* Sets a placeholder formatter for a given name, for this instance only.
*
* @param callable(ProgressBar):string $callable A PHP callable
*/
public function setPlaceholderFormatter(string $name, callable $callable): void
{
$this->placeholders[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*/
public function getPlaceholderFormatter(string $name): ?callable
{
return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name);
}
/** /**
* Sets a format for a given name. * Sets a format for a given name.
* *
@@ -157,12 +178,12 @@ final class ProgressBar
* @param string $message The text to associate with the placeholder * @param string $message The text to associate with the placeholder
* @param string $name The name of the placeholder * @param string $name The name of the placeholder
*/ */
public function setMessage(string $message, string $name = 'message') public function setMessage(string $message, string $name = 'message'): void
{ {
$this->messages[$name] = $message; $this->messages[$name] = $message;
} }
public function getMessage(string $name = 'message') public function getMessage(string $name = 'message'): string
{ {
return $this->messages[$name]; return $this->messages[$name];
} }
@@ -215,7 +236,7 @@ final class ProgressBar
return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step)); return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
} }
public function setBarWidth(int $size) public function setBarWidth(int $size): void
{ {
$this->barWidth = max(1, $size); $this->barWidth = max(1, $size);
} }
@@ -225,7 +246,7 @@ final class ProgressBar
return $this->barWidth; return $this->barWidth;
} }
public function setBarCharacter(string $char) public function setBarCharacter(string $char): void
{ {
$this->barChar = $char; $this->barChar = $char;
} }
@@ -235,7 +256,7 @@ final class ProgressBar
return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar);
} }
public function setEmptyBarCharacter(string $char) public function setEmptyBarCharacter(string $char): void
{ {
$this->emptyBarChar = $char; $this->emptyBarChar = $char;
} }
@@ -245,7 +266,7 @@ final class ProgressBar
return $this->emptyBarChar; return $this->emptyBarChar;
} }
public function setProgressCharacter(string $char) public function setProgressCharacter(string $char): void
{ {
$this->progressChar = $char; $this->progressChar = $char;
} }
@@ -255,7 +276,7 @@ final class ProgressBar
return $this->progressChar; return $this->progressChar;
} }
public function setFormat(string $format) public function setFormat(string $format): void
{ {
$this->format = null; $this->format = null;
$this->internalFormat = $format; $this->internalFormat = $format;
@@ -266,7 +287,7 @@ final class ProgressBar
* *
* @param int|null $freq The frequency in steps * @param int|null $freq The frequency in steps
*/ */
public function setRedrawFrequency(?int $freq) public function setRedrawFrequency(?int $freq): void
{ {
$this->redrawFreq = null !== $freq ? max(1, $freq) : null; $this->redrawFreq = null !== $freq ? max(1, $freq) : null;
} }
@@ -325,7 +346,7 @@ final class ProgressBar
* *
* @param int $step Number of steps to advance * @param int $step Number of steps to advance
*/ */
public function advance(int $step = 1) public function advance(int $step = 1): void
{ {
$this->setProgress($this->step + $step); $this->setProgress($this->step + $step);
} }
@@ -333,12 +354,12 @@ final class ProgressBar
/** /**
* Sets whether to overwrite the progressbar, false for new line. * Sets whether to overwrite the progressbar, false for new line.
*/ */
public function setOverwrite(bool $overwrite) public function setOverwrite(bool $overwrite): void
{ {
$this->overwrite = $overwrite; $this->overwrite = $overwrite;
} }
public function setProgress(int $step) public function setProgress(int $step): void
{ {
if ($this->max && $step > $this->max) { if ($this->max && $step > $this->max) {
$this->max = $step; $this->max = $step;
@@ -371,7 +392,7 @@ final class ProgressBar
} }
} }
public function setMaxSteps(int $max) public function setMaxSteps(int $max): void
{ {
$this->format = null; $this->format = null;
$this->max = max(0, $max); $this->max = max(0, $max);
@@ -431,7 +452,7 @@ final class ProgressBar
$this->overwrite(''); $this->overwrite('');
} }
private function setRealFormat(string $format) private function setRealFormat(string $format): void
{ {
// try to use the _nomax variant if available // try to use the _nomax variant if available
if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
@@ -513,9 +534,7 @@ final class ProgressBar
return $display; return $display;
}, },
'elapsed' => function (self $bar) { 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime()),
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (self $bar) { 'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) { if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
@@ -530,18 +549,10 @@ final class ProgressBar
return Helper::formatTime($bar->getEstimated()); return Helper::formatTime($bar->getEstimated());
}, },
'memory' => function (self $bar) { 'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)),
return Helper::formatMemory(memory_get_usage(true)); 'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT),
}, 'max' => fn (self $bar) => $bar->getMaxSteps(),
'current' => function (self $bar) { 'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100),
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT);
},
'max' => function (self $bar) {
return $bar->getMaxSteps();
},
'percent' => function (self $bar) {
return floor($bar->getProgressPercent() * 100);
},
]; ];
} }
@@ -568,7 +579,7 @@ final class ProgressBar
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) { $callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { if ($formatter = $this->getPlaceholderFormatter($matches[1])) {
$text = $formatter($this, $this->output); $text = $formatter($this, $this->output);
} elseif (isset($this->messages[$matches[1]])) { } elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]]; $text = $this->messages[$matches[1]];
@@ -585,9 +596,7 @@ final class ProgressBar
$line = preg_replace_callback($regex, $callback, $this->format); $line = preg_replace_callback($regex, $callback, $this->format);
// gets string length for each sub line with multiline format // gets string length for each sub line with multiline format
$linesLength = array_map(function ($subLine) { $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line));
return Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r")));
}, explode("\n", $line));
$linesWidth = max($linesLength); $linesWidth = max($linesLength);

View File

@@ -70,6 +70,8 @@ class ProgressIndicator
/** /**
* Sets the current indicator message. * Sets the current indicator message.
*
* @return void
*/ */
public function setMessage(?string $message) public function setMessage(?string $message)
{ {
@@ -80,6 +82,8 @@ class ProgressIndicator
/** /**
* Starts the indicator output. * Starts the indicator output.
*
* @return void
*/ */
public function start(string $message) public function start(string $message)
{ {
@@ -98,6 +102,8 @@ class ProgressIndicator
/** /**
* Advances the indicator. * Advances the indicator.
*
* @return void
*/ */
public function advance() public function advance()
{ {
@@ -123,6 +129,8 @@ class ProgressIndicator
/** /**
* Finish the indicator with message. * Finish the indicator with message.
*
* @return void
*/ */
public function finish(string $message) public function finish(string $message)
{ {
@@ -148,6 +156,8 @@ class ProgressIndicator
* Sets a placeholder formatter for a given name. * Sets a placeholder formatter for a given name.
* *
* This method also allow you to override an existing placeholder. * This method also allow you to override an existing placeholder.
*
* @return void
*/ */
public static function setPlaceholderFormatterDefinition(string $name, callable $callable) public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
{ {
@@ -166,7 +176,7 @@ class ProgressIndicator
return self::$formatters[$name] ?? null; return self::$formatters[$name] ?? null;
} }
private function display() private function display(): void
{ {
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return; return;
@@ -195,7 +205,7 @@ class ProgressIndicator
/** /**
* Overwrites a previous message to the output. * Overwrites a previous message to the output.
*/ */
private function overwrite(string $message) private function overwrite(string $message): void
{ {
if ($this->output->isDecorated()) { if ($this->output->isDecorated()) {
$this->output->write("\x0D\x1B[2K"); $this->output->write("\x0D\x1B[2K");
@@ -216,18 +226,10 @@ class ProgressIndicator
private static function initPlaceholderFormatters(): array private static function initPlaceholderFormatters(): array
{ {
return [ return [
'indicator' => function (self $indicator) { 'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)],
return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; 'message' => fn (self $indicator) => $indicator->message,
}, 'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime),
'message' => function (self $indicator) { 'memory' => fn () => Helper::formatMemory(memory_get_usage(true)),
return $indicator->message;
},
'elapsed' => function (self $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
]; ];
} }
} }

View File

@@ -68,9 +68,7 @@ class QuestionHelper extends Helper
return $this->doAsk($output, $question); return $this->doAsk($output, $question);
} }
$interviewer = function () use ($output, $question) { $interviewer = fn () => $this->doAsk($output, $question);
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question); return $this->validateAttempts($interviewer, $output, $question);
} catch (MissingInputException $exception) { } catch (MissingInputException $exception) {
@@ -91,6 +89,8 @@ class QuestionHelper extends Helper
/** /**
* Prevents usage of stty. * Prevents usage of stty.
*
* @return void
*/ */
public static function disableStty() public static function disableStty()
{ {
@@ -170,7 +170,7 @@ class QuestionHelper extends Helper
} }
if ($validator = $question->getValidator()) { if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default); return \call_user_func($validator, $default);
} elseif ($question instanceof ChoiceQuestion) { } elseif ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices(); $choices = $question->getChoices();
@@ -190,6 +190,8 @@ class QuestionHelper extends Helper
/** /**
* Outputs the question prompt. * Outputs the question prompt.
*
* @return void
*/ */
protected function writePrompt(OutputInterface $output, Question $question) protected function writePrompt(OutputInterface $output, Question $question)
{ {
@@ -226,6 +228,8 @@ class QuestionHelper extends Helper
/** /**
* Outputs an error message. * Outputs an error message.
*
* @return void
*/ */
protected function writeError(OutputInterface $output, \Exception $error) protected function writeError(OutputInterface $output, \Exception $error)
{ {
@@ -325,9 +329,7 @@ class QuestionHelper extends Helper
$matches = array_filter( $matches = array_filter(
$autocomplete($ret), $autocomplete($ret),
function ($match) use ($ret) { fn ($match) => '' === $ret || str_starts_with($match, $ret)
return '' === $ret || str_starts_with($match, $ret);
}
); );
$numMatches = \count($matches); $numMatches = \count($matches);
$ofs = -1; $ofs = -1;

View File

@@ -25,6 +25,9 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*/ */
class SymfonyQuestionHelper extends QuestionHelper class SymfonyQuestionHelper extends QuestionHelper
{ {
/**
* @return void
*/
protected function writePrompt(OutputInterface $output, Question $question) protected function writePrompt(OutputInterface $output, Question $question)
{ {
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
@@ -80,6 +83,9 @@ class SymfonyQuestionHelper extends QuestionHelper
$output->write($prompt); $output->write($prompt);
} }
/**
* @return void
*/
protected function writeError(OutputInterface $output, \Exception $error) protected function writeError(OutputInterface $output, \Exception $error)
{ {
if ($output instanceof SymfonyStyle) { if ($output instanceof SymfonyStyle) {

View File

@@ -66,6 +66,8 @@ class Table
/** /**
* Sets a style definition. * Sets a style definition.
*
* @return void
*/ */
public static function setStyleDefinition(string $name, TableStyle $style) public static function setStyleDefinition(string $name, TableStyle $style)
{ {
@@ -310,6 +312,8 @@ class Table
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+ * +---------------+-----------------------+------------------+
*
* @return void
*/ */
public function render() public function render()
{ {
@@ -450,7 +454,7 @@ class Table
* *
* +-----+-----------+-------+ * +-----+-----------+-------+
*/ */
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null) private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null): void
{ {
if (!$count = $this->numberOfColumns) { if (!$count = $this->numberOfColumns) {
return; return;
@@ -515,7 +519,7 @@ class Table
* *
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/ */
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null) private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null): void
{ {
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row); $columns = $this->getRowColumns($row);
@@ -588,7 +592,7 @@ class Table
/** /**
* Calculate number of columns for this table. * Calculate number of columns for this table.
*/ */
private function calculateNumberOfColumns(array $rows) private function calculateNumberOfColumns(array $rows): void
{ {
$columns = [0]; $columns = [0];
foreach ($rows as $row) { foreach ($rows as $row) {
@@ -727,7 +731,7 @@ class Table
/** /**
* fill cells for a row that contains colspan > 1. * fill cells for a row that contains colspan > 1.
*/ */
private function fillCells(iterable $row) private function fillCells(iterable $row): iterable
{ {
$newRow = []; $newRow = [];
@@ -789,7 +793,7 @@ class Table
/** /**
* Calculates columns widths. * Calculates columns widths.
*/ */
private function calculateColumnsWidth(iterable $groups) private function calculateColumnsWidth(iterable $groups): void
{ {
for ($column = 0; $column < $this->numberOfColumns; ++$column) { for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = []; $lengths = [];
@@ -843,7 +847,7 @@ class Table
/** /**
* Called after rendering to cleanup cache data. * Called after rendering to cleanup cache data.
*/ */
private function cleanup() private function cleanup(): void
{ {
$this->effectiveColumnWidths = []; $this->effectiveColumnWidths = [];
unset($this->numberOfColumns); unset($this->numberOfColumns);

View File

@@ -67,9 +67,7 @@ class TableCellStyle
{ {
return array_filter( return array_filter(
$this->getOptions(), $this->getOptions(),
function ($key) { fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]),
return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]);
},
\ARRAY_FILTER_USE_KEY \ARRAY_FILTER_USE_KEY
); );
} }

View File

@@ -55,11 +55,17 @@ class ArgvInput extends Input
parent::__construct($definition); parent::__construct($definition);
} }
/**
* @return void
*/
protected function setTokens(array $tokens) protected function setTokens(array $tokens)
{ {
$this->tokens = $tokens; $this->tokens = $tokens;
} }
/**
* @return void
*/
protected function parse() protected function parse()
{ {
$parseOptions = true; $parseOptions = true;
@@ -89,7 +95,7 @@ class ArgvInput extends Input
/** /**
* Parses a short option. * Parses a short option.
*/ */
private function parseShortOption(string $token) private function parseShortOption(string $token): void
{ {
$name = substr($token, 1); $name = substr($token, 1);
@@ -110,7 +116,7 @@ class ArgvInput extends Input
* *
* @throws RuntimeException When option given doesn't exist * @throws RuntimeException When option given doesn't exist
*/ */
private function parseShortOptionSet(string $name) private function parseShortOptionSet(string $name): void
{ {
$len = \strlen($name); $len = \strlen($name);
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
@@ -133,7 +139,7 @@ class ArgvInput extends Input
/** /**
* Parses a long option. * Parses a long option.
*/ */
private function parseLongOption(string $token) private function parseLongOption(string $token): void
{ {
$name = substr($token, 2); $name = substr($token, 2);
@@ -152,7 +158,7 @@ class ArgvInput extends Input
* *
* @throws RuntimeException When too many arguments are given * @throws RuntimeException When too many arguments are given
*/ */
private function parseArgument(string $token) private function parseArgument(string $token): void
{ {
$c = \count($this->arguments); $c = \count($this->arguments);
@@ -196,7 +202,7 @@ class ArgvInput extends Input
* *
* @throws RuntimeException When option given doesn't exist * @throws RuntimeException When option given doesn't exist
*/ */
private function addShortOption(string $shortcut, mixed $value) private function addShortOption(string $shortcut, mixed $value): void
{ {
if (!$this->definition->hasShortcut($shortcut)) { if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -210,7 +216,7 @@ class ArgvInput extends Input
* *
* @throws RuntimeException When option given doesn't exist * @throws RuntimeException When option given doesn't exist
*/ */
private function addLongOption(string $name, mixed $value) private function addLongOption(string $name, mixed $value): void
{ {
if (!$this->definition->hasOption($name)) { if (!$this->definition->hasOption($name)) {
if (!$this->definition->hasNegation($name)) { if (!$this->definition->hasNegation($name)) {

View File

@@ -113,6 +113,9 @@ class ArrayInput extends Input
return implode(' ', $params); return implode(' ', $params);
} }
/**
* @return void
*/
protected function parse() protected function parse()
{ {
foreach ($this->parameters as $key => $value) { foreach ($this->parameters as $key => $value) {
@@ -134,7 +137,7 @@ class ArrayInput extends Input
* *
* @throws InvalidOptionException When option given doesn't exist * @throws InvalidOptionException When option given doesn't exist
*/ */
private function addShortOption(string $shortcut, mixed $value) private function addShortOption(string $shortcut, mixed $value): void
{ {
if (!$this->definition->hasShortcut($shortcut)) { if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -149,7 +152,7 @@ class ArrayInput extends Input
* @throws InvalidOptionException When option given doesn't exist * @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing * @throws InvalidOptionException When a required value is missing
*/ */
private function addLongOption(string $name, mixed $value) private function addLongOption(string $name, mixed $value): void
{ {
if (!$this->definition->hasOption($name)) { if (!$this->definition->hasOption($name)) {
if (!$this->definition->hasNegation($name)) { if (!$this->definition->hasNegation($name)) {
@@ -182,7 +185,7 @@ class ArrayInput extends Input
* *
* @throws InvalidArgumentException When argument given doesn't exist * @throws InvalidArgumentException When argument given doesn't exist
*/ */
private function addArgument(string|int $name, mixed $value) private function addArgument(string|int $name, mixed $value): void
{ {
if (!$this->definition->hasArgument($name)) { if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));

View File

@@ -43,6 +43,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
} }
} }
/**
* @return void
*/
public function bind(InputDefinition $definition) public function bind(InputDefinition $definition)
{ {
$this->arguments = []; $this->arguments = [];
@@ -54,17 +57,20 @@ abstract class Input implements InputInterface, StreamableInputInterface
/** /**
* Processes command line arguments. * Processes command line arguments.
*
* @return void
*/ */
abstract protected function parse(); abstract protected function parse();
/**
* @return void
*/
public function validate() public function validate()
{ {
$definition = $this->definition; $definition = $this->definition;
$givenArguments = $this->arguments; $givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { $missingArguments = array_filter(array_keys($definition->getArguments()), fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired());
return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
if (\count($missingArguments) > 0) { if (\count($missingArguments) > 0) {
throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
@@ -76,6 +82,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
return $this->interactive; return $this->interactive;
} }
/**
* @return void
*/
public function setInteractive(bool $interactive) public function setInteractive(bool $interactive)
{ {
$this->interactive = $interactive; $this->interactive = $interactive;
@@ -95,6 +104,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
} }
/**
* @return void
*/
public function setArgument(string $name, mixed $value) public function setArgument(string $name, mixed $value)
{ {
if (!$this->definition->hasArgument($name)) { if (!$this->definition->hasArgument($name)) {
@@ -131,6 +143,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
} }
/**
* @return void
*/
public function setOption(string $name, mixed $value) public function setOption(string $name, mixed $value)
{ {
if ($this->definition->hasNegation($name)) { if ($this->definition->hasNegation($name)) {
@@ -157,11 +172,19 @@ abstract class Input implements InputInterface, StreamableInputInterface
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
} }
/**
* @param resource $stream
*
* @return void
*/
public function setStream($stream) public function setStream($stream)
{ {
$this->stream = $stream; $this->stream = $stream;
} }
/**
* @return resource
*/
public function getStream() public function getStream()
{ {
return $this->stream; return $this->stream;

View File

@@ -91,6 +91,8 @@ class InputArgument
/** /**
* Sets the default value. * Sets the default value.
* *
* @return void
*
* @throws LogicException When incorrect default value is given * @throws LogicException When incorrect default value is given
*/ */
public function setDefault(string|bool|int|float|array $default = null) public function setDefault(string|bool|int|float|array $default = null)

View File

@@ -21,6 +21,8 @@ interface InputAwareInterface
{ {
/** /**
* Sets the Console Input. * Sets the Console Input.
*
* @return void
*/ */
public function setInput(InputInterface $input); public function setInput(InputInterface $input);
} }

View File

@@ -46,6 +46,8 @@ class InputDefinition
/** /**
* Sets the definition of the input. * Sets the definition of the input.
*
* @return void
*/ */
public function setDefinition(array $definition) public function setDefinition(array $definition)
{ {
@@ -67,6 +69,8 @@ class InputDefinition
* Sets the InputArgument objects. * Sets the InputArgument objects.
* *
* @param InputArgument[] $arguments An array of InputArgument objects * @param InputArgument[] $arguments An array of InputArgument objects
*
* @return void
*/ */
public function setArguments(array $arguments = []) public function setArguments(array $arguments = [])
{ {
@@ -81,6 +85,8 @@ class InputDefinition
* Adds an array of InputArgument objects. * Adds an array of InputArgument objects.
* *
* @param InputArgument[] $arguments An array of InputArgument objects * @param InputArgument[] $arguments An array of InputArgument objects
*
* @return void
*/ */
public function addArguments(?array $arguments = []) public function addArguments(?array $arguments = [])
{ {
@@ -92,6 +98,8 @@ class InputDefinition
} }
/** /**
* @return void
*
* @throws LogicException When incorrect argument is given * @throws LogicException When incorrect argument is given
*/ */
public function addArgument(InputArgument $argument) public function addArgument(InputArgument $argument)
@@ -190,6 +198,8 @@ class InputDefinition
* Sets the InputOption objects. * Sets the InputOption objects.
* *
* @param InputOption[] $options An array of InputOption objects * @param InputOption[] $options An array of InputOption objects
*
* @return void
*/ */
public function setOptions(array $options = []) public function setOptions(array $options = [])
{ {
@@ -203,6 +213,8 @@ class InputDefinition
* Adds an array of InputOption objects. * Adds an array of InputOption objects.
* *
* @param InputOption[] $options An array of InputOption objects * @param InputOption[] $options An array of InputOption objects
*
* @return void
*/ */
public function addOptions(array $options = []) public function addOptions(array $options = [])
{ {
@@ -212,6 +224,8 @@ class InputDefinition
} }
/** /**
* @return void
*
* @throws LogicException When option given already exist * @throws LogicException When option given already exist
*/ */
public function addOption(InputOption $option) public function addOption(InputOption $option)

View File

@@ -61,6 +61,8 @@ interface InputInterface
/** /**
* Binds the current Input instance with the given arguments and options. * Binds the current Input instance with the given arguments and options.
* *
* @return void
*
* @throws RuntimeException * @throws RuntimeException
*/ */
public function bind(InputDefinition $definition); public function bind(InputDefinition $definition);
@@ -68,6 +70,8 @@ interface InputInterface
/** /**
* Validates the input. * Validates the input.
* *
* @return void
*
* @throws RuntimeException When not enough arguments are given * @throws RuntimeException When not enough arguments are given
*/ */
public function validate(); public function validate();
@@ -91,6 +95,8 @@ interface InputInterface
/** /**
* Sets an argument value by name. * Sets an argument value by name.
* *
* @return void
*
* @throws InvalidArgumentException When argument given doesn't exist * @throws InvalidArgumentException When argument given doesn't exist
*/ */
public function setArgument(string $name, mixed $value); public function setArgument(string $name, mixed $value);
@@ -119,6 +125,8 @@ interface InputInterface
/** /**
* Sets an option value by name. * Sets an option value by name.
* *
* @return void
*
* @throws InvalidArgumentException When option given doesn't exist * @throws InvalidArgumentException When option given doesn't exist
*/ */
public function setOption(string $name, mixed $value); public function setOption(string $name, mixed $value);
@@ -135,6 +143,8 @@ interface InputInterface
/** /**
* Sets the input interactivity. * Sets the input interactivity.
*
* @return void
*/ */
public function setInteractive(bool $interactive); public function setInteractive(bool $interactive);
} }

View File

@@ -178,6 +178,9 @@ class InputOption
return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
} }
/**
* @return void
*/
public function setDefault(string|bool|int|float|array $default = null) public function setDefault(string|bool|int|float|array $default = null)
{ {
if (1 > \func_num_args()) { if (1 > \func_num_args()) {

View File

@@ -25,6 +25,8 @@ interface StreamableInputInterface extends InputInterface
* This is mainly useful for testing purpose. * This is mainly useful for testing purpose.
* *
* @param resource $stream The input stream * @param resource $stream The input stream
*
* @return void
*/ */
public function setStream($stream); public function setStream($stream);

View File

@@ -26,7 +26,7 @@ enum AnsiColorMode
case Ansi4; case Ansi4;
/* /*
* 8-bit Ansi colors (240 differents colors + 16 duplicate color codes, ensuring backward compatibility). * 8-bit Ansi colors (240 different colors + 16 duplicate color codes, ensuring backward compatibility).
* Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m" * Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m"
* Should be compatible with most terminals. * Should be compatible with most terminals.
*/ */
@@ -78,25 +78,7 @@ enum AnsiColorMode
private function degradeHexColorToAnsi4(int $r, int $g, int $b): int private function degradeHexColorToAnsi4(int $r, int $g, int $b): int
{ {
if (0 === round($this->getSaturation($r, $g, $b) / 50)) { return round($b / 255) << 2 | (round($g / 255) << 1) | round($r / 255);
return 0;
}
return (int) ((round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255));
}
private function getSaturation(int $r, int $g, int $b): int
{
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
$v = max($r, $g, $b);
if (0 === $diff = $v - min($r, $g, $b)) {
return 0;
}
return (int) ((int) $diff * 100 / $v);
} }
/** /**

View File

@@ -29,6 +29,9 @@ class BufferedOutput extends Output
return $content; return $content;
} }
/**
* @return void
*/
protected function doWrite(string $message, bool $newline) protected function doWrite(string $message, bool $newline)
{ {
$this->buffer .= $message; $this->buffer .= $message;

View File

@@ -64,18 +64,27 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
} }
/**
* @return void
*/
public function setDecorated(bool $decorated) public function setDecorated(bool $decorated)
{ {
parent::setDecorated($decorated); parent::setDecorated($decorated);
$this->stderr->setDecorated($decorated); $this->stderr->setDecorated($decorated);
} }
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter) public function setFormatter(OutputFormatterInterface $formatter)
{ {
parent::setFormatter($formatter); parent::setFormatter($formatter);
$this->stderr->setFormatter($formatter); $this->stderr->setFormatter($formatter);
} }
/**
* @return void
*/
public function setVerbosity(int $level) public function setVerbosity(int $level)
{ {
parent::setVerbosity($level); parent::setVerbosity($level);
@@ -87,6 +96,9 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
return $this->stderr; return $this->stderr;
} }
/**
* @return void
*/
public function setErrorOutput(OutputInterface $error) public function setErrorOutput(OutputInterface $error)
{ {
$this->stderr = $error; $this->stderr = $error;

View File

@@ -24,6 +24,9 @@ interface ConsoleOutputInterface extends OutputInterface
*/ */
public function getErrorOutput(): OutputInterface; public function getErrorOutput(): OutputInterface;
/**
* @return void
*/
public function setErrorOutput(OutputInterface $error); public function setErrorOutput(OutputInterface $error);
public function section(): ConsoleSectionOutput; public function section(): ConsoleSectionOutput;

View File

@@ -60,6 +60,8 @@ class ConsoleSectionOutput extends StreamOutput
* Clears previous output for this section. * Clears previous output for this section.
* *
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
*
* @return void
*/ */
public function clear(int $lines = null) public function clear(int $lines = null)
{ {
@@ -81,6 +83,8 @@ class ConsoleSectionOutput extends StreamOutput
/** /**
* Overwrites the previous output with a new message. * Overwrites the previous output with a new message.
*
* @return void
*/ */
public function overwrite(string|iterable $message) public function overwrite(string|iterable $message)
{ {
@@ -153,12 +157,15 @@ class ConsoleSectionOutput extends StreamOutput
/** /**
* @internal * @internal
*/ */
public function addNewLineOfInputSubmit() public function addNewLineOfInputSubmit(): void
{ {
$this->content[] = \PHP_EOL; $this->content[] = \PHP_EOL;
++$this->lines; ++$this->lines;
} }
/**
* @return void
*/
protected function doWrite(string $message, bool $newline) protected function doWrite(string $message, bool $newline)
{ {
if (!$this->isDecorated()) { if (!$this->isDecorated()) {

View File

@@ -26,6 +26,9 @@ class NullOutput implements OutputInterface
{ {
private NullOutputFormatter $formatter; private NullOutputFormatter $formatter;
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter) public function setFormatter(OutputFormatterInterface $formatter)
{ {
// do nothing // do nothing
@@ -37,6 +40,9 @@ class NullOutput implements OutputInterface
return $this->formatter ??= new NullOutputFormatter(); return $this->formatter ??= new NullOutputFormatter();
} }
/**
* @return void
*/
public function setDecorated(bool $decorated) public function setDecorated(bool $decorated)
{ {
// do nothing // do nothing
@@ -47,6 +53,9 @@ class NullOutput implements OutputInterface
return false; return false;
} }
/**
* @return void
*/
public function setVerbosity(int $level) public function setVerbosity(int $level)
{ {
// do nothing // do nothing
@@ -77,11 +86,17 @@ class NullOutput implements OutputInterface
return false; return false;
} }
/**
* @return void
*/
public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
{ {
// do nothing // do nothing
} }
/**
* @return void
*/
public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{ {
// do nothing // do nothing

View File

@@ -44,6 +44,9 @@ abstract class Output implements OutputInterface
$this->formatter->setDecorated($decorated); $this->formatter->setDecorated($decorated);
} }
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter) public function setFormatter(OutputFormatterInterface $formatter)
{ {
$this->formatter = $formatter; $this->formatter = $formatter;
@@ -54,6 +57,9 @@ abstract class Output implements OutputInterface
return $this->formatter; return $this->formatter;
} }
/**
* @return void
*/
public function setDecorated(bool $decorated) public function setDecorated(bool $decorated)
{ {
$this->formatter->setDecorated($decorated); $this->formatter->setDecorated($decorated);
@@ -64,6 +70,9 @@ abstract class Output implements OutputInterface
return $this->formatter->isDecorated(); return $this->formatter->isDecorated();
} }
/**
* @return void
*/
public function setVerbosity(int $level) public function setVerbosity(int $level)
{ {
$this->verbosity = $level; $this->verbosity = $level;
@@ -94,11 +103,17 @@ abstract class Output implements OutputInterface
return self::VERBOSITY_DEBUG <= $this->verbosity; return self::VERBOSITY_DEBUG <= $this->verbosity;
} }
/**
* @return void
*/
public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
{ {
$this->write($messages, true, $options); $this->write($messages, true, $options);
} }
/**
* @return void
*/
public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{ {
if (!is_iterable($messages)) { if (!is_iterable($messages)) {
@@ -133,6 +148,8 @@ abstract class Output implements OutputInterface
/** /**
* Writes a message to the output. * Writes a message to the output.
*
* @return void
*/ */
abstract protected function doWrite(string $message, bool $newline); abstract protected function doWrite(string $message, bool $newline);
} }

View File

@@ -36,6 +36,8 @@ interface OutputInterface
* @param bool $newline Whether to add a newline * @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
* 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*
* @return void
*/ */
public function write(string|iterable $messages, bool $newline = false, int $options = 0); public function write(string|iterable $messages, bool $newline = false, int $options = 0);
@@ -44,11 +46,15 @@ interface OutputInterface
* *
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
* 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*
* @return void
*/ */
public function writeln(string|iterable $messages, int $options = 0); public function writeln(string|iterable $messages, int $options = 0);
/** /**
* Sets the verbosity of the output. * Sets the verbosity of the output.
*
* @return void
*/ */
public function setVerbosity(int $level); public function setVerbosity(int $level);
@@ -79,6 +85,8 @@ interface OutputInterface
/** /**
* Sets the decorated flag. * Sets the decorated flag.
*
* @return void
*/ */
public function setDecorated(bool $decorated); public function setDecorated(bool $decorated);
@@ -87,6 +95,9 @@ interface OutputInterface
*/ */
public function isDecorated(): bool; public function isDecorated(): bool;
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter); public function setFormatter(OutputFormatterInterface $formatter);
/** /**

View File

@@ -62,6 +62,9 @@ class StreamOutput extends Output
return $this->stream; return $this->stream;
} }
/**
* @return void
*/
protected function doWrite(string $message, bool $newline) protected function doWrite(string $message, bool $newline)
{ {
if ($newline) { if ($newline) {

View File

@@ -45,6 +45,9 @@ class TrimmedBufferOutput extends Output
return $content; return $content;
} }
/**
* @return void
*/
protected function doWrite(string $message, bool $newline) protected function doWrite(string $message, bool $newline)
{ {
$this->buffer .= $message; $this->buffer .= $message;

View File

@@ -146,12 +146,11 @@ class Question
if (\is_array($values)) { if (\is_array($values)) {
$values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
$callback = static function () use ($values) { $callback = static fn () => $values;
return $values;
};
} elseif ($values instanceof \Traversable) { } elseif ($values instanceof \Traversable) {
$valueCache = null; $callback = static function () use ($values) {
$callback = static function () use ($values, &$valueCache) { static $valueCache;
return $valueCache ??= iterator_to_array($values, false); return $valueCache ??= iterator_to_array($values, false);
}; };
} else { } else {
@@ -267,6 +266,9 @@ class Question
return $this->normalizer; return $this->normalizer;
} }
/**
* @return bool
*/
protected function isAssoc(array $array) protected function isAssoc(array $array)
{ {
return (bool) \count(array_filter(array_keys($array), 'is_string')); return (bool) \count(array_filter(array_keys($array), 'is_string'));

View File

@@ -4,6 +4,18 @@ Console Component
The Console component eases the creation of beautiful and testable command line The Console component eases the creation of beautiful and testable command line
interfaces. interfaces.
Sponsor
-------
The Console component for Symfony 6.3 is [backed][1] by [Les-Tilleuls.coop][2].
Les-Tilleuls.coop is a team of 70+ Symfony experts who can help you design, develop and
fix your projects. They provide a wide range of professional services including development,
consulting, coaching, training and audits. They also are highly skilled in JS, Go and DevOps.
They are a worker cooperative!
Help Symfony by [sponsoring][3] its development!
Resources Resources
--------- ---------
@@ -18,3 +30,7 @@ Credits
`Resources/bin/hiddeninput.exe` is a third party binary provided within this `Resources/bin/hiddeninput.exe` is a third party binary provided within this
component. Find sources and license at https://github.com/Seldaek/hidden-input. component. Find sources and license at https://github.com/Seldaek/hidden-input.
[1]: https://symfony.com/backers
[2]: https://les-tilleuls.coop
[3]: https://symfony.com/sponsor

View File

@@ -6,6 +6,16 @@
# https://symfony.com/doc/current/contributing/code/license.html # https://symfony.com/doc/current/contributing/code/license.html
_sf_{{ COMMAND_NAME }}() { _sf_{{ COMMAND_NAME }}() {
# Use the default completion for shell redirect operators.
for w in '>' '>>' '&>' '<'; do
if [[ $w = "${COMP_WORDS[COMP_CWORD-1]}" ]]; then
compopt -o filenames
COMPREPLY=($(compgen -f -- "${COMP_WORDS[COMP_CWORD]}"))
return 0
fi
done
# Use newline as only separator to allow space in completion values # Use newline as only separator to allow space in completion values
IFS=$'\n' IFS=$'\n'
local sf_cmd="${COMP_WORDS[0]}" local sf_cmd="${COMP_WORDS[0]}"

View File

@@ -30,6 +30,9 @@ abstract class OutputStyle implements OutputInterface, StyleInterface
$this->output = $output; $this->output = $output;
} }
/**
* @return void
*/
public function newLine(int $count = 1) public function newLine(int $count = 1)
{ {
$this->output->write(str_repeat(\PHP_EOL, $count)); $this->output->write(str_repeat(\PHP_EOL, $count));
@@ -40,16 +43,25 @@ abstract class OutputStyle implements OutputInterface, StyleInterface
return new ProgressBar($this->output, $max); return new ProgressBar($this->output, $max);
} }
/**
* @return void
*/
public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{ {
$this->output->write($messages, $newline, $type); $this->output->write($messages, $newline, $type);
} }
/**
* @return void
*/
public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
{ {
$this->output->writeln($messages, $type); $this->output->writeln($messages, $type);
} }
/**
* @return void
*/
public function setVerbosity(int $level) public function setVerbosity(int $level)
{ {
$this->output->setVerbosity($level); $this->output->setVerbosity($level);
@@ -60,6 +72,9 @@ abstract class OutputStyle implements OutputInterface, StyleInterface
return $this->output->getVerbosity(); return $this->output->getVerbosity();
} }
/**
* @return void
*/
public function setDecorated(bool $decorated) public function setDecorated(bool $decorated)
{ {
$this->output->setDecorated($decorated); $this->output->setDecorated($decorated);
@@ -70,6 +85,9 @@ abstract class OutputStyle implements OutputInterface, StyleInterface
return $this->output->isDecorated(); return $this->output->isDecorated();
} }
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter) public function setFormatter(OutputFormatterInterface $formatter)
{ {
$this->output->setFormatter($formatter); $this->output->setFormatter($formatter);
@@ -100,6 +118,9 @@ abstract class OutputStyle implements OutputInterface, StyleInterface
return $this->output->isDebug(); return $this->output->isDebug();
} }
/**
* @return OutputInterface
*/
protected function getErrorOutput() protected function getErrorOutput()
{ {
if (!$this->output instanceof ConsoleOutputInterface) { if (!$this->output instanceof ConsoleOutputInterface) {

View File

@@ -20,51 +20,71 @@ interface StyleInterface
{ {
/** /**
* Formats a command title. * Formats a command title.
*
* @return void
*/ */
public function title(string $message); public function title(string $message);
/** /**
* Formats a section title. * Formats a section title.
*
* @return void
*/ */
public function section(string $message); public function section(string $message);
/** /**
* Formats a list. * Formats a list.
*
* @return void
*/ */
public function listing(array $elements); public function listing(array $elements);
/** /**
* Formats informational text. * Formats informational text.
*
* @return void
*/ */
public function text(string|array $message); public function text(string|array $message);
/** /**
* Formats a success result bar. * Formats a success result bar.
*
* @return void
*/ */
public function success(string|array $message); public function success(string|array $message);
/** /**
* Formats an error result bar. * Formats an error result bar.
*
* @return void
*/ */
public function error(string|array $message); public function error(string|array $message);
/** /**
* Formats an warning result bar. * Formats an warning result bar.
*
* @return void
*/ */
public function warning(string|array $message); public function warning(string|array $message);
/** /**
* Formats a note admonition. * Formats a note admonition.
*
* @return void
*/ */
public function note(string|array $message); public function note(string|array $message);
/** /**
* Formats a caution admonition. * Formats a caution admonition.
*
* @return void
*/ */
public function caution(string|array $message); public function caution(string|array $message);
/** /**
* Formats a table. * Formats a table.
*
* @return void
*/ */
public function table(array $headers, array $rows); public function table(array $headers, array $rows);
@@ -90,21 +110,29 @@ interface StyleInterface
/** /**
* Add newline(s). * Add newline(s).
*
* @return void
*/ */
public function newLine(int $count = 1); public function newLine(int $count = 1);
/** /**
* Starts the progress output. * Starts the progress output.
*
* @return void
*/ */
public function progressStart(int $max = 0); public function progressStart(int $max = 0);
/** /**
* Advances the progress output X steps. * Advances the progress output X steps.
*
* @return void
*/ */
public function progressAdvance(int $step = 1); public function progressAdvance(int $step = 1);
/** /**
* Finishes the progress output. * Finishes the progress output.
*
* @return void
*/ */
public function progressFinish(); public function progressFinish();
} }

View File

@@ -60,6 +60,8 @@ class SymfonyStyle extends OutputStyle
/** /**
* Formats a message as a block of text. * Formats a message as a block of text.
*
* @return void
*/ */
public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
{ {
@@ -70,6 +72,9 @@ class SymfonyStyle extends OutputStyle
$this->newLine(); $this->newLine();
} }
/**
* @return void
*/
public function title(string $message) public function title(string $message)
{ {
$this->autoPrependBlock(); $this->autoPrependBlock();
@@ -80,6 +85,9 @@ class SymfonyStyle extends OutputStyle
$this->newLine(); $this->newLine();
} }
/**
* @return void
*/
public function section(string $message) public function section(string $message)
{ {
$this->autoPrependBlock(); $this->autoPrependBlock();
@@ -90,17 +98,21 @@ class SymfonyStyle extends OutputStyle
$this->newLine(); $this->newLine();
} }
/**
* @return void
*/
public function listing(array $elements) public function listing(array $elements)
{ {
$this->autoPrependText(); $this->autoPrependText();
$elements = array_map(function ($element) { $elements = array_map(fn ($element) => sprintf(' * %s', $element), $elements);
return sprintf(' * %s', $element);
}, $elements);
$this->writeln($elements); $this->writeln($elements);
$this->newLine(); $this->newLine();
} }
/**
* @return void
*/
public function text(string|array $message) public function text(string|array $message)
{ {
$this->autoPrependText(); $this->autoPrependText();
@@ -113,27 +125,41 @@ class SymfonyStyle extends OutputStyle
/** /**
* Formats a command comment. * Formats a command comment.
*
* @return void
*/ */
public function comment(string|array $message) public function comment(string|array $message)
{ {
$this->block($message, null, null, '<fg=default;bg=default> // </>', false, false); $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
} }
/**
* @return void
*/
public function success(string|array $message) public function success(string|array $message)
{ {
$this->block($message, 'OK', 'fg=black;bg=green', ' ', true); $this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
} }
/**
* @return void
*/
public function error(string|array $message) public function error(string|array $message)
{ {
$this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
} }
/**
* @return void
*/
public function warning(string|array $message) public function warning(string|array $message)
{ {
$this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
} }
/**
* @return void
*/
public function note(string|array $message) public function note(string|array $message)
{ {
$this->block($message, 'NOTE', 'fg=yellow', ' ! '); $this->block($message, 'NOTE', 'fg=yellow', ' ! ');
@@ -141,17 +167,25 @@ class SymfonyStyle extends OutputStyle
/** /**
* Formats an info message. * Formats an info message.
*
* @return void
*/ */
public function info(string|array $message) public function info(string|array $message)
{ {
$this->block($message, 'INFO', 'fg=green', ' ', true); $this->block($message, 'INFO', 'fg=green', ' ', true);
} }
/**
* @return void
*/
public function caution(string|array $message) public function caution(string|array $message)
{ {
$this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
} }
/**
* @return void
*/
public function table(array $headers, array $rows) public function table(array $headers, array $rows)
{ {
$this->createTable() $this->createTable()
@@ -165,6 +199,8 @@ class SymfonyStyle extends OutputStyle
/** /**
* Formats a horizontal table. * Formats a horizontal table.
*
* @return void
*/ */
public function horizontalTable(array $headers, array $rows) public function horizontalTable(array $headers, array $rows)
{ {
@@ -185,6 +221,8 @@ class SymfonyStyle extends OutputStyle
* * 'A title' * * 'A title'
* * ['key' => 'value'] * * ['key' => 'value']
* * new TableSeparator() * * new TableSeparator()
*
* @return void
*/ */
public function definitionList(string|array|TableSeparator ...$list) public function definitionList(string|array|TableSeparator ...$list)
{ {
@@ -247,17 +285,26 @@ class SymfonyStyle extends OutputStyle
return $this->askQuestion($questionChoice); return $this->askQuestion($questionChoice);
} }
/**
* @return void
*/
public function progressStart(int $max = 0) public function progressStart(int $max = 0)
{ {
$this->progressBar = $this->createProgressBar($max); $this->progressBar = $this->createProgressBar($max);
$this->progressBar->start(); $this->progressBar->start();
} }
/**
* @return void
*/
public function progressAdvance(int $step = 1) public function progressAdvance(int $step = 1)
{ {
$this->getProgressBar()->advance($step); $this->getProgressBar()->advance($step);
} }
/**
* @return void
*/
public function progressFinish() public function progressFinish()
{ {
$this->getProgressBar()->finish(); $this->getProgressBar()->finish();
@@ -311,6 +358,9 @@ class SymfonyStyle extends OutputStyle
return $answer; return $answer;
} }
/**
* @return void
*/
public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
{ {
if (!is_iterable($messages)) { if (!is_iterable($messages)) {
@@ -323,6 +373,9 @@ class SymfonyStyle extends OutputStyle
} }
} }
/**
* @return void
*/
public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{ {
if (!is_iterable($messages)) { if (!is_iterable($messages)) {
@@ -335,6 +388,9 @@ class SymfonyStyle extends OutputStyle
} }
} }
/**
* @return void
*/
public function newLine(int $count = 1) public function newLine(int $count = 1)
{ {
parent::newLine($count); parent::newLine($count);
@@ -381,7 +437,7 @@ class SymfonyStyle extends OutputStyle
{ {
$fetched = $this->bufferedOutput->fetch(); $fetched = $this->bufferedOutput->fetch();
// Prepend new line if last char isn't EOL: // Prepend new line if last char isn't EOL:
if (!str_ends_with($fetched, "\n")) { if ($fetched && !str_ends_with($fetched, "\n")) {
$this->newLine(); $this->newLine();
} }
} }

View File

@@ -131,7 +131,7 @@ class Terminal
return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
} }
private static function initDimensions() private static function initDimensions(): void
{ {
if ('\\' === \DIRECTORY_SEPARATOR) { if ('\\' === \DIRECTORY_SEPARATOR) {
$ansicon = getenv('ANSICON'); $ansicon = getenv('ANSICON');
@@ -165,7 +165,7 @@ class Terminal
/** /**
* Initializes dimensions using the output of an stty columns line. * Initializes dimensions using the output of an stty columns line.
*/ */
private static function initDimensionsUsingStty() private static function initDimensionsUsingStty(): void
{ {
if ($sttyString = self::getSttyColumns()) { if ($sttyString = self::getSttyColumns()) {
if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) { if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) {

View File

@@ -128,7 +128,7 @@ trait TesterTrait
* * verbosity: Sets the output verbosity flag * * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available * * capture_stderr_separately: Make output of stdOut and stdErr separately available
*/ */
private function initOutput(array $options) private function initOutput(array $options): void
{ {
$this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
if (!$this->captureStreamsIndependently) { if (!$this->captureStreamsIndependently) {

View File

@@ -17,9 +17,9 @@
], ],
"require": { "require": {
"php": ">=8.1", "php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3", "symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^1.1|^2|^3", "symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0" "symfony/string": "^5.4|^6.0"
}, },
"require-dev": { "require-dev": {
@@ -34,12 +34,6 @@
"provide": { "provide": {
"psr/log-implementation": "1.0|2.0|3.0" "psr/log-implementation": "1.0|2.0|3.0"
}, },
"suggest": {
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": "",
"psr/log": "For using the console logger"
},
"conflict": { "conflict": {
"symfony/dependency-injection": "<5.4", "symfony/dependency-injection": "<5.4",
"symfony/dotenv": "<5.4", "symfony/dotenv": "<5.4",

Some files were not shown because too many files have changed in this diff Show More