diff --git a/composer.json b/composer.json index fcad6dffc..33324a0aa 100644 --- a/composer.json +++ b/composer.json @@ -23,18 +23,18 @@ "pear/archive_tar": "~1.4.14", "pelago/emogrifier": "^6.0.0", "scssphp/scssphp": "^1.10.3", - "symfony/console": "5.4.*", - "symfony/dotenv": "5.4.*", - "symfony/framework-bundle": "5.4.*", - "symfony/http-foundation": "5.4.*", - "symfony/http-kernel": "5.4.*", - "symfony/twig-bundle": "5.4.*", - "symfony/yaml": "5.4.*", + "symfony/console": "~6.4.0", + "symfony/dotenv": "~6.4.0", + "symfony/framework-bundle": "~6.4.0", + "symfony/http-foundation": "~6.4.0", + "symfony/http-kernel": "~6.4.0", + "symfony/twig-bundle": "~6.4.0", + "symfony/yaml": "~6.4.0", "thenetworg/oauth2-azure": "^2.0" }, "require-dev": { - "symfony/stopwatch": "5.4.*", - "symfony/web-profiler-bundle": "5.4.*" + "symfony/stopwatch": "~6.4.0", + "symfony/web-profiler-bundle": "~6.4.0" }, "suggest": { "ext-libsodium": "Required to use the AttributeEncryptedString.", diff --git a/composer.lock b/composer.lock index c1973cc37..1853960d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cb3883f141f50e1bfbc957880ab93be1", + "content-hash": "8e6766bd095aadc740a60d578a06699e", "packages": [ { "name": "apereo/phpcas", @@ -1523,20 +1523,20 @@ }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -1556,7 +1556,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -1566,9 +1566,9 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "psr/container", @@ -2057,58 +2057,57 @@ }, { "name": "symfony/cache", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "5a0fff46df349f0db3fe242263451fddf5277362" + "reference": "ac2d25f97b17eec6e19760b6b9962a4f7c44356a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/5a0fff46df349f0db3fe242263451fddf5277362", - "reference": "5a0fff46df349f0db3fe242263451fddf5277362", + "url": "https://api.github.com/repos/symfony/cache/zipball/ac2d25f97b17eec6e19760b6b9962a4f7c44356a", + "reference": "ac2d25f97b17eec6e19760b6b9962a4f7c44356a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0", + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.3.6|^7.0" }, "conflict": { "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" }, "provide": { - "psr/cache-implementation": "1.0|2.0", - "psr/simple-cache-implementation": "1.0|2.0", - "symfony/cache-implementation": "1.0|2.0" + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6|^2.0", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "/Tests/" ] @@ -2127,14 +2126,14 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", "homepage": "https://symfony.com", "keywords": [ "caching", "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.11" + "source": "https://github.com/symfony/cache/tree/v6.4.0" }, "funding": [ { @@ -2150,33 +2149,30 @@ "type": "tidelift" } ], - "time": "2022-07-28T15:25:17+00:00" + "time": "2023-11-24T19:28:07+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.5.2", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" + "reference": "1d74b127da04ffa87aa940abe15446fa89653778" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/1d74b127da04ffa87aa940abe15446fa89653778", + "reference": "1d74b127da04ffa87aa940abe15446fa89653778", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0|^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" + "php": ">=8.1", + "psr/cache": "^3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -2213,7 +2209,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/cache-contracts/tree/v3.4.0" }, "funding": [ { @@ -2229,42 +2225,38 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-09-25T12:52:38+00:00" }, { "name": "symfony/config", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "ec79e03125c1d2477e43dde8528535d90cc78379" + "reference": "5d33e0fb707d603330e0edfd4691803a1253572e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/ec79e03125c1d2477e43dde8528535d90cc78379", - "reference": "ec79e03125c1d2477e43dde8528535d90cc78379", + "url": "https://api.github.com/repos/symfony/config/zipball/5d33e0fb707d603330e0edfd4691803a1253572e", + "reference": "5d33e0fb707d603330e0edfd4691803a1253572e", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<4.4" + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2292,7 +2284,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.11" + "source": "https://github.com/symfony/config/tree/v6.4.0" }, "funding": [ { @@ -2308,56 +2300,51 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-11-09T08:28:32+00:00" }, { "name": "symfony/console", - "version": "v5.4.19", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740" + "reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dccb8d251a9017d5994c988b034d3e18aaabf740", - "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740", + "url": "https://api.github.com/repos/symfony/console/zipball/cd9864b47c367450e14ab32f78fdbf98c44c26b6", + "reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2386,12 +2373,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.19" + "source": "https://github.com/symfony/console/tree/v6.4.0" }, "funding": [ { @@ -2407,25 +2394,24 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2023-11-20T16:41:16+00:00" }, { "name": "symfony/css-selector", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "c1681789f059ab756001052164726ae88512ae3d" + "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", - "reference": "c1681789f059ab756001052164726ae88512ae3d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4", + "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -2457,7 +2443,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.11" + "source": "https://github.com/symfony/css-selector/tree/v6.4.0" }, "funding": [ { @@ -2473,52 +2459,44 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2023-10-31T08:40:20+00:00" }, { "name": "symfony/dependency-injection", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62" + "reference": "5dc8ad5f2bbba7046f5947682bf7d868ce80d4e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a8b9251016e9476db73e25fa836904bc0bf74c62", - "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5dc8ad5f2bbba7046f5947682bf7d868ce80d4e8", + "reference": "5dc8ad5f2bbba7046f5947682bf7d868ce80d4e8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/service-contracts": "^1.1.6|^2" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<5.3", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4.26" + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" }, "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0|2.0" + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4.26|^5.0|^6.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2546,7 +2524,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.11" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.0" }, "funding": [ { @@ -2562,29 +2540,29 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-10-31T08:40:20+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.2", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -2613,7 +2591,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2629,29 +2607,32 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/dotenv", - "version": "v5.4.19", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "38190ba62566afa26ca723a795d0a004e061bd2a" + "reference": "d0d584a91422ddaa2c94317200d4c4e5b935555f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/38190ba62566afa26ca723a795d0a004e061bd2a", - "reference": "38190ba62566afa26ca723a795d0a004e061bd2a", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/d0d584a91422ddaa2c94317200d4c4e5b935555f", + "reference": "d0d584a91422ddaa2c94317200d4c4e5b935555f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" }, "require-dev": { - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2684,7 +2665,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v5.4.19" + "source": "https://github.com/symfony/dotenv/tree/v6.4.0" }, "funding": [ { @@ -2700,31 +2681,35 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2023-10-26T18:19:48+00:00" }, { "name": "symfony/error-handler", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8" + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/f75d17cb4769eb38cd5fccbda95cd80a054d35c8", - "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c873490a1c97b3a0a4838afc36ff36c112d02788", + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -2755,7 +2740,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.11" + "source": "https://github.com/symfony/error-handler/tree/v6.4.0" }, "funding": [ { @@ -2771,48 +2756,43 @@ "type": "tidelift" } ], - "time": "2022-07-29T07:37:50+00:00" + "time": "2023-10-18T09:43:34+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.9", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d76d2632cfc2206eecb5ad2b26cd5934082941b6", + "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2840,7 +2820,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.0" }, "funding": [ { @@ -2856,33 +2836,30 @@ "type": "tidelift" } ], - "time": "2022-05-05T16:45:39+00:00" + "time": "2023-07-27T06:52:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.2", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -2919,7 +2896,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" }, "funding": [ { @@ -2935,27 +2912,26 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd" + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6699fb0228d1bc35b12aed6dd5e7455457609ddd", - "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/952a8cb588c3bc6ce76f6023000fb932f16a6e59", + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-mbstring": "~1.8" }, "type": "library", "autoload": { @@ -2983,7 +2959,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.11" + "source": "https://github.com/symfony/filesystem/tree/v6.4.0" }, "funding": [ { @@ -2999,26 +2975,27 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-07-26T17:27:13+00:00" }, { "name": "symfony/finder", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" }, "type": "library", "autoload": { @@ -3046,7 +3023,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.11" + "source": "https://github.com/symfony/finder/tree/v6.4.0" }, "funding": [ { @@ -3062,115 +3039,112 @@ "type": "tidelift" } ], - "time": "2022-07-29T07:37:50+00:00" + "time": "2023-10-31T17:30:12+00:00" }, { "name": "symfony/framework-bundle", - "version": "v5.4.19", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "a208ee578000f9dedcb50a9784ec7ff8706a7bf1" + "reference": "981e016715b4a7f22f58c1d9fdf444311965d25e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a208ee578000f9dedcb50a9784ec7ff8706a7bf1", - "reference": "a208ee578000f9dedcb50a9784ec7ff8706a7bf1", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/981e016715b4a7f22f58c1d9fdf444311965d25e", + "reference": "981e016715b4a7f22f58c1d9fdf444311965d25e", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=7.2.5", - "symfony/cache": "^5.2|^6.0", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^5.4.5|^6.0.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", - "symfony/event-dispatcher": "^5.1|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^5.4|^6.0", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/routing": "^5.3|^6.0" + "symfony/routing": "^6.4|^7.0" }, "conflict": { "doctrine/annotations": "<1.13.1", - "doctrine/cache": "<1.11", "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "phpunit/phpunit": "<5.4.3", - "symfony/asset": "<5.3", - "symfony/console": "<5.2.5", - "symfony/dom-crawler": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/form": "<5.2", - "symfony/http-client": "<4.4", - "symfony/lock": "<4.4", - "symfony/mailer": "<5.2", - "symfony/messenger": "<5.4", - "symfony/mime": "<4.4", - "symfony/property-access": "<5.3", - "symfony/property-info": "<4.4", - "symfony/security-csrf": "<5.3", - "symfony/serializer": "<5.2", - "symfony/service-contracts": ">=3.0", - "symfony/stopwatch": "<4.4", - "symfony/translation": "<5.3", - "symfony/twig-bridge": "<4.4", - "symfony/twig-bundle": "<4.4", - "symfony/validator": "<5.2", - "symfony/web-profiler-bundle": "<4.4", - "symfony/workflow": "<5.2" + "symfony/asset": "<5.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<6.3", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.4", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/scheduler": "<6.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.4", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/workflow": "<6.4" }, "require-dev": { "doctrine/annotations": "^1.13.1|^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.3|^6.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/console": "^5.4.9|^6.0.9", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", - "symfony/dotenv": "^5.1|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/form": "^5.2|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/mailer": "^5.2|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/notifier": "^5.4|^6.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/console": "^5.4.9|^6.0.9|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-client": "^6.3|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/mailer": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.3|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^5.4|^6.0|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/property-info": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/string": "^5.0|^6.0", - "symfony/translation": "^5.3|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/validator": "^5.2|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0", + "symfony/scheduler": "^6.4|^7.0", + "symfony/security-bundle": "^5.4|^6.0|^7.0", + "symfony/semaphore": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/string": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "twig/twig": "^2.10|^3.0" }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/property-info": "For using the property_info service", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", - "symfony/yaml": "For using the debug:config and lint:yaml commands" - }, "type": "symfony-bundle", "autoload": { "psr-4": { @@ -3197,7 +3171,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v5.4.19" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.0" }, "funding": [ { @@ -3213,39 +3187,40 @@ "type": "tidelift" } ], - "time": "2023-01-10T17:40:25+00:00" + "time": "2023-11-25T19:10:27+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.4.20", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a" + "reference": "44a6d39a9cc11e154547d882d5aac1e014440771" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0435363362a47c14e9cf50663cb8ffbf491875a", - "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/44a6d39a9cc11e154547d882d5aac1e014440771", + "reference": "44a6d39a9cc11e154547d882d5aac1e014440771", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.3" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0" - }, - "suggest": { - "symfony/mime": "To use the file extension guesser" + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.3|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3273,7 +3248,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.20" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.0" }, "funding": [ { @@ -3289,76 +3264,77 @@ "type": "tidelift" } ], - "time": "2023-01-29T11:11:52+00:00" + "time": "2023-11-20T16:41:16+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.20", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e" + "reference": "16a29c453966f29466ad34444ce97970a336f3c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aaeec341582d3c160cc9ecfa8b2419ba6c69954e", - "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/16a29c453966f29466ad34444ce97970a336f3c8", + "reference": "16a29c453966f29466ad34444ce97970a336f3c8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^5.0|^6.0", - "symfony/http-foundation": "^5.3.7|^6.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.3", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.3", "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/config": "^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0.5|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.3|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-exporter": "^6.2|^7.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, "type": "library", "autoload": { "psr-4": { @@ -3385,7 +3361,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.20" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.0" }, "funding": [ { @@ -3401,20 +3377,20 @@ "type": "tidelift" } ], - "time": "2023-02-01T08:18:48+00:00" + "time": "2023-11-29T10:40:15+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -3429,7 +3405,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3467,7 +3443,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -3483,20 +3459,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -3508,7 +3484,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3548,7 +3524,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -3564,20 +3540,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.26.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", - "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", "shasum": "" }, "require": { @@ -3591,7 +3567,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3635,7 +3611,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" }, "funding": [ { @@ -3651,20 +3627,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2023-01-26T09:30:37+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -3676,7 +3652,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3719,7 +3695,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -3735,20 +3711,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -3763,7 +3739,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3802,7 +3778,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -3818,20 +3794,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.26.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { @@ -3840,7 +3816,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3878,7 +3854,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -3894,99 +3870,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -3995,7 +3892,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4040,7 +3937,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -4056,29 +3953,30 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.26.0", + "name": "symfony/polyfill-php83", + "version": "v1.28.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4090,7 +3988,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, "classmap": [ "Resources/stubs" @@ -4110,7 +4008,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -4119,7 +4017,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" }, "funding": [ { @@ -4135,47 +4033,40 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2023-08-16T06:22:46+00:00" }, { "name": "symfony/routing", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226" + "reference": "ae014d60d7c8e80be5c3b644a286e91249a3e8f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3e01ccd9b2a3a4167ba2b3c53612762300300226", - "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226", + "url": "https://api.github.com/repos/symfony/routing/zipball/ae014d60d7c8e80be5c3b644a286e91249a3e8f4", + "reference": "ae014d60d7c8e80be5c3b644a286e91249a3e8f4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "doctrine/annotations": "<1.12", - "symfony/config": "<5.3", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.12", + "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -4209,7 +4100,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.11" + "source": "https://github.com/symfony/routing/tree/v6.4.0" }, "funding": [ { @@ -4225,7 +4116,7 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-11-29T08:04:54+00:00" }, { "name": "symfony/service-contracts", @@ -4312,34 +4203,34 @@ }, { "name": "symfony/string", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" + "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", - "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "url": "https://api.github.com/repos/symfony/string/zipball/b45fcf399ea9c3af543a92edf7172ba21174d809", + "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -4378,7 +4269,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.11" + "source": "https://github.com/symfony/string/tree/v6.4.0" }, "funding": [ { @@ -4394,32 +4285,29 @@ "type": "tidelift" } ], - "time": "2022-07-24T16:15:25+00:00" + "time": "2023-11-28T20:41:49+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.5.2", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dee0c6e5b4c07ce851b462530088e64b255ac9c5", + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5", "shasum": "" }, "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/translation-implementation": "" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -4429,7 +4317,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Translation\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4456,7 +4347,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.0" }, "funding": [ { @@ -4472,85 +4363,73 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2023-07-25T15:08:44+00:00" }, { "name": "symfony/twig-bridge", - "version": "v5.4.31", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942" + "reference": "142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942", - "reference": "fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf", + "reference": "142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1|^2|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<5.3", - "symfony/form": "<5.4.21|>=6,<6.2.7", - "symfony/http-foundation": "<5.3", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.2", - "symfony/workflow": "<5.2" + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.2", + "symfony/serializer": "<6.4", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/console": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^5.4.21|^6.2.7", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^4.4|^5.0|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/security-http": "^4.4|^5.0|^6.0", - "symfony/serializer": "^5.2|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.2|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/security-http": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.1|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" }, - "suggest": { - "symfony/asset": "For using the AssetExtension", - "symfony/expression-language": "For using the ExpressionExtension", - "symfony/finder": "", - "symfony/form": "For using the FormExtension", - "symfony/http-kernel": "For using the HttpKernelExtension", - "symfony/routing": "For using the RoutingExtension", - "symfony/security-core": "For using the SecurityExtension", - "symfony/security-csrf": "For using the CsrfExtension", - "symfony/security-http": "For using the LogoutUrlExtension", - "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/translation": "For using the TranslationExtension", - "symfony/var-dumper": "For using the DumpExtension", - "symfony/web-link": "For using the WebLinkExtension", - "symfony/yaml": "For using the YamlExtension" - }, "type": "symfony-bridge", "autoload": { "psr-4": { @@ -4577,7 +4456,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v5.4.31" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.0" }, "funding": [ { @@ -4593,52 +4472,47 @@ "type": "tidelift" } ], - "time": "2023-11-09T21:19:08+00:00" + "time": "2023-11-25T08:25:13+00:00" }, { "name": "symfony/twig-bundle", - "version": "v5.4.19", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "286bd9e38b9bcb142f1eda0a75b0bbeb49ff34bd" + "reference": "35d84393e598dfb774e6a2bf49e5229a8a6dbe4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/286bd9e38b9bcb142f1eda0a75b0bbeb49ff34bd", - "reference": "286bd9e38b9bcb142f1eda0a75b0bbeb49ff34bd", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/35d84393e598dfb774e6a2bf49e5229a8a6dbe4c", + "reference": "35d84393e598dfb774e6a2bf49e5229a8a6dbe4c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/twig-bridge": "^5.3|^6.0", + "composer-runtime-api": ">=2.1", + "php": ">=8.1", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.4", "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<5.3", - "symfony/framework-bundle": "<5.0", - "symfony/service-contracts": ">=3.0", - "symfony/translation": "<5.0" + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "doctrine/cache": "^1.0|^2.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.0|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -4666,7 +4540,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v5.4.19" + "source": "https://github.com/symfony/twig-bundle/tree/v6.4.0" }, "funding": [ { @@ -4682,43 +4556,39 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2023-11-07T14:57:07+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.4.11", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861" + "reference": "c40f7d17e91d8b407582ed51a2bbf83c52c367f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8f306d7b8ef34fb3db3305be97ba8e088fb4861", - "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c40f7d17e91d8b407582ed51a2bbf83c52c367f6", + "reference": "c40f7d17e91d8b407582ed51a2bbf83c52c367f6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" + "symfony/console": "<5.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, "bin": [ "Resources/bin/var-dump-server" ], @@ -4755,7 +4625,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.11" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.0" }, "funding": [ { @@ -4771,28 +4641,28 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-11-09T08:28:32+00:00" }, { "name": "symfony/var-exporter", - "version": "v5.4.10", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340" + "reference": "d6081c0316f0f5921f2010d1766925005a82ea3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8fc03ee75eeece3d9be1ef47d26d79bea1afb340", - "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d6081c0316f0f5921f2010d1766925005a82ea3b", + "reference": "d6081c0316f0f5921f2010d1766925005a82ea3b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -4825,10 +4695,12 @@ "export", "hydrate", "instantiate", + "lazy-loading", + "proxy", "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.4.10" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.0" }, "funding": [ { @@ -4844,35 +4716,32 @@ "type": "tidelift" } ], - "time": "2022-05-27T12:56:18+00:00" + "time": "2023-11-28T20:41:49+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.19", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "71c05db20cb9b54d381a28255f17580e2b7e36a5" + "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/71c05db20cb9b54d381a28255f17580e2b7e36a5", - "reference": "71c05db20cb9b54d381a28255f17580e2b7e36a5", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4f9237a1bb42455d609e6687d2613dde5b41a587", + "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "symfony/console": "^5.4|^6.0|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -4903,7 +4772,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.19" + "source": "https://github.com/symfony/yaml/tree/v6.4.0" }, "funding": [ { @@ -4919,7 +4788,7 @@ "type": "tidelift" } ], - "time": "2023-01-10T18:51:14+00:00" + "time": "2023-11-06T11:00:25+00:00" }, { "name": "thenetworg/oauth2-azure", @@ -5116,21 +4985,21 @@ "packages-dev": [ { "name": "symfony/stopwatch", - "version": "v5.4.19", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "bd2b066090fd6a67039371098fa25a84cb2679ec" + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/bd2b066090fd6a67039371098fa25a84cb2679ec", - "reference": "bd2b066090fd6a67039371098fa25a84cb2679ec", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -5158,7 +5027,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.19" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.0" }, "funding": [ { @@ -5174,43 +5043,42 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2023-02-16T10:14:28+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v5.4.19", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "cd83822071f2bc05583af1e53c1bc46be625a56d" + "reference": "14752d3fb77c3c69b6cee7c03c06e2d6494a196b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/cd83822071f2bc05583af1e53c1bc46be625a56d", - "reference": "cd83822071f2bc05583af1e53c1bc46be625a56d", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/14752d3fb77c3c69b6cee7c03c06e2d6494a196b", + "reference": "14752d3fb77c3c69b6cee7c03c06e2d6494a196b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.3|^6.0", - "symfony/http-kernel": "^5.3|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "php": ">=8.1", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<5.2", - "symfony/form": "<4.4", + "symfony/form": "<5.4", "symfony/mailer": "<5.4", - "symfony/messenger": "<4.4" + "symfony/messenger": "<5.4", + "symfony/twig-bundle": ">=7.0" }, "require-dev": { - "symfony/browser-kit": "^4.4|^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -5237,8 +5105,11 @@ ], "description": "Provides a development tool that gives detailed information about the execution of any request", "homepage": "https://symfony.com", + "keywords": [ + "dev" + ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.4.19" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.0" }, "funding": [ { @@ -5254,7 +5125,7 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2023-11-07T14:57:07+00:00" } ], "aliases": [], @@ -5263,7 +5134,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4.0 <8.2.0", + "php": ">=8.1.0 <8.2.0", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", @@ -5274,7 +5145,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "7.4.0" + "php": "8.1.0" }, "plugin-api-version": "2.6.0" } diff --git a/core/config.class.inc.php b/core/config.class.inc.php index a4c80de2a..9698d9027 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -1707,6 +1707,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ], + 'application.secret' => [ + 'type' => 'string', + 'description' => 'Application secret, uses this value for encrypting the cookies used in the remember me functionality and for creating signed URIs when using ESI (Edge Side Includes).', + 'default' => true, + 'value' => true, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], ]; public function IsProperty($sPropCode) @@ -1886,6 +1894,15 @@ class Config */ protected $m_iPasswordHashAlgo; + /** + * Symfony uses this value for encrypting the cookies used in the remember me functionality and for creating signed URIs when using ESI (Edge Side Includes). + * + * @see https://symfony.com/doc/current/reference/configuration/framework.html#secret + * @since 3.2.0 - N°6934 - Symfony 6.4 - upgrade Symfony bundles to 6.4 + * @var string + */ + protected $m_sAppSecret; + /** * Config constructor. * @@ -1930,6 +1947,7 @@ class Config $this->m_aCharsets = array(); $this->m_bQueryCacheEnabled = DEFAULT_QUERY_CACHE_ENABLED; $this->m_iPasswordHashAlgo = DEFAULT_HASH_ALGO; + $this->m_sAppSecret = bin2hex(random_bytes(16)); //define default encryption params according to php install $aEncryptParams = SimpleCrypt::GetNewDefaultParams(); @@ -2090,6 +2108,7 @@ class Config $this->m_sEncryptionLibrary = isset($MySettings['encryption_library']) ? trim($MySettings['encryption_library']) : $this->m_sEncryptionLibrary; $this->m_aCharsets = isset($MySettings['csv_import_charsets']) ? $MySettings['csv_import_charsets'] : array(); $this->m_iPasswordHashAlgo = isset($MySettings['password_hash_algo']) ? $MySettings['password_hash_algo'] : $this->m_iPasswordHashAlgo; + $this->m_sAppSecret = isset($MySettings['application.secret']) ? trim($MySettings['application.secret']) : $this->m_sAppSecret; } protected function Verify() @@ -2225,6 +2244,11 @@ class Config return $this->m_sEncryptionKey; } + public function GetAppSecret() + { + return $this->m_sAppSecret; + } + public function GetEncryptionLibrary() { return $this->m_sEncryptionLibrary; @@ -2323,6 +2347,12 @@ class Config $this->m_sEncryptionKey = $sKey; } + public function SetAppSecret($sKey) + { + $this->m_sAppSecret = $sKey; + } + + public function SetCSVImportCharsets($aCharsets) { $this->m_aCharsets = $aCharsets; @@ -2374,6 +2404,7 @@ class Config $aSettings['encryption_library'] = $this->m_sEncryptionLibrary; $aSettings['csv_import_charsets'] = $this->m_aCharsets; $aSettings['password_hash_algo'] = $this->m_iPasswordHashAlgo; + $aSettings['application.secret'] = $this->m_sAppSecret; foreach ($this->m_aModuleSettings as $sModule => $aProperties) { @@ -2486,7 +2517,8 @@ class Config 'encryption_key' => $this->m_sEncryptionKey, 'encryption_library' => $this->m_sEncryptionLibrary, 'csv_import_charsets' => $this->m_aCharsets, - 'password_hash_algo' => $this->m_iPasswordHashAlgo + 'password_hash_algo' => $this->m_iPasswordHashAlgo, + 'application.secret' => $this->m_sAppSecret, ); foreach ($aOtherValues as $sKey => $value) { diff --git a/datamodels/2.x/itop-portal-base/portal/config/bridge.php b/datamodels/2.x/itop-portal-base/portal/config/bridge.php new file mode 100644 index 000000000..5c10a65e5 --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/config/bridge.php @@ -0,0 +1,10 @@ +parameters()->set('kernel.secret', MetaModel::GetConfig()->Get('application.secret')); + +}; \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/framework.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/framework.yaml index b5707a878..8740e4e8b 100644 --- a/datamodels/2.x/itop-portal-base/portal/config/packages/framework.yaml +++ b/datamodels/2.x/itop-portal-base/portal/config/packages/framework.yaml @@ -1,5 +1,5 @@ framework: - secret: '%env(APP_SECRET)%' + #secret: (part of iTop general configuration, will be set dynamically via bridge.php) #default_locale: en #csrf_protection: true #http_method_override: true diff --git a/datamodels/2.x/itop-portal-base/portal/config/services.yaml b/datamodels/2.x/itop-portal-base/portal/config/services.yaml index 23f7d8eab..233eeed87 100644 --- a/datamodels/2.x/itop-portal-base/portal/config/services.yaml +++ b/datamodels/2.x/itop-portal-base/portal/config/services.yaml @@ -52,6 +52,9 @@ services: $aCombodoPortalInstanceConf: '%combodo.portal.instance.conf%' $sCombodoPortalInstanceAbsoluteUrl: '%env(string:COMBODO_PORTAL_INSTANCE_ABSOLUTE_URL)%' + # Allow container service injection + Symfony\Component\DependencyInjection\ContainerInterface: '@service_container' + # Makes classes in src/ available to be used as services # This creates a service per class whose id is the fully-qualified class name Combodo\iTop\Portal\: @@ -68,8 +71,6 @@ services: _instanceof: Combodo\iTop\Portal\EventListener\UserProvider: tags: [{ name: 'kernel.event_listener', event: 'kernel.request', priority: 500 }] - calls: - - [setContainer, ['@service_container']] Combodo\iTop\Portal\EventListener\ApplicationContextSetUrlMakerClass: tags: [{ name: 'kernel.event_listener', event: 'kernel.request', priority: 400 }] Combodo\iTop\Portal\EventListener\ApplicationContextSetPluginPropertyClass: @@ -78,8 +79,7 @@ services: tags: [{ name: 'kernel.event_listener', event: 'kernel.request', priority: 200 }] Combodo\iTop\Portal\EventListener\ExceptionListener: tags: [{ name: 'kernel.event_listener', event: 'kernel.exception', priority: 500 }] - calls: - - [setContainer, ['@service_container']] + # Add more service definitions when explicit configuration is needed # Please note that last definitions always *replace* previous ones @@ -103,6 +103,10 @@ services: decorates: 'router' arguments: ['@Combodo\iTop\Portal\Routing\UrlGenerator.inner'] + # UI extension helper + Combodo\iTop\Portal\Helper\UIExtensionsHelper: + arguments: ['@service_container'] + # Standard services combodo.current_contact.photo_url: public: true diff --git a/datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php b/datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php index 80a74f163..b725ef596 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php +++ b/datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php @@ -24,13 +24,12 @@ namespace Combodo\iTop\Portal\EventListener; use Dict; use ExceptionLog; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Twig\Environment; /** * Class ExceptionListener @@ -39,19 +38,28 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; * @package Combodo\iTop\Portal\EventListener * @since 2.7.0 */ -class ExceptionListener implements ContainerAwareInterface +class ExceptionListener { - /** @var \Symfony\Component\DependencyInjection\ContainerInterface $container */ - private $oContainer; /** - * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $oEvent + * Constructor. + * + * @param \Twig\Environment $oTwig + */ + public function __construct( + protected Environment $oTwig + ) + { + } + + /** + * @param ExceptionEvent $oEvent * * @throws \Twig\Error\LoaderError * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError */ - public function onKernelException(ExceptionEvent $oEvent) + public function onKernelException(ExceptionEvent $oEvent) : void { // Get the exception object from the received event $oException = $oEvent->getThrowable(); @@ -112,7 +120,7 @@ class ExceptionListener implements ContainerAwareInterface else { $oResponse = new Response(); - $oResponse->setContent($this->oContainer->get('twig')->render('itop-portal-base/portal/templates/errors/layout.html.twig', $aData)); + $oResponse->setContent($this->oTwig->render('itop-portal-base/portal/templates/errors/layout.html.twig', $aData)); } $oResponse->setStatusCode($iStatusCode); @@ -156,11 +164,5 @@ class ExceptionListener implements ContainerAwareInterface return str_replace($sNormalizedAppRoot, '', $sNormalizedInputPath); } - /** - * @inheritDoc - */ - public function setContainer(ContainerInterface $oContainer = null) - { - $this->oContainer = $oContainer; - } + } \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/EventListener/UserProvider.php b/datamodels/2.x/itop-portal-base/portal/src/EventListener/UserProvider.php index b279f8314..12da61a33 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/EventListener/UserProvider.php +++ b/datamodels/2.x/itop-portal-base/portal/src/EventListener/UserProvider.php @@ -36,14 +36,12 @@ use UserRights; * @package Combodo\iTop\Portal\EventListener * @since 2.7.0 */ -class UserProvider implements ContainerAwareInterface +class UserProvider { /** @var \ModuleDesign $oModuleDesign */ private $oModuleDesign; /** @var string $sPortalId */ private $sPortalId; - /** @var \Symfony\Component\DependencyInjection\ContainerInterface $container */ - private $oContainer; /** @var \User $oUser */ private $oUser; /** @var array $aAllowedPortals */ @@ -133,11 +131,5 @@ class UserProvider implements ContainerAwareInterface return $this->aAllowedPortals; } - /** - * @inheritDoc - */ - public function setContainer(ContainerInterface $oContainer = null) - { - $this->oContainer = $oContainer; - } + } \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/Kernel.php b/datamodels/2.x/itop-portal-base/portal/src/Kernel.php index 5bc544c91..bdbffffa0 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Kernel.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Kernel.php @@ -43,7 +43,7 @@ class Kernel extends BaseKernel /** * @return string */ - public function getCacheDir() + public function getCacheDir(): string { $cacheDir = $_ENV['PORTAL_ID'].'-'.$this->environment; @@ -53,8 +53,8 @@ class Kernel extends BaseKernel /** * @return string */ - public function getLogDir() - { + public function getLogDir(): string + { $logDir = $_ENV['PORTAL_ID'] . '-' . $this->environment; return utils::GetLogPath() . "/portals/$logDir"; @@ -63,8 +63,8 @@ class Kernel extends BaseKernel /** * @return \Generator|iterable|\Symfony\Component\HttpKernel\Bundle\BundleInterface[] */ - public function registerBundles() - { + public function registerBundles(): iterable + { $contents = require $this->getProjectDir().'/config/bundles.php'; foreach ($contents as $class => $envs) { if (isset($envs[$this->environment]) || isset($envs['all'])) { @@ -83,6 +83,7 @@ class Kernel extends BaseKernel $confDir = '../config'; $container->import(new FileResource($this->getProjectDir().'/config/bundles.php')); + $container->import($confDir.'/bridge.php'); $container->parameters()->set('container.dumper.inline_class_loader', true); $container->import($confDir.'/{packages}/*'.self::CONFIG_EXTS); diff --git a/datamodels/2.x/itop-portal-base/portal/src/Routing/UrlGenerator.php b/datamodels/2.x/itop-portal-base/portal/src/Routing/UrlGenerator.php index 3290188c6..4be543a7e 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Routing/UrlGenerator.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Routing/UrlGenerator.php @@ -48,7 +48,7 @@ class UrlGenerator implements RouterInterface /** * @inheritDoc */ - public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) + public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH): string { $parameters = $this->getExtraParams($parameters); @@ -66,7 +66,7 @@ class UrlGenerator implements RouterInterface /** * @inheritDoc */ - public function getContext() + public function getContext(): RequestContext { return $this->router->getContext(); } @@ -82,7 +82,7 @@ class UrlGenerator implements RouterInterface /** * @inheritDoc */ - public function match($pathinfo) + public function match($pathinfo): array { return $this->router->match($pathinfo); } diff --git a/lib/bin/patch-type-declarations b/lib/bin/patch-type-declarations index 1b0b28230..4e63fefc4 100644 --- a/lib/bin/patch-type-declarations +++ b/lib/bin/patch-type-declarations @@ -4,15 +4,16 @@ /** * Proxy PHP file generated by Composer * - * This file includes the referenced bin path (../symfony/error-handler/Resources/bin/patch-type-declarations) using ob_start to remove the shebang if present - * to prevent the shebang from being output on PHP<8 + * This file includes the referenced bin path (../symfony/error-handler/Resources/bin/patch-type-declarations) + * using a stream wrapper to prevent the shebang from being output on PHP<8 * * @generated */ namespace Composer; -$binPath = __DIR__ . "/" . '../symfony/error-handler/Resources/bin/patch-type-declarations'; +$GLOBALS['_composer_bin_dir'] = __DIR__; +$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; if (PHP_VERSION_ID < 80000) { if (!class_exists('Composer\BinProxyWrapper')) { @@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) { { private $handle; private $position; + private $realpath; public function stream_open($path, $mode, $options, &$opened_path) { - // get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution - $opened_path = substr($path, 21); - $opened_path = realpath($opened_path) ?: $opened_path; - $this->handle = fopen($opened_path, $mode); + // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution + $opened_path = substr($path, 17); + $this->realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); $this->position = 0; - // remove all traces of this stream wrapper once it has been used - stream_wrapper_unregister('composer-bin-proxy'); - return (bool) $this->handle; } @@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) { return $operation ? flock($this->handle, $operation) : true; } + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + public function stream_tell() { return $this->position; @@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) { public function stream_stat() { - return fstat($this->handle); + return array(); } public function stream_set_option($option, $arg1, $arg2) { return true; } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } } } - if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { - include("composer-bin-proxy://" . $binPath); - exit(0); + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations'); } } -include $binPath; +return include __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations'; diff --git a/lib/bin/patch-type-declarations.bat b/lib/bin/patch-type-declarations.bat index b92a2da31..2b0707968 100644 --- a/lib/bin/patch-type-declarations.bat +++ b/lib/bin/patch-type-declarations.bat @@ -1,4 +1,5 @@ @ECHO OFF setlocal DISABLEDELAYEDEXPANSION -SET BIN_TARGET=%~dp0/../symfony/error-handler/Resources/bin/patch-type-declarations +SET BIN_TARGET=%~dp0/patch-type-declarations +SET COMPOSER_RUNTIME_BIN_DIR=%~dp0 php "%BIN_TARGET%" %* diff --git a/lib/bin/var-dump-server b/lib/bin/var-dump-server index 10567b2c7..18db1c1eb 100644 --- a/lib/bin/var-dump-server +++ b/lib/bin/var-dump-server @@ -4,15 +4,16 @@ /** * Proxy PHP file generated by Composer * - * This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server) using ob_start to remove the shebang if present - * to prevent the shebang from being output on PHP<8 + * This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server) + * using a stream wrapper to prevent the shebang from being output on PHP<8 * * @generated */ namespace Composer; -$binPath = __DIR__ . "/" . '../symfony/var-dumper/Resources/bin/var-dump-server'; +$GLOBALS['_composer_bin_dir'] = __DIR__; +$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; if (PHP_VERSION_ID < 80000) { if (!class_exists('Composer\BinProxyWrapper')) { @@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) { { private $handle; private $position; + private $realpath; public function stream_open($path, $mode, $options, &$opened_path) { - // get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution - $opened_path = substr($path, 21); - $opened_path = realpath($opened_path) ?: $opened_path; - $this->handle = fopen($opened_path, $mode); + // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution + $opened_path = substr($path, 17); + $this->realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); $this->position = 0; - // remove all traces of this stream wrapper once it has been used - stream_wrapper_unregister('composer-bin-proxy'); - return (bool) $this->handle; } @@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) { return $operation ? flock($this->handle, $operation) : true; } + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + public function stream_tell() { return $this->position; @@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) { public function stream_stat() { - return fstat($this->handle); + return array(); } public function stream_set_option($option, $arg1, $arg2) { return true; } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } } } - if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { - include("composer-bin-proxy://" . $binPath); - exit(0); + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); } } -include $binPath; +return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'; diff --git a/lib/bin/var-dump-server.bat b/lib/bin/var-dump-server.bat index 46836b50c..94333da54 100644 --- a/lib/bin/var-dump-server.bat +++ b/lib/bin/var-dump-server.bat @@ -1,4 +1,5 @@ @ECHO OFF setlocal DISABLEDELAYEDEXPANSION -SET BIN_TARGET=%~dp0/../symfony/var-dumper/Resources/bin/var-dump-server +SET BIN_TARGET=%~dp0/var-dump-server +SET COMPOSER_RUNTIME_BIN_DIR=%~dp0 php "%BIN_TARGET%" %* diff --git a/lib/bin/yaml-lint b/lib/bin/yaml-lint index 182c83f09..388092f5b 100644 --- a/lib/bin/yaml-lint +++ b/lib/bin/yaml-lint @@ -4,15 +4,16 @@ /** * Proxy PHP file generated by Composer * - * This file includes the referenced bin path (../symfony/yaml/Resources/bin/yaml-lint) using ob_start to remove the shebang if present - * to prevent the shebang from being output on PHP<8 + * This file includes the referenced bin path (../symfony/yaml/Resources/bin/yaml-lint) + * using a stream wrapper to prevent the shebang from being output on PHP<8 * * @generated */ namespace Composer; -$binPath = __DIR__ . "/" . '../symfony/yaml/Resources/bin/yaml-lint'; +$GLOBALS['_composer_bin_dir'] = __DIR__; +$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; if (PHP_VERSION_ID < 80000) { if (!class_exists('Composer\BinProxyWrapper')) { @@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) { { private $handle; private $position; + private $realpath; public function stream_open($path, $mode, $options, &$opened_path) { - // get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution - $opened_path = substr($path, 21); - $opened_path = realpath($opened_path) ?: $opened_path; - $this->handle = fopen($opened_path, $mode); + // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution + $opened_path = substr($path, 17); + $this->realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); $this->position = 0; - // remove all traces of this stream wrapper once it has been used - stream_wrapper_unregister('composer-bin-proxy'); - return (bool) $this->handle; } @@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) { return $operation ? flock($this->handle, $operation) : true; } + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + public function stream_tell() { return $this->position; @@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) { public function stream_stat() { - return fstat($this->handle); + return array(); } public function stream_set_option($option, $arg1, $arg2) { return true; } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } } } - if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { - include("composer-bin-proxy://" . $binPath); - exit(0); + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint'); } } -include $binPath; +return include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint'; diff --git a/lib/bin/yaml-lint.bat b/lib/bin/yaml-lint.bat index 0474134c6..fa7663748 100644 --- a/lib/bin/yaml-lint.bat +++ b/lib/bin/yaml-lint.bat @@ -1,4 +1,5 @@ @ECHO OFF setlocal DISABLEDELAYEDEXPANSION -SET BIN_TARGET=%~dp0/../symfony/yaml/Resources/bin/yaml-lint +SET BIN_TARGET=%~dp0/yaml-lint +SET COMPOSER_RUNTIME_BIN_DIR=%~dp0 php "%BIN_TARGET%" %* diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 80e888e24..e4bf469ec 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -526,6 +526,15 @@ return array( 'DataTable' => $baseDir . '/application/datatable.class.inc.php', 'DataTableConfig' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/DataTableConfig/DataTableConfig.php', 'Datamatrix' => $vendorDir . '/combodo/tcpdf/include/barcodes/datamatrix.php', + 'DateError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateError.php', + 'DateException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateException.php', + 'DateInvalidOperationException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php', + 'DateInvalidTimeZoneException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php', + 'DateMalformedIntervalStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php', + 'DateMalformedPeriodStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php', + 'DateMalformedStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php', + 'DateObjectError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php', + 'DateRangeError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php', 'DateTimeFormat' => $baseDir . '/core/datetimeformat.class.inc.php', 'DeadLockLog' => $baseDir . '/core/log.class.inc.php', 'DefaultLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', @@ -709,7 +718,6 @@ return array( 'ItopCounter' => $baseDir . '/core/counter.class.inc.php', 'JSButtonItem' => $baseDir . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', - 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'JsonPPage' => $baseDir . '/sources/Application/WebPage/JsonPPage.php', 'JsonPage' => $baseDir . '/sources/Application/WebPage/JsonPage.php', 'KeyValueStore' => $baseDir . '/core/counter.class.inc.php', @@ -1123,6 +1131,7 @@ return array( 'OqlObjectQuery' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'OqlQuery' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'OqlUnionQuery' => $baseDir . '/core/oql/oqlquery.class.inc.php', + 'Override' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/Override.php', 'PDF417' => $vendorDir . '/combodo/tcpdf/include/barcodes/pdf417.php', 'PDFBulkExport' => $baseDir . '/core/pdfbulkexport.class.inc.php', 'PDFPage' => $baseDir . '/sources/Application/WebPage/PDFPage.php', @@ -1450,7 +1459,6 @@ return array( 'RestResultWithObjects' => $baseDir . '/core/restservices.class.inc.php', 'RestResultWithRelations' => $baseDir . '/core/restservices.class.inc.php', 'RestUtils' => $baseDir . '/application/applicationextension.inc.php', - 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RotatingLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', 'RowStatus' => $baseDir . '/core/bulkchange.class.inc.php', 'RowStatus_Disappeared' => $baseDir . '/core/bulkchange.class.inc.php', @@ -1582,10 +1590,12 @@ return array( 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'SummaryCardService' => $baseDir . '/sources/Service/SummaryCard/SummaryCardService.php', 'Symfony\\Bridge\\Twig\\AppVariable' => $vendorDir . '/symfony/twig-bridge/AppVariable.php', + 'Symfony\\Bridge\\Twig\\Attribute\\Template' => $vendorDir . '/symfony/twig-bridge/Attribute/Template.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => $vendorDir . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bridge/Command/LintCommand.php', 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => $vendorDir . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', 'Symfony\\Bridge\\Twig\\ErrorRenderer\\TwigErrorRenderer' => $vendorDir . '/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php', + 'Symfony\\Bridge\\Twig\\EventListener\\TemplateAttributeListener' => $vendorDir . '/symfony/twig-bridge/EventListener/TemplateAttributeListener.php', 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => $vendorDir . '/symfony/twig-bridge/Extension/AssetExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => $vendorDir . '/symfony/twig-bridge/Extension/CodeExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CsrfExtension' => $vendorDir . '/symfony/twig-bridge/Extension/CsrfExtension.php', @@ -1593,9 +1603,12 @@ return array( 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => $vendorDir . '/symfony/twig-bridge/Extension/DumpExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ExpressionExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => $vendorDir . '/symfony/twig-bridge/Extension/FormExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\HtmlSanitizerExtension' => $vendorDir . '/symfony/twig-bridge/Extension/HtmlSanitizerExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpFoundationExtension' => $vendorDir . '/symfony/twig-bridge/Extension/HttpFoundationExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelExtension' => $vendorDir . '/symfony/twig-bridge/Extension/HttpKernelExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelRuntime' => $vendorDir . '/symfony/twig-bridge/Extension/HttpKernelRuntime.php', + 'Symfony\\Bridge\\Twig\\Extension\\ImportMapExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ImportMapExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\ImportMapRuntime' => $vendorDir . '/symfony/twig-bridge/Extension/ImportMapRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\LogoutUrlExtension' => $vendorDir . '/symfony/twig-bridge/Extension/LogoutUrlExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ProfilerExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\RoutingExtension' => $vendorDir . '/symfony/twig-bridge/Extension/RoutingExtension.php', @@ -1622,6 +1635,8 @@ return array( 'Symfony\\Bridge\\Twig\\Node\\StopwatchNode' => $vendorDir . '/symfony/twig-bridge/Node/StopwatchNode.php', 'Symfony\\Bridge\\Twig\\Node\\TransDefaultDomainNode' => $vendorDir . '/symfony/twig-bridge/Node/TransDefaultDomainNode.php', 'Symfony\\Bridge\\Twig\\Node\\TransNode' => $vendorDir . '/symfony/twig-bridge/Node/TransNode.php', + 'Symfony\\Bridge\\Twig\\Test\\FormLayoutTestCase' => $vendorDir . '/symfony/twig-bridge/Test/FormLayoutTestCase.php', + 'Symfony\\Bridge\\Twig\\Test\\Traits\\RuntimeLoaderProvider' => $vendorDir . '/symfony/twig-bridge/Test/Traits/RuntimeLoaderProvider.php', 'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php', @@ -1644,6 +1659,7 @@ return array( 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheClearCommand' => $vendorDir . '/symfony/framework-bundle/Command/CacheClearCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolClearCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolClearCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolDeleteCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolDeleteCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolInvalidateTagsCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolListCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolListCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolPruneCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolPruneCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheWarmupCommand' => $vendorDir . '/symfony/framework-bundle/Command/CacheWarmupCommand.php', @@ -1686,22 +1702,27 @@ return array( 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AssetsContextPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ContainerBuilderDebugDumpPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\DataCollectorTranslatorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\EnableLoggerDebugModePass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ErrorLoggerCompilerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\LoggingTranslatorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ProfilerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RemoveUnusedSessionMarshallingHandlerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SessionPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerRealRefPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerWeakRefPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\UnusedTagsPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\WorkflowGuardListenerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Configuration' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Configuration.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\FrameworkExtension' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\VirtualRequestStackPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\ConsoleProfilerListener' => $vendorDir . '/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php', 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SuggestMissingPackageSubscriber' => $vendorDir . '/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php', 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle' => $vendorDir . '/symfony/framework-bundle/FrameworkBundle.php', 'Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache' => $vendorDir . '/symfony/framework-bundle/HttpCache/HttpCache.php', 'Symfony\\Bundle\\FrameworkBundle\\KernelBrowser' => $vendorDir . '/symfony/framework-bundle/KernelBrowser.php', 'Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait' => $vendorDir . '/symfony/framework-bundle/Kernel/MicroKernelTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\AnnotatedRouteControllerLoader' => $vendorDir . '/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\AttributeRouteControllerLoader' => $vendorDir . '/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\Attribute\\AsRoutingConditionService' => $vendorDir . '/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader' => $vendorDir . '/symfony/framework-bundle/Routing/DelegatingLoader.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher' => $vendorDir . '/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInterface' => $vendorDir . '/symfony/framework-bundle/Routing/RouteLoaderInterface.php', @@ -1709,8 +1730,6 @@ return array( 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => $vendorDir . '/symfony/framework-bundle/Secrets/AbstractVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => $vendorDir . '/symfony/framework-bundle/Secrets/DotenvVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => $vendorDir . '/symfony/framework-bundle/Secrets/SodiumVault.php', - 'Symfony\\Bundle\\FrameworkBundle\\Session\\DeprecatedSessionFactory' => $vendorDir . '/symfony/framework-bundle/Session/DeprecatedSessionFactory.php', - 'Symfony\\Bundle\\FrameworkBundle\\Session\\ServiceSessionFactory' => $vendorDir . '/symfony/framework-bundle/Session/ServiceSessionFactory.php', 'Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => $vendorDir . '/symfony/framework-bundle/Translation/Translator.php', 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheWarmer' => $vendorDir . '/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php', 'Symfony\\Bundle\\TwigBundle\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bundle/Command/LintCommand.php', @@ -1742,7 +1761,6 @@ return array( 'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => $vendorDir . '/symfony/cache/Adapter/ChainAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter' => $vendorDir . '/symfony/cache/Adapter/CouchbaseBucketAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter' => $vendorDir . '/symfony/cache/Adapter/CouchbaseCollectionAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\DoctrineDbalAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineDbalAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', @@ -1766,7 +1784,6 @@ return array( 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPass.php', 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', - 'Symfony\\Component\\Cache\\DoctrineProvider' => $vendorDir . '/symfony/cache/DoctrineProvider.php', 'Symfony\\Component\\Cache\\Exception\\CacheException' => $vendorDir . '/symfony/cache/Exception/CacheException.php', 'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/cache/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Cache\\Exception\\LogicException' => $vendorDir . '/symfony/cache/Exception/LogicException.php', @@ -1787,10 +1804,15 @@ return array( 'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemCommonTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemTrait.php', 'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => $vendorDir . '/symfony/cache/Traits/ProxyTrait.php', + 'Symfony\\Component\\Cache\\Traits\\Redis5Proxy' => $vendorDir . '/symfony/cache/Traits/Redis5Proxy.php', + 'Symfony\\Component\\Cache\\Traits\\Redis6Proxy' => $vendorDir . '/symfony/cache/Traits/Redis6Proxy.php', + 'Symfony\\Component\\Cache\\Traits\\RedisCluster5Proxy' => $vendorDir . '/symfony/cache/Traits/RedisCluster5Proxy.php', + 'Symfony\\Component\\Cache\\Traits\\RedisCluster6Proxy' => $vendorDir . '/symfony/cache/Traits/RedisCluster6Proxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterNodeProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisProxy' => $vendorDir . '/symfony/cache/Traits/RedisProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisTrait' => $vendorDir . '/symfony/cache/Traits/RedisTrait.php', + 'Symfony\\Component\\Cache\\Traits\\RelayProxy' => $vendorDir . '/symfony/cache/Traits/RelayProxy.php', 'Symfony\\Component\\Config\\Builder\\ClassBuilder' => $vendorDir . '/symfony/config/Builder/ClassBuilder.php', 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGenerator.php', 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php', @@ -1822,7 +1844,10 @@ return array( 'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/TreeBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ValidationBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\ConfigurableInterface' => $vendorDir . '/symfony/config/Definition/ConfigurableInterface.php', + 'Symfony\\Component\\Config\\Definition\\Configuration' => $vendorDir . '/symfony/config/Definition/Configuration.php', 'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => $vendorDir . '/symfony/config/Definition/ConfigurationInterface.php', + 'Symfony\\Component\\Config\\Definition\\Configurator\\DefinitionConfigurator' => $vendorDir . '/symfony/config/Definition/Configurator/DefinitionConfigurator.php', 'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', 'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', 'Symfony\\Component\\Config\\Definition\\EnumNode' => $vendorDir . '/symfony/config/Definition/EnumNode.php', @@ -1835,6 +1860,7 @@ return array( 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => $vendorDir . '/symfony/config/Definition/Exception/UnsetKeyException.php', 'Symfony\\Component\\Config\\Definition\\FloatNode' => $vendorDir . '/symfony/config/Definition/FloatNode.php', 'Symfony\\Component\\Config\\Definition\\IntegerNode' => $vendorDir . '/symfony/config/Definition/IntegerNode.php', + 'Symfony\\Component\\Config\\Definition\\Loader\\DefinitionFileLoader' => $vendorDir . '/symfony/config/Definition/Loader/DefinitionFileLoader.php', 'Symfony\\Component\\Config\\Definition\\NodeInterface' => $vendorDir . '/symfony/config/Definition/NodeInterface.php', 'Symfony\\Component\\Config\\Definition\\NumericNode' => $vendorDir . '/symfony/config/Definition/NumericNode.php', 'Symfony\\Component\\Config\\Definition\\Processor' => $vendorDir . '/symfony/config/Definition/Processor.php', @@ -1848,6 +1874,7 @@ return array( 'Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php', 'Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php', 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php', + 'Symfony\\Component\\Config\\Loader\\DirectoryAwareLoaderInterface' => $vendorDir . '/symfony/config/Loader/DirectoryAwareLoaderInterface.php', 'Symfony\\Component\\Config\\Loader\\FileLoader' => $vendorDir . '/symfony/config/Loader/FileLoader.php', 'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/config/Loader/GlobFileLoader.php', 'Symfony\\Component\\Config\\Loader\\Loader' => $vendorDir . '/symfony/config/Loader/Loader.php', @@ -1886,19 +1913,25 @@ return array( 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', 'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php', 'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => $vendorDir . '/symfony/console/Command/SignalableCommandInterface.php', + 'Symfony\\Component\\Console\\Command\\TraceableCommand' => $vendorDir . '/symfony/console/Command/TraceableCommand.php', 'Symfony\\Component\\Console\\Completion\\CompletionInput' => $vendorDir . '/symfony/console/Completion/CompletionInput.php', 'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => $vendorDir . '/symfony/console/Completion/CompletionSuggestions.php', 'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/BashCompletionOutput.php', 'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => $vendorDir . '/symfony/console/Completion/Output/CompletionOutputInterface.php', + 'Symfony\\Component\\Console\\Completion\\Output\\FishCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/FishCompletionOutput.php', + 'Symfony\\Component\\Console\\Completion\\Output\\ZshCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/ZshCompletionOutput.php', 'Symfony\\Component\\Console\\Completion\\Suggestion' => $vendorDir . '/symfony/console/Completion/Suggestion.php', 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', 'Symfony\\Component\\Console\\Cursor' => $vendorDir . '/symfony/console/Cursor.php', + 'Symfony\\Component\\Console\\DataCollector\\CommandDataCollector' => $vendorDir . '/symfony/console/DataCollector/CommandDataCollector.php', + 'Symfony\\Component\\Console\\Debug\\CliRequest' => $vendorDir . '/symfony/console/Debug/CliRequest.php', 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php', 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\ReStructuredTextDescriptor' => $vendorDir . '/symfony/console/Descriptor/ReStructuredTextDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php', 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php', @@ -1914,6 +1947,7 @@ return array( 'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', 'Symfony\\Component\\Console\\Exception\\MissingInputException' => $vendorDir . '/symfony/console/Exception/MissingInputException.php', 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RunCommandFailedException' => $vendorDir . '/symfony/console/Exception/RunCommandFailedException.php', 'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatter.php', 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatterStyle.php', @@ -1931,6 +1965,7 @@ return array( 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', 'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php', 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\OutputWrapper' => $vendorDir . '/symfony/console/Helper/OutputWrapper.php', 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php', 'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php', 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php', @@ -1953,6 +1988,10 @@ return array( 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php', 'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php', 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => $vendorDir . '/symfony/console/Messenger/RunCommandContext.php', + 'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => $vendorDir . '/symfony/console/Messenger/RunCommandMessage.php', + 'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => $vendorDir . '/symfony/console/Messenger/RunCommandMessageHandler.php', + 'Symfony\\Component\\Console\\Output\\AnsiColorMode' => $vendorDir . '/symfony/console/Output/AnsiColorMode.php', 'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', @@ -1965,6 +2004,7 @@ return array( 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', 'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SignalRegistry\\SignalMap' => $vendorDir . '/symfony/console/SignalRegistry/SignalMap.php', 'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => $vendorDir . '/symfony/console/SignalRegistry/SignalRegistry.php', 'Symfony\\Component\\Console\\SingleCommandApplication' => $vendorDir . '/symfony/console/SingleCommandApplication.php', 'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', @@ -2029,15 +2069,26 @@ return array( 'Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => $vendorDir . '/symfony/dependency-injection/Argument/ArgumentInterface.php', 'Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => $vendorDir . '/symfony/dependency-injection/Argument/BoundArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/IteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\LazyClosure' => $vendorDir . '/symfony/dependency-injection/Argument/LazyClosure.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => $vendorDir . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', 'Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => $vendorDir . '/symfony/dependency-injection/Argument/RewindableGenerator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AsAlias' => $vendorDir . '/symfony/dependency-injection/Attribute/AsAlias.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AsDecorator' => $vendorDir . '/symfony/dependency-injection/Attribute/AsDecorator.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\AsTaggedItem' => $vendorDir . '/symfony/dependency-injection/Attribute/AsTaggedItem.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure' => $vendorDir . '/symfony/dependency-injection/Attribute/Autoconfigure.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\AutoconfigureTag' => $vendorDir . '/symfony/dependency-injection/Attribute/AutoconfigureTag.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Autowire' => $vendorDir . '/symfony/dependency-injection/Attribute/Autowire.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable' => $vendorDir . '/symfony/dependency-injection/Attribute/AutowireCallable.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireDecorated' => $vendorDir . '/symfony/dependency-injection/Attribute/AutowireDecorated.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireIterator' => $vendorDir . '/symfony/dependency-injection/Attribute/AutowireIterator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator' => $vendorDir . '/symfony/dependency-injection/Attribute/AutowireLocator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireServiceClosure' => $vendorDir . '/symfony/dependency-injection/Attribute/AutowireServiceClosure.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Exclude' => $vendorDir . '/symfony/dependency-injection/Attribute/Exclude.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\MapDecorated' => $vendorDir . '/symfony/dependency-injection/Attribute/MapDecorated.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator' => $vendorDir . '/symfony/dependency-injection/Attribute/TaggedIterator.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator' => $vendorDir . '/symfony/dependency-injection/Attribute/TaggedLocator.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\Target' => $vendorDir . '/symfony/dependency-injection/Attribute/Target.php', @@ -2048,6 +2099,7 @@ return array( 'Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AttributeAutoconfigurationPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireAsDecoratorPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowirePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredMethodsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredPropertiesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php', @@ -2071,6 +2123,7 @@ return array( 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveBuildParametersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', @@ -2086,7 +2139,6 @@ return array( 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNamedArgumentsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNoPreloadPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolvePrivatesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveServiceSubscribersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveTaggedIteratorArgumentPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php', @@ -2129,13 +2181,18 @@ return array( 'Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException' => $vendorDir . '/symfony/dependency-injection/Exception/ServiceNotFoundException.php', 'Symfony\\Component\\DependencyInjection\\ExpressionLanguage' => $vendorDir . '/symfony/dependency-injection/ExpressionLanguage.php', 'Symfony\\Component\\DependencyInjection\\ExpressionLanguageProvider' => $vendorDir . '/symfony/dependency-injection/ExpressionLanguageProvider.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\AbstractExtension' => $vendorDir . '/symfony/dependency-injection/Extension/AbstractExtension.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ConfigurableExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php', 'Symfony\\Component\\DependencyInjection\\Extension\\ConfigurationExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php', 'Symfony\\Component\\DependencyInjection\\Extension\\Extension' => $vendorDir . '/symfony/dependency-injection/Extension/Extension.php', 'Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/ExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ExtensionTrait' => $vendorDir . '/symfony/dependency-injection/Extension/ExtensionTrait.php', 'Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/PrependExtensionInterface.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\InstantiatorInterface' => $vendorDir . '/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\LazyServiceInstantiator' => $vendorDir . '/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\RealServiceInstantiator' => $vendorDir . '/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\DumperInterface' => $vendorDir . '/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\LazyServiceDumper' => $vendorDir . '/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\NullDumper' => $vendorDir . '/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\ProxyHelper' => $vendorDir . '/symfony/dependency-injection/LazyProxy/ProxyHelper.php', 'Symfony\\Component\\DependencyInjection\\Loader\\ClosureLoader' => $vendorDir . '/symfony/dependency-injection/Loader/ClosureLoader.php', @@ -2146,6 +2203,7 @@ return array( 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\DefaultsConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\EnvConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\FromCallableConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InlineServiceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InstanceofConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ParametersConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php', @@ -2161,10 +2219,12 @@ return array( 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\CallTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ClassTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ConfiguratorTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ConstructorTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DecorateTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DeprecateTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FactoryTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FileTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FromCallableTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\LazyTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ParentTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\PropertyTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php', @@ -2209,6 +2269,7 @@ return array( 'Symfony\\Component\\ErrorHandler\\ErrorHandler' => $vendorDir . '/symfony/error-handler/ErrorHandler.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\CliErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\ErrorRendererInterface' => $vendorDir . '/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\FileLinkFormatter' => $vendorDir . '/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\HtmlErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\SerializerErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php', 'Symfony\\Component\\ErrorHandler\\Error\\ClassNotFoundError' => $vendorDir . '/symfony/error-handler/Error/ClassNotFoundError.php', @@ -2230,7 +2291,6 @@ return array( 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php', 'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php', 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', - 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', @@ -2265,6 +2325,7 @@ return array( 'Symfony\\Component\\HttpFoundation\\AcceptHeader' => $vendorDir . '/symfony/http-foundation/AcceptHeader.php', 'Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => $vendorDir . '/symfony/http-foundation/AcceptHeaderItem.php', 'Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => $vendorDir . '/symfony/http-foundation/BinaryFileResponse.php', + 'Symfony\\Component\\HttpFoundation\\ChainRequestMatcher' => $vendorDir . '/symfony/http-foundation/ChainRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\Cookie' => $vendorDir . '/symfony/http-foundation/Cookie.php', 'Symfony\\Component\\HttpFoundation\\Exception\\BadRequestException' => $vendorDir . '/symfony/http-foundation/Exception/BadRequestException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => $vendorDir . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', @@ -2272,6 +2333,7 @@ return array( 'Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface' => $vendorDir . '/symfony/http-foundation/Exception/RequestExceptionInterface.php', 'Symfony\\Component\\HttpFoundation\\Exception\\SessionNotFoundException' => $vendorDir . '/symfony/http-foundation/Exception/SessionNotFoundException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\SuspiciousOperationException' => $vendorDir . '/symfony/http-foundation/Exception/SuspiciousOperationException.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\UnexpectedValueException' => $vendorDir . '/symfony/http-foundation/Exception/UnexpectedValueException.php', 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => $vendorDir . '/symfony/http-foundation/ExpressionRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\FileBag' => $vendorDir . '/symfony/http-foundation/FileBag.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', @@ -2296,18 +2358,28 @@ return array( 'Symfony\\Component\\HttpFoundation\\JsonResponse' => $vendorDir . '/symfony/http-foundation/JsonResponse.php', 'Symfony\\Component\\HttpFoundation\\ParameterBag' => $vendorDir . '/symfony/http-foundation/ParameterBag.php', 'Symfony\\Component\\HttpFoundation\\RateLimiter\\AbstractRequestRateLimiter' => $vendorDir . '/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php', + 'Symfony\\Component\\HttpFoundation\\RateLimiter\\PeekableRequestRateLimiterInterface' => $vendorDir . '/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php', 'Symfony\\Component\\HttpFoundation\\RateLimiter\\RequestRateLimiterInterface' => $vendorDir . '/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php', 'Symfony\\Component\\HttpFoundation\\RedirectResponse' => $vendorDir . '/symfony/http-foundation/RedirectResponse.php', 'Symfony\\Component\\HttpFoundation\\Request' => $vendorDir . '/symfony/http-foundation/Request.php', 'Symfony\\Component\\HttpFoundation\\RequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface' => $vendorDir . '/symfony/http-foundation/RequestMatcherInterface.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\AttributesRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\ExpressionRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\HostRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\IpsRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\IsJsonRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\MethodRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\PathRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\PortRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\SchemeRequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\RequestStack' => $vendorDir . '/symfony/http-foundation/RequestStack.php', 'Symfony\\Component\\HttpFoundation\\Response' => $vendorDir . '/symfony/http-foundation/Response.php', 'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag' => $vendorDir . '/symfony/http-foundation/ResponseHeaderBag.php', 'Symfony\\Component\\HttpFoundation\\ServerBag' => $vendorDir . '/symfony/http-foundation/ServerBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag' => $vendorDir . '/symfony/http-foundation/Session/Attribute/AttributeBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface' => $vendorDir . '/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag' => $vendorDir . '/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\FlashBagAwareSessionInterface' => $vendorDir . '/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag' => $vendorDir . '/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag' => $vendorDir . '/symfony/http-foundation/Session/Flash/FlashBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface' => $vendorDir . '/symfony/http-foundation/Session/Flash/FlashBagInterface.php', @@ -2340,14 +2412,25 @@ return array( 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorageFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\ServiceSessionFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', + 'Symfony\\Component\\HttpFoundation\\StreamedJsonResponse' => $vendorDir . '/symfony/http-foundation/StreamedJsonResponse.php', 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => $vendorDir . '/symfony/http-foundation/StreamedResponse.php', + 'Symfony\\Component\\HttpFoundation\\UriSigner' => $vendorDir . '/symfony/http-foundation/UriSigner.php', 'Symfony\\Component\\HttpFoundation\\UrlHelper' => $vendorDir . '/symfony/http-foundation/UrlHelper.php', - 'Symfony\\Component\\HttpKernel\\Attribute\\ArgumentInterface' => $vendorDir . '/symfony/http-kernel/Attribute/ArgumentInterface.php', 'Symfony\\Component\\HttpKernel\\Attribute\\AsController' => $vendorDir . '/symfony/http-kernel/Attribute/AsController.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\AsTargetedValueResolver' => $vendorDir . '/symfony/http-kernel/Attribute/AsTargetedValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\Cache' => $vendorDir . '/symfony/http-kernel/Attribute/Cache.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapDateTime' => $vendorDir . '/symfony/http-kernel/Attribute/MapDateTime.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapQueryParameter' => $vendorDir . '/symfony/http-kernel/Attribute/MapQueryParameter.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapQueryString' => $vendorDir . '/symfony/http-kernel/Attribute/MapQueryString.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapRequestPayload' => $vendorDir . '/symfony/http-kernel/Attribute/MapRequestPayload.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\ValueResolver' => $vendorDir . '/symfony/http-kernel/Attribute/ValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\WithHttpStatus' => $vendorDir . '/symfony/http-kernel/Attribute/WithHttpStatus.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\WithLogLevel' => $vendorDir . '/symfony/http-kernel/Attribute/WithLogLevel.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle' => $vendorDir . '/symfony/http-kernel/Bundle/AbstractBundle.php', 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => $vendorDir . '/symfony/http-kernel/Bundle/Bundle.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\BundleExtension' => $vendorDir . '/symfony/http-kernel/Bundle/BundleExtension.php', 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => $vendorDir . '/symfony/http-kernel/Bundle/BundleInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => $vendorDir . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\ChainCacheClearer' => $vendorDir . '/symfony/http-kernel/CacheClearer/ChainCacheClearer.php', @@ -2362,13 +2445,18 @@ return array( 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactoryInterface' => $vendorDir . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\BackedEnumValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DateTimeValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\QueryParameterValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestPayloadValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\UidValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', @@ -2378,6 +2466,7 @@ return array( 'Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => $vendorDir . '/symfony/http-kernel/Controller/ErrorController.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ValueResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\ConfigDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/ConfigDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/DataCollector.php', @@ -2391,8 +2480,10 @@ return array( 'Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ErrorHandlerConfigurator' => $vendorDir . '/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php', 'Symfony\\Component\\HttpKernel\\Debug\\FileLinkFormatter' => $vendorDir . '/symfony/http-kernel/Debug/FileLinkFormatter.php', 'Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\HttpKernel\\Debug\\VirtualRequestStack' => $vendorDir . '/symfony/http-kernel/Debug/VirtualRequestStack.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddAnnotatedClassesToCachePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ControllerArgumentValueResolverPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php', @@ -2407,8 +2498,8 @@ return array( 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/AbstractSessionListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => $vendorDir . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\CacheAttributeListener' => $vendorDir . '/symfony/http-kernel/EventListener/CacheAttributeListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => $vendorDir . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => $vendorDir . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => $vendorDir . '/symfony/http-kernel/EventListener/DumpListener.php', @@ -2422,7 +2513,6 @@ return array( 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/SessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => $vendorDir . '/symfony/http-kernel/EventListener/SurrogateListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/TestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => $vendorDir . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', 'Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerEvent.php', @@ -2442,11 +2532,13 @@ return array( 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => $vendorDir . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', 'Symfony\\Component\\HttpKernel\\Exception\\InvalidMetadataException' => $vendorDir . '/symfony/http-kernel/Exception/InvalidMetadataException.php', 'Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\LockedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/LockedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException' => $vendorDir . '/symfony/http-kernel/Exception/NotFoundHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/PreconditionFailedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ResolverNotFoundException' => $vendorDir . '/symfony/http-kernel/Exception/ResolverNotFoundException.php', 'Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => $vendorDir . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', @@ -2480,6 +2572,7 @@ return array( 'Symfony\\Component\\HttpKernel\\Kernel' => $vendorDir . '/symfony/http-kernel/Kernel.php', 'Symfony\\Component\\HttpKernel\\KernelEvents' => $vendorDir . '/symfony/http-kernel/KernelEvents.php', 'Symfony\\Component\\HttpKernel\\KernelInterface' => $vendorDir . '/symfony/http-kernel/KernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\DebugLoggerConfigurator' => $vendorDir . '/symfony/http-kernel/Log/DebugLoggerConfigurator.php', 'Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface' => $vendorDir . '/symfony/http-kernel/Log/DebugLoggerInterface.php', 'Symfony\\Component\\HttpKernel\\Log\\Logger' => $vendorDir . '/symfony/http-kernel/Log/Logger.php', 'Symfony\\Component\\HttpKernel\\Profiler\\FileProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/FileProfilerStorage.php', @@ -2491,7 +2584,9 @@ return array( 'Symfony\\Component\\HttpKernel\\UriSigner' => $vendorDir . '/symfony/http-kernel/UriSigner.php', 'Symfony\\Component\\Routing\\Alias' => $vendorDir . '/symfony/routing/Alias.php', 'Symfony\\Component\\Routing\\Annotation\\Route' => $vendorDir . '/symfony/routing/Annotation/Route.php', + 'Symfony\\Component\\Routing\\Attribute\\Route' => $vendorDir . '/symfony/routing/Attribute/Route.php', 'Symfony\\Component\\Routing\\CompiledRoute' => $vendorDir . '/symfony/routing/CompiledRoute.php', + 'Symfony\\Component\\Routing\\DependencyInjection\\AddExpressionLanguageProvidersPass' => $vendorDir . '/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php', 'Symfony\\Component\\Routing\\DependencyInjection\\RoutingResolverPass' => $vendorDir . '/symfony/routing/DependencyInjection/RoutingResolverPass.php', 'Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/routing/Exception/ExceptionInterface.php', 'Symfony\\Component\\Routing\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/routing/Exception/InvalidArgumentException.php', @@ -2513,6 +2608,9 @@ return array( 'Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationClassLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AttributeClassLoader' => $vendorDir . '/symfony/routing/Loader/AttributeClassLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AttributeDirectoryLoader' => $vendorDir . '/symfony/routing/Loader/AttributeDirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AttributeFileLoader' => $vendorDir . '/symfony/routing/Loader/AttributeFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\ClosureLoader' => $vendorDir . '/symfony/routing/Loader/ClosureLoader.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\AliasConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/AliasConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\CollectionConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/CollectionConfigurator.php', @@ -2529,6 +2627,7 @@ return array( 'Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/routing/Loader/GlobFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\ObjectLoader' => $vendorDir . '/symfony/routing/Loader/ObjectLoader.php', 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/routing/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\Psr4DirectoryLoader' => $vendorDir . '/symfony/routing/Loader/Psr4DirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/routing/Loader/XmlFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/routing/Loader/YamlFileLoader.php', 'Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/CompiledUrlMatcher.php', @@ -2546,9 +2645,10 @@ return array( 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/UrlMatcherInterface.php', 'Symfony\\Component\\Routing\\RequestContext' => $vendorDir . '/symfony/routing/RequestContext.php', 'Symfony\\Component\\Routing\\RequestContextAwareInterface' => $vendorDir . '/symfony/routing/RequestContextAwareInterface.php', + 'Symfony\\Component\\Routing\\Requirement\\EnumRequirement' => $vendorDir . '/symfony/routing/Requirement/EnumRequirement.php', + 'Symfony\\Component\\Routing\\Requirement\\Requirement' => $vendorDir . '/symfony/routing/Requirement/Requirement.php', 'Symfony\\Component\\Routing\\Route' => $vendorDir . '/symfony/routing/Route.php', 'Symfony\\Component\\Routing\\RouteCollection' => $vendorDir . '/symfony/routing/RouteCollection.php', - 'Symfony\\Component\\Routing\\RouteCollectionBuilder' => $vendorDir . '/symfony/routing/RouteCollectionBuilder.php', 'Symfony\\Component\\Routing\\RouteCompiler' => $vendorDir . '/symfony/routing/RouteCompiler.php', 'Symfony\\Component\\Routing\\RouteCompilerInterface' => $vendorDir . '/symfony/routing/RouteCompilerInterface.php', 'Symfony\\Component\\Routing\\Router' => $vendorDir . '/symfony/routing/Router.php', @@ -2585,6 +2685,7 @@ return array( 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => $vendorDir . '/symfony/var-dumper/Caster/DsPairStub.php', 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => $vendorDir . '/symfony/var-dumper/Caster/EnumStub.php', 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ExceptionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\FFICaster' => $vendorDir . '/symfony/var-dumper/Caster/FFICaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FiberCaster' => $vendorDir . '/symfony/var-dumper/Caster/FiberCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => $vendorDir . '/symfony/var-dumper/Caster/FrameStub.php', 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => $vendorDir . '/symfony/var-dumper/Caster/GmpCaster.php', @@ -2601,10 +2702,12 @@ return array( 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => $vendorDir . '/symfony/var-dumper/Caster/RedisCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ReflectionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/ResourceCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ScalarStub' => $vendorDir . '/symfony/var-dumper/Caster/ScalarStub.php', 'Symfony\\Component\\VarDumper\\Caster\\SplCaster' => $vendorDir . '/symfony/var-dumper/Caster/SplCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => $vendorDir . '/symfony/var-dumper/Caster/StubCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => $vendorDir . '/symfony/var-dumper/Caster/SymfonyCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => $vendorDir . '/symfony/var-dumper/Caster/TraceStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\UninitializedStub' => $vendorDir . '/symfony/var-dumper/Caster/UninitializedStub.php', 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => $vendorDir . '/symfony/var-dumper/Caster/UuidCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlReaderCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlResourceCaster.php', @@ -2635,13 +2738,22 @@ return array( 'Symfony\\Component\\VarDumper\\VarDumper' => $vendorDir . '/symfony/var-dumper/VarDumper.php', 'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/var-exporter/Exception/ClassNotFoundException.php', 'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/var-exporter/Exception/ExceptionInterface.php', + 'Symfony\\Component\\VarExporter\\Exception\\LogicException' => $vendorDir . '/symfony/var-exporter/Exception/LogicException.php', 'Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => $vendorDir . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php', + 'Symfony\\Component\\VarExporter\\Hydrator' => $vendorDir . '/symfony/var-exporter/Hydrator.php', 'Symfony\\Component\\VarExporter\\Instantiator' => $vendorDir . '/symfony/var-exporter/Instantiator.php', 'Symfony\\Component\\VarExporter\\Internal\\Exporter' => $vendorDir . '/symfony/var-exporter/Internal/Exporter.php', 'Symfony\\Component\\VarExporter\\Internal\\Hydrator' => $vendorDir . '/symfony/var-exporter/Internal/Hydrator.php', + 'Symfony\\Component\\VarExporter\\Internal\\LazyObjectRegistry' => $vendorDir . '/symfony/var-exporter/Internal/LazyObjectRegistry.php', + 'Symfony\\Component\\VarExporter\\Internal\\LazyObjectState' => $vendorDir . '/symfony/var-exporter/Internal/LazyObjectState.php', + 'Symfony\\Component\\VarExporter\\Internal\\LazyObjectTrait' => $vendorDir . '/symfony/var-exporter/Internal/LazyObjectTrait.php', 'Symfony\\Component\\VarExporter\\Internal\\Reference' => $vendorDir . '/symfony/var-exporter/Internal/Reference.php', 'Symfony\\Component\\VarExporter\\Internal\\Registry' => $vendorDir . '/symfony/var-exporter/Internal/Registry.php', 'Symfony\\Component\\VarExporter\\Internal\\Values' => $vendorDir . '/symfony/var-exporter/Internal/Values.php', + 'Symfony\\Component\\VarExporter\\LazyGhostTrait' => $vendorDir . '/symfony/var-exporter/LazyGhostTrait.php', + 'Symfony\\Component\\VarExporter\\LazyObjectInterface' => $vendorDir . '/symfony/var-exporter/LazyObjectInterface.php', + 'Symfony\\Component\\VarExporter\\LazyProxyTrait' => $vendorDir . '/symfony/var-exporter/LazyProxyTrait.php', + 'Symfony\\Component\\VarExporter\\ProxyHelper' => $vendorDir . '/symfony/var-exporter/ProxyHelper.php', 'Symfony\\Component\\VarExporter\\VarExporter' => $vendorDir . '/symfony/var-exporter/VarExporter.php', 'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', @@ -2682,10 +2794,9 @@ return array( 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', 'Symfony\\Polyfill\\Php72\\Php72' => $vendorDir . '/symfony/polyfill-php72/Php72.php', - 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php', 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php', - 'Symfony\\Polyfill\\Php81\\Php81' => $vendorDir . '/symfony/polyfill-php81/Php81.php', + 'Symfony\\Polyfill\\Php83\\Php83' => $vendorDir . '/symfony/polyfill-php83/Php83.php', 'SynchroExceptionNotStarted' => $baseDir . '/application/exceptions/SynchroExceptionNotStarted.php', 'System' => $vendorDir . '/pear/pear-core-minimal/src/System.php', 'TCPDF' => $vendorDir . '/combodo/tcpdf/tcpdf.php', @@ -3006,4 +3117,5 @@ return array( 'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php', 'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php', 'utils' => $baseDir . '/application/utils.inc.php', + '©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php', ); diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php index 50c5aa314..b8586901d 100644 --- a/lib/composer/autoload_files.php +++ b/lib/composer/autoload_files.php @@ -6,12 +6,11 @@ $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( - 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', - '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', - '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index 3b30be01d..b7c94ac93 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -9,9 +9,8 @@ return array( 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), 'Twig\\' => array($vendorDir . '/twig/twig/src'), 'TheNetworg\\OAuth2\\Client\\' => array($vendorDir . '/thenetworg/oauth2-azure/src'), - 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), - 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 065c0f934..e8b0f9a93 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -7,12 +7,11 @@ namespace Composer\Autoload; class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f { public static $files = array ( - 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', - '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', - '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', @@ -36,9 +35,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f ), 'S' => array ( - 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Php83\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23, - 'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Php72\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, @@ -120,18 +118,14 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f array ( 0 => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src', ), - 'Symfony\\Polyfill\\Php81\\' => + 'Symfony\\Polyfill\\Php83\\' => array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + 0 => __DIR__ . '/..' . '/symfony/polyfill-php83', ), 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), - 'Symfony\\Polyfill\\Php73\\' => - array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', - ), 'Symfony\\Polyfill\\Php72\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', @@ -890,6 +884,15 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'DataTable' => __DIR__ . '/../..' . '/application/datatable.class.inc.php', 'DataTableConfig' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/DataTableConfig/DataTableConfig.php', 'Datamatrix' => __DIR__ . '/..' . '/combodo/tcpdf/include/barcodes/datamatrix.php', + 'DateError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateError.php', + 'DateException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateException.php', + 'DateInvalidOperationException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php', + 'DateInvalidTimeZoneException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php', + 'DateMalformedIntervalStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php', + 'DateMalformedPeriodStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php', + 'DateMalformedStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php', + 'DateObjectError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php', + 'DateRangeError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php', 'DateTimeFormat' => __DIR__ . '/../..' . '/core/datetimeformat.class.inc.php', 'DeadLockLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'DefaultLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', @@ -1073,7 +1076,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'ItopCounter' => __DIR__ . '/../..' . '/core/counter.class.inc.php', 'JSButtonItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'JsonPPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/JsonPPage.php', 'JsonPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/JsonPage.php', 'KeyValueStore' => __DIR__ . '/../..' . '/core/counter.class.inc.php', @@ -1487,6 +1489,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'OqlObjectQuery' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'OqlQuery' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'OqlUnionQuery' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', + 'Override' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/Override.php', 'PDF417' => __DIR__ . '/..' . '/combodo/tcpdf/include/barcodes/pdf417.php', 'PDFBulkExport' => __DIR__ . '/../..' . '/core/pdfbulkexport.class.inc.php', 'PDFPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/PDFPage.php', @@ -1814,7 +1817,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'RestResultWithObjects' => __DIR__ . '/../..' . '/core/restservices.class.inc.php', 'RestResultWithRelations' => __DIR__ . '/../..' . '/core/restservices.class.inc.php', 'RestUtils' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RotatingLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'RowStatus' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', 'RowStatus_Disappeared' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', @@ -1946,10 +1948,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'SummaryCardService' => __DIR__ . '/../..' . '/sources/Service/SummaryCard/SummaryCardService.php', 'Symfony\\Bridge\\Twig\\AppVariable' => __DIR__ . '/..' . '/symfony/twig-bridge/AppVariable.php', + 'Symfony\\Bridge\\Twig\\Attribute\\Template' => __DIR__ . '/..' . '/symfony/twig-bridge/Attribute/Template.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/LintCommand.php', 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => __DIR__ . '/..' . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', 'Symfony\\Bridge\\Twig\\ErrorRenderer\\TwigErrorRenderer' => __DIR__ . '/..' . '/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php', + 'Symfony\\Bridge\\Twig\\EventListener\\TemplateAttributeListener' => __DIR__ . '/..' . '/symfony/twig-bridge/EventListener/TemplateAttributeListener.php', 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/AssetExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CodeExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CsrfExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CsrfExtension.php', @@ -1957,9 +1961,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/DumpExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ExpressionExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/FormExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\HtmlSanitizerExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HtmlSanitizerExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpFoundationExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpFoundationExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpKernelExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelRuntime' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpKernelRuntime.php', + 'Symfony\\Bridge\\Twig\\Extension\\ImportMapExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ImportMapExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\ImportMapRuntime' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ImportMapRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\LogoutUrlExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/LogoutUrlExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ProfilerExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\RoutingExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/RoutingExtension.php', @@ -1986,6 +1993,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Bridge\\Twig\\Node\\StopwatchNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/StopwatchNode.php', 'Symfony\\Bridge\\Twig\\Node\\TransDefaultDomainNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/TransDefaultDomainNode.php', 'Symfony\\Bridge\\Twig\\Node\\TransNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/TransNode.php', + 'Symfony\\Bridge\\Twig\\Test\\FormLayoutTestCase' => __DIR__ . '/..' . '/symfony/twig-bridge/Test/FormLayoutTestCase.php', + 'Symfony\\Bridge\\Twig\\Test\\Traits\\RuntimeLoaderProvider' => __DIR__ . '/..' . '/symfony/twig-bridge/Test/Traits/RuntimeLoaderProvider.php', 'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php', @@ -2008,6 +2017,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheClearCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CacheClearCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolClearCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolClearCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolDeleteCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolDeleteCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolInvalidateTagsCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolListCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolListCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolPruneCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolPruneCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheWarmupCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CacheWarmupCommand.php', @@ -2050,22 +2060,27 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AssetsContextPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ContainerBuilderDebugDumpPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\DataCollectorTranslatorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\EnableLoggerDebugModePass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ErrorLoggerCompilerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\LoggingTranslatorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ProfilerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RemoveUnusedSessionMarshallingHandlerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SessionPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerRealRefPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerWeakRefPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\UnusedTagsPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\WorkflowGuardListenerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Configuration' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Configuration.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\FrameworkExtension' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\VirtualRequestStackPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\ConsoleProfilerListener' => __DIR__ . '/..' . '/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php', 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SuggestMissingPackageSubscriber' => __DIR__ . '/..' . '/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php', 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle' => __DIR__ . '/..' . '/symfony/framework-bundle/FrameworkBundle.php', 'Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache' => __DIR__ . '/..' . '/symfony/framework-bundle/HttpCache/HttpCache.php', 'Symfony\\Bundle\\FrameworkBundle\\KernelBrowser' => __DIR__ . '/..' . '/symfony/framework-bundle/KernelBrowser.php', 'Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Kernel/MicroKernelTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\AnnotatedRouteControllerLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\AttributeRouteControllerLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\Attribute\\AsRoutingConditionService' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/DelegatingLoader.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInterface' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/RouteLoaderInterface.php', @@ -2073,8 +2088,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/AbstractVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/DotenvVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/SodiumVault.php', - 'Symfony\\Bundle\\FrameworkBundle\\Session\\DeprecatedSessionFactory' => __DIR__ . '/..' . '/symfony/framework-bundle/Session/DeprecatedSessionFactory.php', - 'Symfony\\Bundle\\FrameworkBundle\\Session\\ServiceSessionFactory' => __DIR__ . '/..' . '/symfony/framework-bundle/Session/ServiceSessionFactory.php', 'Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => __DIR__ . '/..' . '/symfony/framework-bundle/Translation/Translator.php', 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheWarmer' => __DIR__ . '/..' . '/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php', 'Symfony\\Bundle\\TwigBundle\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bundle/Command/LintCommand.php', @@ -2106,7 +2119,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ChainAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/CouchbaseBucketAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/CouchbaseCollectionAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\DoctrineDbalAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineDbalAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', @@ -2130,7 +2142,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPass.php', 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', - 'Symfony\\Component\\Cache\\DoctrineProvider' => __DIR__ . '/..' . '/symfony/cache/DoctrineProvider.php', 'Symfony\\Component\\Cache\\Exception\\CacheException' => __DIR__ . '/..' . '/symfony/cache/Exception/CacheException.php', 'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/cache/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Cache\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/cache/Exception/LogicException.php', @@ -2151,10 +2162,15 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemCommonTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemTrait.php', 'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ProxyTrait.php', + 'Symfony\\Component\\Cache\\Traits\\Redis5Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis5Proxy.php', + 'Symfony\\Component\\Cache\\Traits\\Redis6Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis6Proxy.php', + 'Symfony\\Component\\Cache\\Traits\\RedisCluster5Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster5Proxy.php', + 'Symfony\\Component\\Cache\\Traits\\RedisCluster6Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster6Proxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterNodeProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisTrait.php', + 'Symfony\\Component\\Cache\\Traits\\RelayProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RelayProxy.php', 'Symfony\\Component\\Config\\Builder\\ClassBuilder' => __DIR__ . '/..' . '/symfony/config/Builder/ClassBuilder.php', 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGenerator.php', 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php', @@ -2186,7 +2202,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/TreeBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ValidationBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\ConfigurableInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurableInterface.php', + 'Symfony\\Component\\Config\\Definition\\Configuration' => __DIR__ . '/..' . '/symfony/config/Definition/Configuration.php', 'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurationInterface.php', + 'Symfony\\Component\\Config\\Definition\\Configurator\\DefinitionConfigurator' => __DIR__ . '/..' . '/symfony/config/Definition/Configurator/DefinitionConfigurator.php', 'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', 'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', 'Symfony\\Component\\Config\\Definition\\EnumNode' => __DIR__ . '/..' . '/symfony/config/Definition/EnumNode.php', @@ -2199,6 +2218,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/UnsetKeyException.php', 'Symfony\\Component\\Config\\Definition\\FloatNode' => __DIR__ . '/..' . '/symfony/config/Definition/FloatNode.php', 'Symfony\\Component\\Config\\Definition\\IntegerNode' => __DIR__ . '/..' . '/symfony/config/Definition/IntegerNode.php', + 'Symfony\\Component\\Config\\Definition\\Loader\\DefinitionFileLoader' => __DIR__ . '/..' . '/symfony/config/Definition/Loader/DefinitionFileLoader.php', 'Symfony\\Component\\Config\\Definition\\NodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/NodeInterface.php', 'Symfony\\Component\\Config\\Definition\\NumericNode' => __DIR__ . '/..' . '/symfony/config/Definition/NumericNode.php', 'Symfony\\Component\\Config\\Definition\\Processor' => __DIR__ . '/..' . '/symfony/config/Definition/Processor.php', @@ -2212,6 +2232,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php', 'Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php', 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php', + 'Symfony\\Component\\Config\\Loader\\DirectoryAwareLoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/DirectoryAwareLoaderInterface.php', 'Symfony\\Component\\Config\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/FileLoader.php', 'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/GlobFileLoader.php', 'Symfony\\Component\\Config\\Loader\\Loader' => __DIR__ . '/..' . '/symfony/config/Loader/Loader.php', @@ -2250,19 +2271,25 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php', 'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php', 'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => __DIR__ . '/..' . '/symfony/console/Command/SignalableCommandInterface.php', + 'Symfony\\Component\\Console\\Command\\TraceableCommand' => __DIR__ . '/..' . '/symfony/console/Command/TraceableCommand.php', 'Symfony\\Component\\Console\\Completion\\CompletionInput' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionInput.php', 'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionSuggestions.php', 'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/BashCompletionOutput.php', 'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => __DIR__ . '/..' . '/symfony/console/Completion/Output/CompletionOutputInterface.php', + 'Symfony\\Component\\Console\\Completion\\Output\\FishCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/FishCompletionOutput.php', + 'Symfony\\Component\\Console\\Completion\\Output\\ZshCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/ZshCompletionOutput.php', 'Symfony\\Component\\Console\\Completion\\Suggestion' => __DIR__ . '/..' . '/symfony/console/Completion/Suggestion.php', 'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php', 'Symfony\\Component\\Console\\Cursor' => __DIR__ . '/..' . '/symfony/console/Cursor.php', + 'Symfony\\Component\\Console\\DataCollector\\CommandDataCollector' => __DIR__ . '/..' . '/symfony/console/DataCollector/CommandDataCollector.php', + 'Symfony\\Component\\Console\\Debug\\CliRequest' => __DIR__ . '/..' . '/symfony/console/Debug/CliRequest.php', 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php', 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php', 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php', 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\ReStructuredTextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/ReStructuredTextDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php', 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php', @@ -2278,6 +2305,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php', 'Symfony\\Component\\Console\\Exception\\MissingInputException' => __DIR__ . '/..' . '/symfony/console/Exception/MissingInputException.php', 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RunCommandFailedException' => __DIR__ . '/..' . '/symfony/console/Exception/RunCommandFailedException.php', 'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php', 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatter.php', 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatterStyle.php', @@ -2295,6 +2323,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', 'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php', 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\OutputWrapper' => __DIR__ . '/..' . '/symfony/console/Helper/OutputWrapper.php', 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php', 'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php', 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php', @@ -2317,6 +2346,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php', 'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php', 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandContext.php', + 'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessage.php', + 'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessageHandler.php', + 'Symfony\\Component\\Console\\Output\\AnsiColorMode' => __DIR__ . '/..' . '/symfony/console/Output/AnsiColorMode.php', 'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php', @@ -2329,6 +2362,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php', 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php', 'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SignalRegistry\\SignalMap' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalMap.php', 'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalRegistry.php', 'Symfony\\Component\\Console\\SingleCommandApplication' => __DIR__ . '/..' . '/symfony/console/SingleCommandApplication.php', 'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php', @@ -2393,15 +2427,26 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ArgumentInterface.php', 'Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/BoundArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/IteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\LazyClosure' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/LazyClosure.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', 'Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/RewindableGenerator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AsAlias' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AsAlias.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AsDecorator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AsDecorator.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\AsTaggedItem' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AsTaggedItem.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Autoconfigure.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\AutoconfigureTag' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutoconfigureTag.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Autowire' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Autowire.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutowireCallable.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireDecorated' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutowireDecorated.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireIterator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutowireIterator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutowireLocator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutowireServiceClosure' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutowireServiceClosure.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Exclude' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Exclude.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\MapDecorated' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/MapDecorated.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/TaggedIterator.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/TaggedLocator.php', 'Symfony\\Component\\DependencyInjection\\Attribute\\Target' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Target.php', @@ -2412,6 +2457,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AttributeAutoconfigurationPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireAsDecoratorPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowirePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredMethodsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredPropertiesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php', @@ -2435,6 +2481,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveBuildParametersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', @@ -2450,7 +2497,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNamedArgumentsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNoPreloadPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolvePrivatesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveServiceSubscribersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveTaggedIteratorArgumentPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php', @@ -2493,13 +2539,18 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ServiceNotFoundException.php', 'Symfony\\Component\\DependencyInjection\\ExpressionLanguage' => __DIR__ . '/..' . '/symfony/dependency-injection/ExpressionLanguage.php', 'Symfony\\Component\\DependencyInjection\\ExpressionLanguageProvider' => __DIR__ . '/..' . '/symfony/dependency-injection/ExpressionLanguageProvider.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\AbstractExtension' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/AbstractExtension.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ConfigurableExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php', 'Symfony\\Component\\DependencyInjection\\Extension\\ConfigurationExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php', 'Symfony\\Component\\DependencyInjection\\Extension\\Extension' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/Extension.php', 'Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ExtensionTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ExtensionTrait.php', 'Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/PrependExtensionInterface.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\InstantiatorInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\LazyServiceInstantiator' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\RealServiceInstantiator' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\LazyServiceDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\NullDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php', 'Symfony\\Component\\DependencyInjection\\LazyProxy\\ProxyHelper' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/ProxyHelper.php', 'Symfony\\Component\\DependencyInjection\\Loader\\ClosureLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/ClosureLoader.php', @@ -2510,6 +2561,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\DefaultsConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\EnvConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\FromCallableConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InlineServiceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InstanceofConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ParametersConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php', @@ -2525,10 +2577,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\CallTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ClassTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ConfiguratorTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ConstructorTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DecorateTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DeprecateTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FactoryTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FileTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FromCallableTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\LazyTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ParentTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\PropertyTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php', @@ -2573,6 +2627,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\ErrorHandler\\ErrorHandler' => __DIR__ . '/..' . '/symfony/error-handler/ErrorHandler.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\CliErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\ErrorRendererInterface' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\FileLinkFormatter' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\HtmlErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php', 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\SerializerErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php', 'Symfony\\Component\\ErrorHandler\\Error\\ClassNotFoundError' => __DIR__ . '/..' . '/symfony/error-handler/Error/ClassNotFoundError.php', @@ -2594,7 +2649,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php', 'Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php', 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', - 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', @@ -2629,6 +2683,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpFoundation\\AcceptHeader' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeader.php', 'Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeaderItem.php', 'Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => __DIR__ . '/..' . '/symfony/http-foundation/BinaryFileResponse.php', + 'Symfony\\Component\\HttpFoundation\\ChainRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/ChainRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\Cookie' => __DIR__ . '/..' . '/symfony/http-foundation/Cookie.php', 'Symfony\\Component\\HttpFoundation\\Exception\\BadRequestException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/BadRequestException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', @@ -2636,6 +2691,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/RequestExceptionInterface.php', 'Symfony\\Component\\HttpFoundation\\Exception\\SessionNotFoundException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/SessionNotFoundException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\SuspiciousOperationException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/SuspiciousOperationException.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\UnexpectedValueException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/UnexpectedValueException.php', 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/ExpressionRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\FileBag' => __DIR__ . '/..' . '/symfony/http-foundation/FileBag.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', @@ -2660,18 +2716,28 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpFoundation\\JsonResponse' => __DIR__ . '/..' . '/symfony/http-foundation/JsonResponse.php', 'Symfony\\Component\\HttpFoundation\\ParameterBag' => __DIR__ . '/..' . '/symfony/http-foundation/ParameterBag.php', 'Symfony\\Component\\HttpFoundation\\RateLimiter\\AbstractRequestRateLimiter' => __DIR__ . '/..' . '/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php', + 'Symfony\\Component\\HttpFoundation\\RateLimiter\\PeekableRequestRateLimiterInterface' => __DIR__ . '/..' . '/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php', 'Symfony\\Component\\HttpFoundation\\RateLimiter\\RequestRateLimiterInterface' => __DIR__ . '/..' . '/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php', 'Symfony\\Component\\HttpFoundation\\RedirectResponse' => __DIR__ . '/..' . '/symfony/http-foundation/RedirectResponse.php', 'Symfony\\Component\\HttpFoundation\\Request' => __DIR__ . '/..' . '/symfony/http-foundation/Request.php', 'Symfony\\Component\\HttpFoundation\\RequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcherInterface.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\AttributesRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\ExpressionRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\HostRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\IpsRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\IsJsonRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\MethodRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\PathRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\PortRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher\\SchemeRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\RequestStack' => __DIR__ . '/..' . '/symfony/http-foundation/RequestStack.php', 'Symfony\\Component\\HttpFoundation\\Response' => __DIR__ . '/..' . '/symfony/http-foundation/Response.php', 'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag' => __DIR__ . '/..' . '/symfony/http-foundation/ResponseHeaderBag.php', 'Symfony\\Component\\HttpFoundation\\ServerBag' => __DIR__ . '/..' . '/symfony/http-foundation/ServerBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/AttributeBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\FlashBagAwareSessionInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/FlashBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/FlashBagInterface.php', @@ -2704,14 +2770,25 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorageFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\ServiceSessionFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', + 'Symfony\\Component\\HttpFoundation\\StreamedJsonResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedJsonResponse.php', 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedResponse.php', + 'Symfony\\Component\\HttpFoundation\\UriSigner' => __DIR__ . '/..' . '/symfony/http-foundation/UriSigner.php', 'Symfony\\Component\\HttpFoundation\\UrlHelper' => __DIR__ . '/..' . '/symfony/http-foundation/UrlHelper.php', - 'Symfony\\Component\\HttpKernel\\Attribute\\ArgumentInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/ArgumentInterface.php', 'Symfony\\Component\\HttpKernel\\Attribute\\AsController' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/AsController.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\AsTargetedValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/AsTargetedValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\Cache' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/Cache.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapDateTime' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/MapDateTime.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapQueryParameter' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/MapQueryParameter.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapQueryString' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/MapQueryString.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\MapRequestPayload' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/MapRequestPayload.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\ValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/ValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\WithHttpStatus' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/WithHttpStatus.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\WithLogLevel' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/WithLogLevel.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/AbstractBundle.php', 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/Bundle.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\BundleExtension' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/BundleExtension.php', 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/BundleInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\ChainCacheClearer' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/ChainCacheClearer.php', @@ -2726,13 +2803,18 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactoryInterface' => __DIR__ . '/..' . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\BackedEnumValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DateTimeValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\QueryParameterValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestPayloadValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\UidValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', @@ -2742,6 +2824,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ErrorController.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ValueResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\ConfigDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/ConfigDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/DataCollector.php', @@ -2755,8 +2838,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ErrorHandlerConfigurator' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php', 'Symfony\\Component\\HttpKernel\\Debug\\FileLinkFormatter' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/FileLinkFormatter.php', 'Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\HttpKernel\\Debug\\VirtualRequestStack' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/VirtualRequestStack.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddAnnotatedClassesToCachePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ControllerArgumentValueResolverPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php', @@ -2771,8 +2856,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AbstractSessionListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\CacheAttributeListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/CacheAttributeListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DumpListener.php', @@ -2786,7 +2871,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SurrogateListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', 'Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerEvent.php', @@ -2806,11 +2890,13 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', 'Symfony\\Component\\HttpKernel\\Exception\\InvalidMetadataException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/InvalidMetadataException.php', 'Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\LockedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/LockedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/NotFoundHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/PreconditionFailedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ResolverNotFoundException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ResolverNotFoundException.php', 'Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', @@ -2844,6 +2930,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\Kernel' => __DIR__ . '/..' . '/symfony/http-kernel/Kernel.php', 'Symfony\\Component\\HttpKernel\\KernelEvents' => __DIR__ . '/..' . '/symfony/http-kernel/KernelEvents.php', 'Symfony\\Component\\HttpKernel\\KernelInterface' => __DIR__ . '/..' . '/symfony/http-kernel/KernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\DebugLoggerConfigurator' => __DIR__ . '/..' . '/symfony/http-kernel/Log/DebugLoggerConfigurator.php', 'Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Log/DebugLoggerInterface.php', 'Symfony\\Component\\HttpKernel\\Log\\Logger' => __DIR__ . '/..' . '/symfony/http-kernel/Log/Logger.php', 'Symfony\\Component\\HttpKernel\\Profiler\\FileProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/FileProfilerStorage.php', @@ -2855,7 +2942,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\HttpKernel\\UriSigner' => __DIR__ . '/..' . '/symfony/http-kernel/UriSigner.php', 'Symfony\\Component\\Routing\\Alias' => __DIR__ . '/..' . '/symfony/routing/Alias.php', 'Symfony\\Component\\Routing\\Annotation\\Route' => __DIR__ . '/..' . '/symfony/routing/Annotation/Route.php', + 'Symfony\\Component\\Routing\\Attribute\\Route' => __DIR__ . '/..' . '/symfony/routing/Attribute/Route.php', 'Symfony\\Component\\Routing\\CompiledRoute' => __DIR__ . '/..' . '/symfony/routing/CompiledRoute.php', + 'Symfony\\Component\\Routing\\DependencyInjection\\AddExpressionLanguageProvidersPass' => __DIR__ . '/..' . '/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php', 'Symfony\\Component\\Routing\\DependencyInjection\\RoutingResolverPass' => __DIR__ . '/..' . '/symfony/routing/DependencyInjection/RoutingResolverPass.php', 'Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/routing/Exception/ExceptionInterface.php', 'Symfony\\Component\\Routing\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/routing/Exception/InvalidArgumentException.php', @@ -2877,6 +2966,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationClassLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AttributeClassLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AttributeClassLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AttributeDirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AttributeDirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AttributeFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AttributeFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\ClosureLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ClosureLoader.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\AliasConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/AliasConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\CollectionConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/CollectionConfigurator.php', @@ -2893,6 +2985,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/GlobFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\ObjectLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ObjectLoader.php', 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\Psr4DirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/Psr4DirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/XmlFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/YamlFileLoader.php', 'Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/CompiledUrlMatcher.php', @@ -2910,9 +3003,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/UrlMatcherInterface.php', 'Symfony\\Component\\Routing\\RequestContext' => __DIR__ . '/..' . '/symfony/routing/RequestContext.php', 'Symfony\\Component\\Routing\\RequestContextAwareInterface' => __DIR__ . '/..' . '/symfony/routing/RequestContextAwareInterface.php', + 'Symfony\\Component\\Routing\\Requirement\\EnumRequirement' => __DIR__ . '/..' . '/symfony/routing/Requirement/EnumRequirement.php', + 'Symfony\\Component\\Routing\\Requirement\\Requirement' => __DIR__ . '/..' . '/symfony/routing/Requirement/Requirement.php', 'Symfony\\Component\\Routing\\Route' => __DIR__ . '/..' . '/symfony/routing/Route.php', 'Symfony\\Component\\Routing\\RouteCollection' => __DIR__ . '/..' . '/symfony/routing/RouteCollection.php', - 'Symfony\\Component\\Routing\\RouteCollectionBuilder' => __DIR__ . '/..' . '/symfony/routing/RouteCollectionBuilder.php', 'Symfony\\Component\\Routing\\RouteCompiler' => __DIR__ . '/..' . '/symfony/routing/RouteCompiler.php', 'Symfony\\Component\\Routing\\RouteCompilerInterface' => __DIR__ . '/..' . '/symfony/routing/RouteCompilerInterface.php', 'Symfony\\Component\\Routing\\Router' => __DIR__ . '/..' . '/symfony/routing/Router.php', @@ -2949,6 +3043,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsPairStub.php', 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/EnumStub.php', 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ExceptionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\FFICaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FFICaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FiberCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FiberCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FrameStub.php', 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/GmpCaster.php', @@ -2965,10 +3060,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/RedisCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ReflectionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ResourceCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ScalarStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ScalarStub.php', 'Symfony\\Component\\VarDumper\\Caster\\SplCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SplCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/StubCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SymfonyCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/TraceStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\UninitializedStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/UninitializedStub.php', 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/UuidCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlReaderCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlResourceCaster.php', @@ -2999,13 +3096,22 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\VarDumper\\VarDumper' => __DIR__ . '/..' . '/symfony/var-dumper/VarDumper.php', 'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ClassNotFoundException.php', 'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ExceptionInterface.php', + 'Symfony\\Component\\VarExporter\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/LogicException.php', 'Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php', + 'Symfony\\Component\\VarExporter\\Hydrator' => __DIR__ . '/..' . '/symfony/var-exporter/Hydrator.php', 'Symfony\\Component\\VarExporter\\Instantiator' => __DIR__ . '/..' . '/symfony/var-exporter/Instantiator.php', 'Symfony\\Component\\VarExporter\\Internal\\Exporter' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Exporter.php', 'Symfony\\Component\\VarExporter\\Internal\\Hydrator' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Hydrator.php', + 'Symfony\\Component\\VarExporter\\Internal\\LazyObjectRegistry' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/LazyObjectRegistry.php', + 'Symfony\\Component\\VarExporter\\Internal\\LazyObjectState' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/LazyObjectState.php', + 'Symfony\\Component\\VarExporter\\Internal\\LazyObjectTrait' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/LazyObjectTrait.php', 'Symfony\\Component\\VarExporter\\Internal\\Reference' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Reference.php', 'Symfony\\Component\\VarExporter\\Internal\\Registry' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Registry.php', 'Symfony\\Component\\VarExporter\\Internal\\Values' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Values.php', + 'Symfony\\Component\\VarExporter\\LazyGhostTrait' => __DIR__ . '/..' . '/symfony/var-exporter/LazyGhostTrait.php', + 'Symfony\\Component\\VarExporter\\LazyObjectInterface' => __DIR__ . '/..' . '/symfony/var-exporter/LazyObjectInterface.php', + 'Symfony\\Component\\VarExporter\\LazyProxyTrait' => __DIR__ . '/..' . '/symfony/var-exporter/LazyProxyTrait.php', + 'Symfony\\Component\\VarExporter\\ProxyHelper' => __DIR__ . '/..' . '/symfony/var-exporter/ProxyHelper.php', 'Symfony\\Component\\VarExporter\\VarExporter' => __DIR__ . '/..' . '/symfony/var-exporter/VarExporter.php', 'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', @@ -3046,10 +3152,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Normalizer.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', 'Symfony\\Polyfill\\Php72\\Php72' => __DIR__ . '/..' . '/symfony/polyfill-php72/Php72.php', - 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php', 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php', - 'Symfony\\Polyfill\\Php81\\Php81' => __DIR__ . '/..' . '/symfony/polyfill-php81/Php81.php', + 'Symfony\\Polyfill\\Php83\\Php83' => __DIR__ . '/..' . '/symfony/polyfill-php83/Php83.php', 'SynchroExceptionNotStarted' => __DIR__ . '/../..' . '/application/exceptions/SynchroExceptionNotStarted.php', 'System' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/System.php', 'TCPDF' => __DIR__ . '/..' . '/combodo/tcpdf/tcpdf.php', @@ -3370,6 +3475,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php', 'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php', 'utils' => __DIR__ . '/../..' . '/application/utils.inc.php', + '©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/lib/composer/installed.json b/lib/composer/installed.json index 356bcc52b..dde14fa73 100644 --- a/lib/composer/installed.json +++ b/lib/composer/installed.json @@ -1580,23 +1580,23 @@ }, { "name": "psr/cache", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "version": "3.0.0", + "version_normalized": "3.0.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, - "time": "2016-08-06T20:24:11+00:00", + "time": "2021-02-03T23:26:27+00:00", "type": "library", "extra": { "branch-alias": { @@ -1616,7 +1616,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -1625,6 +1625,9 @@ "psr", "psr-6" ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, "install-path": "../psr/cache" }, { @@ -2141,61 +2144,60 @@ }, { "name": "symfony/cache", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "5a0fff46df349f0db3fe242263451fddf5277362" + "reference": "ac2d25f97b17eec6e19760b6b9962a4f7c44356a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/5a0fff46df349f0db3fe242263451fddf5277362", - "reference": "5a0fff46df349f0db3fe242263451fddf5277362", + "url": "https://api.github.com/repos/symfony/cache/zipball/ac2d25f97b17eec6e19760b6b9962a4f7c44356a", + "reference": "ac2d25f97b17eec6e19760b6b9962a4f7c44356a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0", + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.3.6|^7.0" }, "conflict": { "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" }, "provide": { - "psr/cache-implementation": "1.0|2.0", - "psr/simple-cache-implementation": "1.0|2.0", - "symfony/cache-implementation": "1.0|2.0" + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6|^2.0", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, - "time": "2022-07-28T15:25:17+00:00", + "time": "2023-11-24T19:28:07+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "/Tests/" ] @@ -2214,14 +2216,14 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", "homepage": "https://symfony.com", "keywords": [ "caching", "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.11" + "source": "https://github.com/symfony/cache/tree/v6.4.0" }, "funding": [ { @@ -2241,31 +2243,28 @@ }, { "name": "symfony/cache-contracts", - "version": "v2.5.2", - "version_normalized": "2.5.2.0", + "version": "v3.4.0", + "version_normalized": "3.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" + "reference": "1d74b127da04ffa87aa940abe15446fa89653778" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/1d74b127da04ffa87aa940abe15446fa89653778", + "reference": "1d74b127da04ffa87aa940abe15446fa89653778", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0|^3.0" + "php": ">=8.1", + "psr/cache": "^3.0" }, - "suggest": { - "symfony/cache-implementation": "" - }, - "time": "2022-01-02T09:53:40+00:00", + "time": "2023-09-25T12:52:38+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -2303,7 +2302,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/cache-contracts/tree/v3.4.0" }, "funding": [ { @@ -2323,41 +2322,37 @@ }, { "name": "symfony/config", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "ec79e03125c1d2477e43dde8528535d90cc78379" + "reference": "5d33e0fb707d603330e0edfd4691803a1253572e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/ec79e03125c1d2477e43dde8528535d90cc78379", - "reference": "ec79e03125c1d2477e43dde8528535d90cc78379", + "url": "https://api.github.com/repos/symfony/config/zipball/5d33e0fb707d603330e0edfd4691803a1253572e", + "reference": "5d33e0fb707d603330e0edfd4691803a1253572e", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<4.4" + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "time": "2022-07-20T13:00:38+00:00", + "time": "2023-11-09T08:28:32+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2385,7 +2380,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.11" + "source": "https://github.com/symfony/config/tree/v6.4.0" }, "funding": [ { @@ -2405,55 +2400,50 @@ }, { "name": "symfony/console", - "version": "v5.4.19", - "version_normalized": "5.4.19.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740" + "reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dccb8d251a9017d5994c988b034d3e18aaabf740", - "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740", + "url": "https://api.github.com/repos/symfony/console/zipball/cd9864b47c367450e14ab32f78fdbf98c44c26b6", + "reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "time": "2023-01-01T08:32:19+00:00", + "time": "2023-11-20T16:41:16+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2482,12 +2472,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.19" + "source": "https://github.com/symfony/console/tree/v6.4.0" }, "funding": [ { @@ -2507,24 +2497,23 @@ }, { "name": "symfony/css-selector", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "c1681789f059ab756001052164726ae88512ae3d" + "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", - "reference": "c1681789f059ab756001052164726ae88512ae3d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4", + "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, - "time": "2022-06-27T16:58:25+00:00", + "time": "2023-10-31T08:40:20+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2556,7 +2545,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.11" + "source": "https://github.com/symfony/css-selector/tree/v6.4.0" }, "funding": [ { @@ -2576,51 +2565,43 @@ }, { "name": "symfony/dependency-injection", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62" + "reference": "5dc8ad5f2bbba7046f5947682bf7d868ce80d4e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a8b9251016e9476db73e25fa836904bc0bf74c62", - "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5dc8ad5f2bbba7046f5947682bf7d868ce80d4e8", + "reference": "5dc8ad5f2bbba7046f5947682bf7d868ce80d4e8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/service-contracts": "^1.1.6|^2" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<5.3", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4.26" + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" }, "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0|2.0" + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4.26|^5.0|^6.0" + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" - }, - "time": "2022-07-20T13:00:38+00:00", + "time": "2023-10-31T08:40:20+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2648,7 +2629,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.11" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.0" }, "funding": [ { @@ -2668,27 +2649,27 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.2", - "version_normalized": "2.5.2.0", + "version": "v3.4.0", + "version_normalized": "3.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, - "time": "2022-01-02T09:53:40+00:00", + "time": "2023-05-23T14:45:45+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -2718,7 +2699,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2738,28 +2719,31 @@ }, { "name": "symfony/dotenv", - "version": "v5.4.19", - "version_normalized": "5.4.19.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "38190ba62566afa26ca723a795d0a004e061bd2a" + "reference": "d0d584a91422ddaa2c94317200d4c4e5b935555f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/38190ba62566afa26ca723a795d0a004e061bd2a", - "reference": "38190ba62566afa26ca723a795d0a004e061bd2a", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/d0d584a91422ddaa2c94317200d4c4e5b935555f", + "reference": "d0d584a91422ddaa2c94317200d4c4e5b935555f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" }, "require-dev": { - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" }, - "time": "2023-01-01T08:32:19+00:00", + "time": "2023-10-26T18:19:48+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2792,7 +2776,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v5.4.19" + "source": "https://github.com/symfony/dotenv/tree/v6.4.0" }, "funding": [ { @@ -2812,30 +2796,34 @@ }, { "name": "symfony/error-handler", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8" + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/f75d17cb4769eb38cd5fccbda95cd80a054d35c8", - "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c873490a1c97b3a0a4838afc36ff36c112d02788", + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0" }, - "time": "2022-07-29T07:37:50+00:00", + "time": "2023-10-18T09:43:34+00:00", "bin": [ "Resources/bin/patch-type-declarations" ], @@ -2866,7 +2854,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.11" + "source": "https://github.com/symfony/error-handler/tree/v6.4.0" }, "funding": [ { @@ -2886,47 +2874,42 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.4.9", - "version_normalized": "5.4.9.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d76d2632cfc2206eecb5ad2b26cd5934082941b6", + "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "time": "2022-05-05T16:45:39+00:00", + "time": "2023-07-27T06:52:43+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2954,7 +2937,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.0" }, "funding": [ { @@ -2974,31 +2957,28 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.2", - "version_normalized": "2.5.2.0", + "version": "v3.4.0", + "version_normalized": "3.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, - "time": "2022-01-02T09:53:40+00:00", + "time": "2023-05-23T14:45:45+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -3036,7 +3016,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" }, "funding": [ { @@ -3056,26 +3036,25 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd" + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6699fb0228d1bc35b12aed6dd5e7455457609ddd", - "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/952a8cb588c3bc6ce76f6023000fb932f16a6e59", + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-mbstring": "~1.8" }, - "time": "2022-07-20T13:00:38+00:00", + "time": "2023-07-26T17:27:13+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3103,7 +3082,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.11" + "source": "https://github.com/symfony/filesystem/tree/v6.4.0" }, "funding": [ { @@ -3123,25 +3102,26 @@ }, { "name": "symfony/finder", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, - "time": "2022-07-29T07:37:50+00:00", + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "time": "2023-10-31T17:30:12+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3169,7 +3149,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.11" + "source": "https://github.com/symfony/finder/tree/v6.4.0" }, "funding": [ { @@ -3189,113 +3169,110 @@ }, { "name": "symfony/framework-bundle", - "version": "v5.4.19", - "version_normalized": "5.4.19.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "a208ee578000f9dedcb50a9784ec7ff8706a7bf1" + "reference": "981e016715b4a7f22f58c1d9fdf444311965d25e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a208ee578000f9dedcb50a9784ec7ff8706a7bf1", - "reference": "a208ee578000f9dedcb50a9784ec7ff8706a7bf1", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/981e016715b4a7f22f58c1d9fdf444311965d25e", + "reference": "981e016715b4a7f22f58c1d9fdf444311965d25e", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=7.2.5", - "symfony/cache": "^5.2|^6.0", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^5.4.5|^6.0.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", - "symfony/event-dispatcher": "^5.1|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^5.4|^6.0", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/routing": "^5.3|^6.0" + "symfony/routing": "^6.4|^7.0" }, "conflict": { "doctrine/annotations": "<1.13.1", - "doctrine/cache": "<1.11", "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "phpunit/phpunit": "<5.4.3", - "symfony/asset": "<5.3", - "symfony/console": "<5.2.5", - "symfony/dom-crawler": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/form": "<5.2", - "symfony/http-client": "<4.4", - "symfony/lock": "<4.4", - "symfony/mailer": "<5.2", - "symfony/messenger": "<5.4", - "symfony/mime": "<4.4", - "symfony/property-access": "<5.3", - "symfony/property-info": "<4.4", - "symfony/security-csrf": "<5.3", - "symfony/serializer": "<5.2", - "symfony/service-contracts": ">=3.0", - "symfony/stopwatch": "<4.4", - "symfony/translation": "<5.3", - "symfony/twig-bridge": "<4.4", - "symfony/twig-bundle": "<4.4", - "symfony/validator": "<5.2", - "symfony/web-profiler-bundle": "<4.4", - "symfony/workflow": "<5.2" + "symfony/asset": "<5.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<6.3", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.4", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/scheduler": "<6.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.4", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/workflow": "<6.4" }, "require-dev": { "doctrine/annotations": "^1.13.1|^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.3|^6.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/console": "^5.4.9|^6.0.9", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", - "symfony/dotenv": "^5.1|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/form": "^5.2|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/mailer": "^5.2|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/notifier": "^5.4|^6.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/console": "^5.4.9|^6.0.9|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-client": "^6.3|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/mailer": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.3|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^5.4|^6.0|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/property-info": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/string": "^5.0|^6.0", - "symfony/translation": "^5.3|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/validator": "^5.2|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0", + "symfony/scheduler": "^6.4|^7.0", + "symfony/security-bundle": "^5.4|^6.0|^7.0", + "symfony/semaphore": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/string": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "twig/twig": "^2.10|^3.0" }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/property-info": "For using the property_info service", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", - "symfony/yaml": "For using the debug:config and lint:yaml commands" - }, - "time": "2023-01-10T17:40:25+00:00", + "time": "2023-11-25T19:10:27+00:00", "type": "symfony-bundle", "installation-source": "dist", "autoload": { @@ -3323,7 +3300,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v5.4.19" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.0" }, "funding": [ { @@ -3343,38 +3320,39 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.20", - "version_normalized": "5.4.20.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a" + "reference": "44a6d39a9cc11e154547d882d5aac1e014440771" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0435363362a47c14e9cf50663cb8ffbf491875a", - "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/44a6d39a9cc11e154547d882d5aac1e014440771", + "reference": "44a6d39a9cc11e154547d882d5aac1e014440771", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.3" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0" + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.3|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" - }, - "time": "2023-01-29T11:11:52+00:00", + "time": "2023-11-20T16:41:16+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3402,7 +3380,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.20" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.0" }, "funding": [ { @@ -3422,74 +3400,75 @@ }, { "name": "symfony/http-kernel", - "version": "v5.4.20", - "version_normalized": "5.4.20.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e" + "reference": "16a29c453966f29466ad34444ce97970a336f3c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aaeec341582d3c160cc9ecfa8b2419ba6c69954e", - "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/16a29c453966f29466ad34444ce97970a336f3c8", + "reference": "16a29c453966f29466ad34444ce97970a336f3c8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^5.0|^6.0", - "symfony/http-foundation": "^5.3.7|^6.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.3", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.3", "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/config": "^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0.5|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.3|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-exporter": "^6.2|^7.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, - "time": "2023-02-01T08:18:48+00:00", + "time": "2023-11-29T10:40:15+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3517,7 +3496,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.20" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.0" }, "funding": [ { @@ -3537,17 +3516,17 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -3559,11 +3538,11 @@ "suggest": { "ext-ctype": "For best performance" }, - "time": "2022-05-24T11:49:31+00:00", + "time": "2023-01-26T09:26:14+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3602,7 +3581,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -3622,17 +3601,17 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -3641,11 +3620,11 @@ "suggest": { "ext-intl": "For best performance" }, - "time": "2022-05-24T11:49:31+00:00", + "time": "2023-01-26T09:26:14+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3686,7 +3665,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -3706,17 +3685,17 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", - "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", "shasum": "" }, "require": { @@ -3727,11 +3706,11 @@ "suggest": { "ext-intl": "For best performance" }, - "time": "2022-05-24T11:49:31+00:00", + "time": "2023-01-26T09:30:37+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3776,7 +3755,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" }, "funding": [ { @@ -3796,17 +3775,17 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -3815,11 +3794,11 @@ "suggest": { "ext-intl": "For best performance" }, - "time": "2022-05-24T11:49:31+00:00", + "time": "2023-01-26T09:26:14+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3863,7 +3842,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -3883,17 +3862,17 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -3905,11 +3884,11 @@ "suggest": { "ext-mbstring": "For best performance" }, - "time": "2022-05-24T11:49:31+00:00", + "time": "2023-07-28T09:04:16+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3949,7 +3928,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -3969,27 +3948,27 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { "php": ">=7.1" }, - "time": "2022-05-24T11:49:31+00:00", + "time": "2023-01-26T09:26:14+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4028,7 +4007,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -4046,111 +4025,29 @@ ], "install-path": "../symfony/polyfill-php72" }, - { - "name": "symfony/polyfill-php73", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "time": "2022-05-24T11:49:31+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "installation-source": "dist", - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "install-path": "../symfony/polyfill-php73" - }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { "php": ">=7.1" }, - "time": "2022-05-10T07:21:04+00:00", + "time": "2023-01-26T09:26:14+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4196,7 +4093,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -4215,28 +4112,29 @@ "install-path": "../symfony/polyfill-php80" }, { - "name": "symfony/polyfill-php81", - "version": "v1.26.0", - "version_normalized": "1.26.0.0", + "name": "symfony/polyfill-php83", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" }, - "time": "2022-05-24T11:49:31+00:00", + "time": "2023-08-16T06:22:46+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4249,7 +4147,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, "classmap": [ "Resources/stubs" @@ -4269,7 +4167,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -4278,7 +4176,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" }, "funding": [ { @@ -4294,50 +4192,43 @@ "type": "tidelift" } ], - "install-path": "../symfony/polyfill-php81" + "install-path": "../symfony/polyfill-php83" }, { "name": "symfony/routing", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226" + "reference": "ae014d60d7c8e80be5c3b644a286e91249a3e8f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3e01ccd9b2a3a4167ba2b3c53612762300300226", - "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226", + "url": "https://api.github.com/repos/symfony/routing/zipball/ae014d60d7c8e80be5c3b644a286e91249a3e8f4", + "reference": "ae014d60d7c8e80be5c3b644a286e91249a3e8f4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "doctrine/annotations": "<1.12", - "symfony/config": "<5.3", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.12", + "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" - }, - "time": "2022-07-20T13:00:38+00:00", + "time": "2023-11-29T08:04:54+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -4371,7 +4262,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.11" + "source": "https://github.com/symfony/routing/tree/v6.4.0" }, "funding": [ { @@ -4477,24 +4368,24 @@ }, { "name": "symfony/stopwatch", - "version": "v5.4.19", - "version_normalized": "5.4.19.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "bd2b066090fd6a67039371098fa25a84cb2679ec" + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/bd2b066090fd6a67039371098fa25a84cb2679ec", - "reference": "bd2b066090fd6a67039371098fa25a84cb2679ec", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" }, - "time": "2023-01-01T08:32:19+00:00", + "time": "2023-02-16T10:14:28+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -4522,7 +4413,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.19" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.0" }, "funding": [ { @@ -4542,37 +4433,37 @@ }, { "name": "symfony/string", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" + "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", - "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "url": "https://api.github.com/repos/symfony/string/zipball/b45fcf399ea9c3af543a92edf7172ba21174d809", + "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, - "time": "2022-07-24T16:15:25+00:00", + "time": "2023-11-28T20:41:49+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -4611,7 +4502,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.11" + "source": "https://github.com/symfony/string/tree/v6.4.0" }, "funding": [ { @@ -4631,30 +4522,27 @@ }, { "name": "symfony/translation-contracts", - "version": "v2.5.2", - "version_normalized": "2.5.2.0", + "version": "v3.4.0", + "version_normalized": "3.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dee0c6e5b4c07ce851b462530088e64b255ac9c5", + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.1" }, - "suggest": { - "symfony/translation-implementation": "" - }, - "time": "2022-06-27T16:58:25+00:00", + "time": "2023-07-25T15:08:44+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -4665,7 +4553,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Translation\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4692,7 +4583,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.0" }, "funding": [ { @@ -4712,83 +4603,71 @@ }, { "name": "symfony/twig-bridge", - "version": "v5.4.31", - "version_normalized": "5.4.31.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942" + "reference": "142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942", - "reference": "fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf", + "reference": "142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1|^2|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<5.3", - "symfony/form": "<5.4.21|>=6,<6.2.7", - "symfony/http-foundation": "<5.3", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.2", - "symfony/workflow": "<5.2" + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.2", + "symfony/serializer": "<6.4", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/console": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^5.4.21|^6.2.7", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^4.4|^5.0|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/security-http": "^4.4|^5.0|^6.0", - "symfony/serializer": "^5.2|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.2|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/security-http": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.1|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" }, - "suggest": { - "symfony/asset": "For using the AssetExtension", - "symfony/expression-language": "For using the ExpressionExtension", - "symfony/finder": "", - "symfony/form": "For using the FormExtension", - "symfony/http-kernel": "For using the HttpKernelExtension", - "symfony/routing": "For using the RoutingExtension", - "symfony/security-core": "For using the SecurityExtension", - "symfony/security-csrf": "For using the CsrfExtension", - "symfony/security-http": "For using the LogoutUrlExtension", - "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/translation": "For using the TranslationExtension", - "symfony/var-dumper": "For using the DumpExtension", - "symfony/web-link": "For using the WebLinkExtension", - "symfony/yaml": "For using the YamlExtension" - }, - "time": "2023-11-09T21:19:08+00:00", + "time": "2023-11-25T08:25:13+00:00", "type": "symfony-bridge", "installation-source": "dist", "autoload": { @@ -4816,7 +4695,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v5.4.31" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.0" }, "funding": [ { @@ -4836,51 +4715,46 @@ }, { "name": "symfony/twig-bundle", - "version": "v5.4.19", - "version_normalized": "5.4.19.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "286bd9e38b9bcb142f1eda0a75b0bbeb49ff34bd" + "reference": "35d84393e598dfb774e6a2bf49e5229a8a6dbe4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/286bd9e38b9bcb142f1eda0a75b0bbeb49ff34bd", - "reference": "286bd9e38b9bcb142f1eda0a75b0bbeb49ff34bd", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/35d84393e598dfb774e6a2bf49e5229a8a6dbe4c", + "reference": "35d84393e598dfb774e6a2bf49e5229a8a6dbe4c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/twig-bridge": "^5.3|^6.0", + "composer-runtime-api": ">=2.1", + "php": ">=8.1", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.4", "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<5.3", - "symfony/framework-bundle": "<5.0", - "symfony/service-contracts": ">=3.0", - "symfony/translation": "<5.0" + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "doctrine/cache": "^1.0|^2.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.0|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, - "time": "2023-01-01T08:32:19+00:00", + "time": "2023-11-07T14:57:07+00:00", "type": "symfony-bundle", "installation-source": "dist", "autoload": { @@ -4908,7 +4782,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v5.4.19" + "source": "https://github.com/symfony/twig-bundle/tree/v6.4.0" }, "funding": [ { @@ -4928,41 +4802,37 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861" + "reference": "c40f7d17e91d8b407582ed51a2bbf83c52c367f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8f306d7b8ef34fb3db3305be97ba8e088fb4861", - "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c40f7d17e91d8b407582ed51a2bbf83c52c367f6", + "reference": "c40f7d17e91d8b407582ed51a2bbf83c52c367f6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" + "symfony/console": "<5.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "time": "2022-07-20T13:00:38+00:00", + "time": "2023-11-09T08:28:32+00:00", "bin": [ "Resources/bin/var-dump-server" ], @@ -5000,7 +4870,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.11" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.0" }, "funding": [ { @@ -5020,27 +4890,27 @@ }, { "name": "symfony/var-exporter", - "version": "v5.4.10", - "version_normalized": "5.4.10.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340" + "reference": "d6081c0316f0f5921f2010d1766925005a82ea3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8fc03ee75eeece3d9be1ef47d26d79bea1afb340", - "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d6081c0316f0f5921f2010d1766925005a82ea3b", + "reference": "d6081c0316f0f5921f2010d1766925005a82ea3b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, - "time": "2022-05-27T12:56:18+00:00", + "time": "2023-11-28T20:41:49+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -5073,10 +4943,12 @@ "export", "hydrate", "instantiate", + "lazy-loading", + "proxy", "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.4.10" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.0" }, "funding": [ { @@ -5096,42 +4968,41 @@ }, { "name": "symfony/web-profiler-bundle", - "version": "v5.4.19", - "version_normalized": "5.4.19.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "cd83822071f2bc05583af1e53c1bc46be625a56d" + "reference": "14752d3fb77c3c69b6cee7c03c06e2d6494a196b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/cd83822071f2bc05583af1e53c1bc46be625a56d", - "reference": "cd83822071f2bc05583af1e53c1bc46be625a56d", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/14752d3fb77c3c69b6cee7c03c06e2d6494a196b", + "reference": "14752d3fb77c3c69b6cee7c03c06e2d6494a196b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.3|^6.0", - "symfony/http-kernel": "^5.3|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "php": ">=8.1", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<5.2", - "symfony/form": "<4.4", + "symfony/form": "<5.4", "symfony/mailer": "<5.4", - "symfony/messenger": "<4.4" + "symfony/messenger": "<5.4", + "symfony/twig-bundle": ">=7.0" }, "require-dev": { - "symfony/browser-kit": "^4.4|^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, - "time": "2023-01-01T08:32:19+00:00", + "time": "2023-11-07T14:57:07+00:00", "type": "symfony-bundle", "installation-source": "dist", "autoload": { @@ -5158,8 +5029,11 @@ ], "description": "Provides a development tool that gives detailed information about the execution of any request", "homepage": "https://symfony.com", + "keywords": [ + "dev" + ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.4.19" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.0" }, "funding": [ { @@ -5179,34 +5053,31 @@ }, { "name": "symfony/yaml", - "version": "v5.4.19", - "version_normalized": "5.4.19.0", + "version": "v6.4.0", + "version_normalized": "6.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "71c05db20cb9b54d381a28255f17580e2b7e36a5" + "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/71c05db20cb9b54d381a28255f17580e2b7e36a5", - "reference": "71c05db20cb9b54d381a28255f17580e2b7e36a5", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4f9237a1bb42455d609e6687d2613dde5b41a587", + "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" + "symfony/console": "^5.4|^6.0|^7.0" }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "time": "2023-01-10T18:51:14+00:00", + "time": "2023-11-06T11:00:25+00:00", "bin": [ "Resources/bin/yaml-lint" ], @@ -5237,7 +5108,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.19" + "source": "https://github.com/symfony/yaml/tree/v6.4.0" }, "funding": [ { diff --git a/lib/composer/installed.php b/lib/composer/installed.php index 47ed00828..0e22c3555 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'combodo/itop', 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '5465287089bacacae3304af6688ce5991893835a', + 'reference' => '4fb581c784061ae348ea6d19e70248ec1bfeb310', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -22,7 +22,7 @@ 'combodo/itop' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '5465287089bacacae3304af6688ce5991893835a', + 'reference' => '4fb581c784061ae348ea6d19e70248ec1bfeb310', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -215,9 +215,9 @@ 'dev_requirement' => false, ), 'psr/cache' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/cache', 'aliases' => array(), @@ -226,7 +226,7 @@ 'psr/cache-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0|2.0', + 0 => '2.0|3.0', ), ), 'psr/container' => array( @@ -242,7 +242,7 @@ 'dev_requirement' => false, 'provided' => array( 0 => '^1.0', - 1 => '1.0', + 1 => '1.1|2.0', ), ), 'psr/event-dispatcher' => array( @@ -317,13 +317,13 @@ 'psr/log-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0|2.0', + 0 => '1.0|2.0|3.0', ), ), 'psr/simple-cache-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0|2.0', + 0 => '1.0|2.0|3.0', ), ), 'ralouphie/getallheaders' => array( @@ -360,18 +360,18 @@ 'dev_requirement' => false, ), 'symfony/cache' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => '5a0fff46df349f0db3fe242263451fddf5277362', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'ac2d25f97b17eec6e19760b6b9962a4f7c44356a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/cache', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/cache-contracts' => array( - 'pretty_version' => 'v2.5.2', - 'version' => '2.5.2.0', - 'reference' => '64be4a7acb83b6f2bf6de9a02cee6dad41277ebc', + 'pretty_version' => 'v3.4.0', + 'version' => '3.4.0.0', + 'reference' => '1d74b127da04ffa87aa940abe15446fa89653778', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/cache-contracts', 'aliases' => array(), @@ -380,85 +380,85 @@ 'symfony/cache-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0|2.0', + 0 => '1.1|2.0|3.0', ), ), 'symfony/config' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => 'ec79e03125c1d2477e43dde8528535d90cc78379', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '5d33e0fb707d603330e0edfd4691803a1253572e', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/config', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/console' => array( - 'pretty_version' => 'v5.4.19', - 'version' => '5.4.19.0', - 'reference' => 'dccb8d251a9017d5994c988b034d3e18aaabf740', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'cd9864b47c367450e14ab32f78fdbf98c44c26b6', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/css-selector' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => 'c1681789f059ab756001052164726ae88512ae3d', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'd036c6c0d0b09e24a14a35f8292146a658f986e4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/css-selector', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/dependency-injection' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => 'a8b9251016e9476db73e25fa836904bc0bf74c62', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '5dc8ad5f2bbba7046f5947682bf7d868ce80d4e8', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dependency-injection', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/deprecation-contracts' => array( - 'pretty_version' => 'v2.5.2', - 'version' => '2.5.2.0', - 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', + 'pretty_version' => 'v3.4.0', + 'version' => '3.4.0.0', + 'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/dotenv' => array( - 'pretty_version' => 'v5.4.19', - 'version' => '5.4.19.0', - 'reference' => '38190ba62566afa26ca723a795d0a004e061bd2a', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'd0d584a91422ddaa2c94317200d4c4e5b935555f', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dotenv', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/error-handler' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => 'f75d17cb4769eb38cd5fccbda95cd80a054d35c8', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'c873490a1c97b3a0a4838afc36ff36c112d02788', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/error-handler', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/event-dispatcher' => array( - 'pretty_version' => 'v5.4.9', - 'version' => '5.4.9.0', - 'reference' => '8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'd76d2632cfc2206eecb5ad2b26cd5934082941b6', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/event-dispatcher-contracts' => array( - 'pretty_version' => 'v2.5.2', - 'version' => '2.5.2.0', - 'reference' => 'f98b54df6ad059855739db6fcbc2d36995283fe1', + 'pretty_version' => 'v3.4.0', + 'version' => '3.4.0.0', + 'reference' => 'a76aed96a42d2b521153fb382d418e30d18b59df', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', 'aliases' => array(), @@ -467,139 +467,130 @@ 'symfony/event-dispatcher-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '2.0', + 0 => '2.0|3.0', ), ), 'symfony/filesystem' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => '6699fb0228d1bc35b12aed6dd5e7455457609ddd', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '952a8cb588c3bc6ce76f6023000fb932f16a6e59', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/finder' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => '7872a66f57caffa2916a584db1aa7f12adc76f8c', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '11d736e97f116ac375a81f96e662911a34cd50ce', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/framework-bundle' => array( - 'pretty_version' => 'v5.4.19', - 'version' => '5.4.19.0', - 'reference' => 'a208ee578000f9dedcb50a9784ec7ff8706a7bf1', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '981e016715b4a7f22f58c1d9fdf444311965d25e', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/framework-bundle', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/http-foundation' => array( - 'pretty_version' => 'v5.4.20', - 'version' => '5.4.20.0', - 'reference' => 'd0435363362a47c14e9cf50663cb8ffbf491875a', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '44a6d39a9cc11e154547d882d5aac1e014440771', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-foundation', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/http-kernel' => array( - 'pretty_version' => 'v5.4.20', - 'version' => '5.4.20.0', - 'reference' => 'aaeec341582d3c160cc9ecfa8b2419ba6c69954e', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '16a29c453966f29466ad34444ce97970a336f3c8', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-kernel', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => '6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => 'ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-grapheme' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => '433d05519ce6990bf3530fba6957499d327395c2', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '875e90aeea2777b6f135677f618529449334a612', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-idn' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => '59a8d271f00dd0e4c2e518104cc7963f655a1aa8', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => 'ecaafce9f77234a6a449d29e49267ba10499116d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-normalizer' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => '219aa369ceff116e673852dce47c3a41794c14bd', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => '9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '42292d99c55abe617799667f454222c54c60e229', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php72' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => 'bf44a9fd41feaac72b074de600314a93e2ae78e2', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '70f4aebd92afca2f865444d30a4d2151c13c3179', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php72', 'aliases' => array(), 'dev_requirement' => false, ), - 'symfony/polyfill-php73' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => 'e440d35fa0286f77fb45b79a03fedbeda9307e85', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-php73', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '6caa57379c4aec19c0a12a38b59b26487dcfe4b5', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false, ), - 'symfony/polyfill-php81' => array( - 'pretty_version' => 'v1.26.0', - 'version' => '1.26.0.0', - 'reference' => '13f6d1271c663dc5ae9fb843a8f16521db7687a1', + 'symfony/polyfill-php83' => array( + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => 'b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11', 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'install_path' => __DIR__ . '/../symfony/polyfill-php83', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/routing' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => '3e01ccd9b2a3a4167ba2b3c53612762300300226', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'ae014d60d7c8e80be5c3b644a286e91249a3e8f4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/routing', 'aliases' => array(), @@ -617,85 +608,85 @@ 'symfony/service-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0|2.0', + 0 => '1.1|2.0|3.0', ), ), 'symfony/stopwatch' => array( - 'pretty_version' => 'v5.4.19', - 'version' => '5.4.19.0', - 'reference' => 'bd2b066090fd6a67039371098fa25a84cb2679ec', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/stopwatch', 'aliases' => array(), 'dev_requirement' => true, ), 'symfony/string' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => '5eb661e49ad389e4ae2b6e4df8d783a8a6548322', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'b45fcf399ea9c3af543a92edf7172ba21174d809', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/string', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/translation-contracts' => array( - 'pretty_version' => 'v2.5.2', - 'version' => '2.5.2.0', - 'reference' => '136b19dd05cdf0709db6537d058bcab6dd6e2dbe', + 'pretty_version' => 'v3.4.0', + 'version' => '3.4.0.0', + 'reference' => 'dee0c6e5b4c07ce851b462530088e64b255ac9c5', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/translation-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/twig-bridge' => array( - 'pretty_version' => 'v5.4.31', - 'version' => '5.4.31.0', - 'reference' => 'fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf', 'type' => 'symfony-bridge', 'install_path' => __DIR__ . '/../symfony/twig-bridge', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/twig-bundle' => array( - 'pretty_version' => 'v5.4.19', - 'version' => '5.4.19.0', - 'reference' => '286bd9e38b9bcb142f1eda0a75b0bbeb49ff34bd', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '35d84393e598dfb774e6a2bf49e5229a8a6dbe4c', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/twig-bundle', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/var-dumper' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => 'b8f306d7b8ef34fb3db3305be97ba8e088fb4861', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'c40f7d17e91d8b407582ed51a2bbf83c52c367f6', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/var-dumper', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/var-exporter' => array( - 'pretty_version' => 'v5.4.10', - 'version' => '5.4.10.0', - 'reference' => '8fc03ee75eeece3d9be1ef47d26d79bea1afb340', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => 'd6081c0316f0f5921f2010d1766925005a82ea3b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/var-exporter', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/web-profiler-bundle' => array( - 'pretty_version' => 'v5.4.19', - 'version' => '5.4.19.0', - 'reference' => 'cd83822071f2bc05583af1e53c1bc46be625a56d', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '14752d3fb77c3c69b6cee7c03c06e2d6494a196b', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/web-profiler-bundle', 'aliases' => array(), 'dev_requirement' => true, ), 'symfony/yaml' => array( - 'pretty_version' => 'v5.4.19', - 'version' => '5.4.19.0', - 'reference' => '71c05db20cb9b54d381a28255f17580e2b7e36a5', + 'pretty_version' => 'v6.4.0', + 'version' => '6.4.0.0', + 'reference' => '4f9237a1bb42455d609e6687d2613dde5b41a587', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), diff --git a/lib/psr/cache/README.md b/lib/psr/cache/README.md index c8706ceea..9855a318b 100644 --- a/lib/psr/cache/README.md +++ b/lib/psr/cache/README.md @@ -1,9 +1,12 @@ -PSR Cache -========= +Caching Interface +============== -This repository holds all interfaces defined by -[PSR-6](http://www.php-fig.org/psr/psr-6/). +This repository holds all interfaces related to [PSR-6 (Caching Interface)][psr-url]. -Note that this is not a Cache implementation of its own. It is merely an -interface that describes a Cache implementation. See the specification for more -details. +Note that this is not a Caching implementation of its own. It is merely interfaces that describe the components of a Caching mechanism. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-6/ +[package-url]: https://packagist.org/packages/psr/cache +[implementation-url]: https://packagist.org/providers/psr/cache-implementation diff --git a/lib/psr/cache/composer.json b/lib/psr/cache/composer.json index e828fec94..4b687971e 100644 --- a/lib/psr/cache/composer.json +++ b/lib/psr/cache/composer.json @@ -6,11 +6,11 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "autoload": { "psr-4": { diff --git a/lib/psr/cache/src/CacheException.php b/lib/psr/cache/src/CacheException.php index e27f22f8d..bb785f46c 100644 --- a/lib/psr/cache/src/CacheException.php +++ b/lib/psr/cache/src/CacheException.php @@ -5,6 +5,6 @@ namespace Psr\Cache; /** * Exception interface for all exceptions thrown by an Implementing Library. */ -interface CacheException +interface CacheException extends \Throwable { } diff --git a/lib/psr/cache/src/CacheItemInterface.php b/lib/psr/cache/src/CacheItemInterface.php index 63d05dd1f..2b2e4bb88 100644 --- a/lib/psr/cache/src/CacheItemInterface.php +++ b/lib/psr/cache/src/CacheItemInterface.php @@ -32,7 +32,7 @@ interface CacheItemInterface * @return string * The key string for this cache item. */ - public function getKey(); + public function getKey(): string; /** * Retrieves the value of the item from the cache associated with this object's key. @@ -46,7 +46,7 @@ interface CacheItemInterface * @return mixed * The value corresponding to this cache item's key, or null if not found. */ - public function get(); + public function get(): mixed; /** * Confirms if the cache item lookup resulted in a cache hit. @@ -57,7 +57,7 @@ interface CacheItemInterface * @return bool * True if the request resulted in a cache hit. False otherwise. */ - public function isHit(); + public function isHit(): bool; /** * Sets the value represented by this cache item. @@ -72,12 +72,12 @@ interface CacheItemInterface * @return static * The invoked object. */ - public function set($value); + public function set(mixed $value): static; /** * Sets the expiration time for this cache item. * - * @param \DateTimeInterface|null $expiration + * @param ?\DateTimeInterface $expiration * The point in time after which the item MUST be considered expired. * If null is passed explicitly, a default value MAY be used. If none is set, * the value should be stored permanently or for as long as the @@ -86,7 +86,7 @@ interface CacheItemInterface * @return static * The called object. */ - public function expiresAt($expiration); + public function expiresAt(?\DateTimeInterface $expiration): static; /** * Sets the expiration time for this cache item. @@ -101,5 +101,5 @@ interface CacheItemInterface * @return static * The called object. */ - public function expiresAfter($time); + public function expiresAfter(int|\DateInterval|null $time): static; } diff --git a/lib/psr/cache/src/CacheItemPoolInterface.php b/lib/psr/cache/src/CacheItemPoolInterface.php index 035141967..4b3017c75 100644 --- a/lib/psr/cache/src/CacheItemPoolInterface.php +++ b/lib/psr/cache/src/CacheItemPoolInterface.php @@ -29,7 +29,7 @@ interface CacheItemPoolInterface * @return CacheItemInterface * The corresponding Cache Item. */ - public function getItem($key); + public function getItem(string $key): CacheItemInterface; /** * Returns a traversable set of cache items. @@ -41,13 +41,13 @@ interface CacheItemPoolInterface * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException * MUST be thrown. * - * @return array|\Traversable - * A traversable collection of Cache Items keyed by the cache keys of + * @return iterable + * An iterable collection of Cache Items keyed by the cache keys of * each item. A Cache item will be returned for each key, even if that * key is not found. However, if no keys are specified then an empty * traversable MUST be returned instead. */ - public function getItems(array $keys = array()); + public function getItems(array $keys = []): iterable; /** * Confirms if the cache contains specified cache item. @@ -66,7 +66,7 @@ interface CacheItemPoolInterface * @return bool * True if item exists in the cache, false otherwise. */ - public function hasItem($key); + public function hasItem(string $key): bool; /** * Deletes all items in the pool. @@ -74,7 +74,7 @@ interface CacheItemPoolInterface * @return bool * True if the pool was successfully cleared. False if there was an error. */ - public function clear(); + public function clear(): bool; /** * Removes the item from the pool. @@ -89,14 +89,14 @@ interface CacheItemPoolInterface * @return bool * True if the item was successfully removed. False if there was an error. */ - public function deleteItem($key); + public function deleteItem(string $key): bool; /** * Removes multiple items from the pool. * * @param string[] $keys * An array of keys that should be removed from the pool. - + * * @throws InvalidArgumentException * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException * MUST be thrown. @@ -104,7 +104,7 @@ interface CacheItemPoolInterface * @return bool * True if the items were successfully removed. False if there was an error. */ - public function deleteItems(array $keys); + public function deleteItems(array $keys): bool; /** * Persists a cache item immediately. @@ -115,7 +115,7 @@ interface CacheItemPoolInterface * @return bool * True if the item was successfully persisted. False if there was an error. */ - public function save(CacheItemInterface $item); + public function save(CacheItemInterface $item): bool; /** * Sets a cache item to be persisted later. @@ -126,7 +126,7 @@ interface CacheItemPoolInterface * @return bool * False if the item could not be queued or if a commit was attempted and failed. True otherwise. */ - public function saveDeferred(CacheItemInterface $item); + public function saveDeferred(CacheItemInterface $item): bool; /** * Persists any deferred cache items. @@ -134,5 +134,5 @@ interface CacheItemPoolInterface * @return bool * True if all not-yet-saved items were successfully saved or there were none. False otherwise. */ - public function commit(); + public function commit(): bool; } diff --git a/lib/symfony/cache-contracts/.gitignore b/lib/symfony/cache-contracts/.gitignore deleted file mode 100644 index c49a5d8df..000000000 --- a/lib/symfony/cache-contracts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/cache-contracts/CacheInterface.php b/lib/symfony/cache-contracts/CacheInterface.php index 67e4dfd3a..a4fcea731 100644 --- a/lib/symfony/cache-contracts/CacheInterface.php +++ b/lib/symfony/cache-contracts/CacheInterface.php @@ -29,29 +29,31 @@ interface CacheInterface * requested key, that could be used e.g. for expiration control. It could also * be an ItemInterface instance when its additional features are needed. * - * @param string $key The key of the item to retrieve from the cache - * @param callable|CallbackInterface $callback Should return the computed value for the given key/item - * @param float|null $beta A float that, as it grows, controls the likeliness of triggering - * early expiration. 0 disables it, INF forces immediate expiration. - * The default (or providing null) is implementation dependent but should - * typically be 1.0, which should provide optimal stampede protection. - * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration - * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * @template T * - * @return mixed + * @param string $key The key of the item to retrieve from the cache + * @param (callable(CacheItemInterface,bool):T)|(callable(ItemInterface,bool):T)|CallbackInterface $callback + * @param float|null $beta A float that, as it grows, controls the likeliness of triggering + * early expiration. 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. + * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * + * @return T * * @throws InvalidArgumentException When $key is not valid or when $beta is negative */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null); + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed; /** * Removes an item from the pool. * * @param string $key The key to delete * - * @throws InvalidArgumentException When $key is not valid - * * @return bool True if the item was successfully removed, false if there was any error + * + * @throws InvalidArgumentException When $key is not valid */ public function delete(string $key): bool; } diff --git a/lib/symfony/cache-contracts/CacheTrait.php b/lib/symfony/cache-contracts/CacheTrait.php index d340e0696..8a4b0bda8 100644 --- a/lib/symfony/cache-contracts/CacheTrait.php +++ b/lib/symfony/cache-contracts/CacheTrait.php @@ -25,28 +25,20 @@ class_exists(InvalidArgumentException::class); */ trait CacheTrait { - /** - * {@inheritdoc} - * - * @return mixed - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed { return $this->doGet($this, $key, $callback, $beta, $metadata); } - /** - * {@inheritdoc} - */ public function delete(string $key): bool { return $this->deleteItem($key); } - private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null) + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null): mixed { - if (0 > $beta = $beta ?? 1.0) { - throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException { }; + if (0 > $beta ??= 1.0) { + throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException {}; } $item = $pool->getItem($key); @@ -60,7 +52,7 @@ trait CacheTrait if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) { // force applying defaultLifetime to expiry $item->expiresAt(null); - $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ + $logger?->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ 'key' => $key, 'delta' => sprintf('%.1f', $expiry - $now), ]); diff --git a/lib/symfony/cache-contracts/CallbackInterface.php b/lib/symfony/cache-contracts/CallbackInterface.php index 7dae2aac3..15941e9c9 100644 --- a/lib/symfony/cache-contracts/CallbackInterface.php +++ b/lib/symfony/cache-contracts/CallbackInterface.php @@ -17,6 +17,8 @@ use Psr\Cache\CacheItemInterface; * Computes and returns the cached value of an item. * * @author Nicolas Grekas + * + * @template T */ interface CallbackInterface { @@ -24,7 +26,7 @@ interface CallbackInterface * @param CacheItemInterface|ItemInterface $item The item to compute the value for * @param bool &$save Should be set to false when the value should not be saved in the pool * - * @return mixed The computed value for the passed item + * @return T The computed value for the passed item */ - public function __invoke(CacheItemInterface $item, bool &$save); + public function __invoke(CacheItemInterface $item, bool &$save): mixed; } diff --git a/lib/symfony/cache-contracts/ItemInterface.php b/lib/symfony/cache-contracts/ItemInterface.php index 10c048897..8c4c51253 100644 --- a/lib/symfony/cache-contracts/ItemInterface.php +++ b/lib/symfony/cache-contracts/ItemInterface.php @@ -54,7 +54,7 @@ interface ItemInterface extends CacheItemInterface * @throws InvalidArgumentException When $tag is not valid * @throws CacheException When the item comes from a pool that is not tag-aware */ - public function tag($tags): self; + public function tag(string|iterable $tags): static; /** * Returns a list of metadata info that were saved alongside with the cached value. diff --git a/lib/symfony/cache-contracts/LICENSE b/lib/symfony/cache-contracts/LICENSE index 74cdc2dbf..7536caeae 100644 --- a/lib/symfony/cache-contracts/LICENSE +++ b/lib/symfony/cache-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2022 Fabien Potencier +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/cache-contracts/README.md b/lib/symfony/cache-contracts/README.md index 7085a6996..ffe0833af 100644 --- a/lib/symfony/cache-contracts/README.md +++ b/lib/symfony/cache-contracts/README.md @@ -3,7 +3,7 @@ Symfony Cache Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/lib/symfony/cache-contracts/TagAwareCacheInterface.php b/lib/symfony/cache-contracts/TagAwareCacheInterface.php index 7c4cf1111..8e0b6be82 100644 --- a/lib/symfony/cache-contracts/TagAwareCacheInterface.php +++ b/lib/symfony/cache-contracts/TagAwareCacheInterface.php @@ -34,5 +34,5 @@ interface TagAwareCacheInterface extends CacheInterface * * @throws InvalidArgumentException When $tags is not valid */ - public function invalidateTags(array $tags); + public function invalidateTags(array $tags): bool; } diff --git a/lib/symfony/cache-contracts/composer.json b/lib/symfony/cache-contracts/composer.json index 9f45e1780..f80d0b559 100644 --- a/lib/symfony/cache-contracts/composer.json +++ b/lib/symfony/cache-contracts/composer.json @@ -16,11 +16,8 @@ } ], "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0|^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" + "php": ">=8.1", + "psr/cache": "^3.0" }, "autoload": { "psr-4": { "Symfony\\Contracts\\Cache\\": "" } @@ -28,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/lib/symfony/cache/Adapter/AbstractAdapter.php b/lib/symfony/cache/Adapter/AbstractAdapter.php index 3d0140922..ed90f4716 100644 --- a/lib/symfony/cache/Adapter/AbstractAdapter.php +++ b/lib/symfony/cache/Adapter/AbstractAdapter.php @@ -33,8 +33,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg */ protected const NS_SEPARATOR = ':'; - private static $apcuSupported; - private static $phpFilesSupported; + private static bool $apcuSupported; protected function __construct(string $namespace = '', int $defaultLifetime = 0) { @@ -43,28 +42,20 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); } - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + self::$createCacheItem ??= \Closure::bind( static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; - $item->value = $v = $value; + $item->value = $value; $item->isHit = $isHit; - // Detect wrapped values that encode for their expiry and creation duration - // For compactness, these values are packed in the key of an array using - // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F - if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { - $item->value = $v[$k]; - $v = unpack('Ve/Nc', substr($k, 1, -1)); - $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; - $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; - } + $item->unpack(); return $item; }, null, CacheItem::class ); - self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind( + self::$mergeByLifetime ??= \Closure::bind( static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) { $byLifetime = []; $now = microtime(true); @@ -80,11 +71,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg $expiredIds[] = $getId($key); continue; } - if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { - unset($metadata[CacheItem::METADATA_TAGS]); - } - // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators - $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value; + $byLifetime[$ttl][$getId($key)] = $item->pack(); } return $byLifetime; @@ -98,21 +85,19 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg * Returns the best possible adapter that your runtime supports. * * Using ApcuAdapter makes system caches compatible with read-only filesystems. - * - * @return AdapterInterface */ - public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, LoggerInterface $logger = null) + public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, LoggerInterface $logger = null): AdapterInterface { $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true); if (null !== $logger) { $opcache->setLogger($logger); } - if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) { + if (!self::$apcuSupported ??= ApcuAdapter::isSupported()) { return $opcache; } - if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { + if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) { return $opcache; } @@ -124,7 +109,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg return new ChainAdapter([$apcu, $opcache]); } - public static function createConnection(string $dsn, array $options = []) + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): mixed { if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) { return RedisAdapter::createConnection($dsn, $options); @@ -132,7 +117,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg if (str_starts_with($dsn, 'memcached:')) { return MemcachedAdapter::createConnection($dsn, $options); } - if (0 === strpos($dsn, 'couchbase:')) { + if (str_starts_with($dsn, 'couchbase:')) { if (CouchbaseBucketAdapter::isSupported()) { return CouchbaseBucketAdapter::createConnection($dsn, $options); } @@ -140,18 +125,13 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg return CouchbaseCollectionAdapter::createConnection($dsn, $options); } - throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn)); + throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:" nor "couchbase:".'); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { $ok = true; - $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, \Closure::fromCallable([$this, 'getId']), $this->defaultLifetime); + $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, $this->getId(...), $this->defaultLifetime); $retry = $this->deferred = []; if ($expiredIds) { diff --git a/lib/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/lib/symfony/cache/Adapter/AbstractTagAwareAdapter.php index a384b16ad..ef62b4fb2 100644 --- a/lib/symfony/cache/Adapter/AbstractTagAwareAdapter.php +++ b/lib/symfony/cache/Adapter/AbstractTagAwareAdapter.php @@ -35,7 +35,7 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA use AbstractAdapterTrait; use ContractsTrait; - private const TAGS_PREFIX = "\0tags\0"; + private const TAGS_PREFIX = "\1tags\1"; protected function __construct(string $namespace = '', int $defaultLifetime = 0) { @@ -44,7 +44,7 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); } - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + self::$createCacheItem ??= \Closure::bind( static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; @@ -56,7 +56,7 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA $item->isHit = $isHit; // Extract value, tags and meta data from the cache value $item->value = $value['value']; - $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? []; + $item->metadata[CacheItem::METADATA_TAGS] = isset($value['tags']) ? array_combine($value['tags'], $value['tags']) : []; if (isset($value['meta'])) { // For compactness these values are packed, & expiry is offset to reduce size $v = unpack('Ve/Nc', $value['meta']); @@ -69,7 +69,7 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA null, CacheItem::class ); - self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind( + self::$mergeByLifetime ??= \Closure::bind( static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) { $byLifetime = []; $now = microtime(true); @@ -95,18 +95,19 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA if ($metadata) { // For compactness, expiry and creation duration are packed, using magic numbers as separators - $value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]); + $value['meta'] = pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME]); } // Extract tag changes, these should be removed from values in doSave() $value['tag-operations'] = ['add' => [], 'remove' => []]; $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? []; - foreach (array_diff($value['tags'], $oldTags) as $addedTag) { + foreach (array_diff_key($value['tags'], $oldTags) as $addedTag) { $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag); } - foreach (array_diff($oldTags, $value['tags']) as $removedTag) { + foreach (array_diff_key($oldTags, $value['tags']) as $removedTag) { $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag); } + $value['tags'] = array_keys($value['tags']); $byLifetime[$ttl][$getId($key)] = $value; $item->metadata = $item->newMetadata; @@ -135,10 +136,8 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA * Removes multiple items from the pool and their corresponding tags. * * @param array $ids An array of identifiers that should be removed from the pool - * - * @return bool */ - abstract protected function doDelete(array $ids); + abstract protected function doDelete(array $ids): bool; /** * Removes relations between tags and deleted items. @@ -166,13 +165,10 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA $this->doDelete($ids); } - /** - * {@inheritdoc} - */ public function commit(): bool { $ok = true; - $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, \Closure::fromCallable([$this, 'getId']), self::TAGS_PREFIX, $this->defaultLifetime); + $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, $this->getId(...), self::TAGS_PREFIX, $this->defaultLifetime); $retry = $this->deferred = []; if ($expiredIds) { @@ -230,9 +226,6 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA return $ok; } - /** - * {@inheritdoc} - */ public function deleteItems(array $keys): bool { if (!$keys) { @@ -254,7 +247,7 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id; } } - } catch (\Exception $e) { + } catch (\Exception) { $ok = false; } @@ -262,7 +255,7 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) { return true; } - } catch (\Exception $e) { + } catch (\Exception) { } // When bulk-delete failed, retry each item individually @@ -282,12 +275,9 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA return $ok; } - /** - * {@inheritdoc} - */ - public function invalidateTags(array $tags) + public function invalidateTags(array $tags): bool { - if (empty($tags)) { + if (!$tags) { return false; } diff --git a/lib/symfony/cache/Adapter/AdapterInterface.php b/lib/symfony/cache/Adapter/AdapterInterface.php index f8dce866d..e55672093 100644 --- a/lib/symfony/cache/Adapter/AdapterInterface.php +++ b/lib/symfony/cache/Adapter/AdapterInterface.php @@ -24,24 +24,12 @@ class_exists(CacheItem::class); */ interface AdapterInterface extends CacheItemPoolInterface { - /** - * {@inheritdoc} - * - * @return CacheItem - */ - public function getItem($key); + public function getItem(mixed $key): CacheItem; /** - * {@inheritdoc} - * - * @return \Traversable + * @return iterable */ - public function getItems(array $keys = []); + public function getItems(array $keys = []): iterable; - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = ''); + public function clear(string $prefix = ''): bool; } diff --git a/lib/symfony/cache/Adapter/ApcuAdapter.php b/lib/symfony/cache/Adapter/ApcuAdapter.php index 270a139e8..3dc93fd54 100644 --- a/lib/symfony/cache/Adapter/ApcuAdapter.php +++ b/lib/symfony/cache/Adapter/ApcuAdapter.php @@ -20,7 +20,7 @@ use Symfony\Component\Cache\Marshaller\MarshallerInterface; */ class ApcuAdapter extends AbstractAdapter { - private $marshaller; + private ?MarshallerInterface $marshaller; /** * @throws CacheException if APCu is not enabled @@ -47,27 +47,20 @@ class ApcuAdapter extends AbstractAdapter $this->marshaller = $marshaller; } + /** + * @return bool + */ public static function isSupported() { - return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN); + return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL); } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); try { $values = []; - $ids = array_flip($ids); - foreach (apcu_fetch(array_keys($ids), $ok) ?: [] as $k => $v) { - if (!isset($ids[$k])) { - // work around https://github.com/krakjoe/apcu/issues/247 - $k = key($ids); - } - unset($ids[$k]); - + foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) { if (null !== $v || $ok) { $values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v; } @@ -81,28 +74,19 @@ class ApcuAdapter extends AbstractAdapter } } - /** - * {@inheritdoc} - */ - protected function doHave(string $id) + protected function doHave(string $id): bool { return apcu_exists($id); } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + protected function doClear(string $namespace): bool { - return isset($namespace[0]) && class_exists(\APCUIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) + return isset($namespace[0]) && class_exists(\APCUIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) ? apcu_delete(new \APCUIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY)) : apcu_clear_cache(); } - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) + protected function doDelete(array $ids): bool { foreach ($ids as $id) { apcu_delete($id); @@ -111,10 +95,7 @@ class ApcuAdapter extends AbstractAdapter return true; } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { if (null !== $this->marshaller && (!$values = $this->marshaller->marshall($values, $failed))) { return $failed; diff --git a/lib/symfony/cache/Adapter/ArrayAdapter.php b/lib/symfony/cache/Adapter/ArrayAdapter.php index d8695b743..1100c7734 100644 --- a/lib/symfony/cache/Adapter/ArrayAdapter.php +++ b/lib/symfony/cache/Adapter/ArrayAdapter.php @@ -30,14 +30,15 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter { use LoggerAwareTrait; - private $storeSerialized; - private $values = []; - private $expiries = []; - private $defaultLifetime; - private $maxLifetime; - private $maxItems; + private bool $storeSerialized; + private array $values = []; + private array $tags = []; + private array $expiries = []; + private int $defaultLifetime; + private float $maxLifetime; + private int $maxItems; - private static $createCacheItem; + private static \Closure $createCacheItem; /** * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise @@ -56,12 +57,15 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter $this->storeSerialized = $storeSerialized; $this->maxLifetime = $maxLifetime; $this->maxItems = $maxItems; - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( - static function ($key, $value, $isHit) { + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit, $tags) { $item = new CacheItem(); $item->key = $key; $item->value = $value; $item->isHit = $isHit; + if (null !== $tags) { + $item->metadata[CacheItem::METADATA_TAGS] = $tags; + } return $item; }, @@ -70,10 +74,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter ); } - /** - * {@inheritdoc} - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed { $item = $this->getItem($key); $metadata = $item->getMetadata(); @@ -90,20 +91,12 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter return $item->get(); } - /** - * {@inheritdoc} - */ public function delete(string $key): bool { return $this->deleteItem($key); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) { if ($this->maxItems) { @@ -120,10 +113,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter return isset($this->expiries[$key]) && !$this->deleteItem($key); } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { if (!$isHit = $this->hasItem($key)) { $value = null; @@ -136,38 +126,25 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; } - return (self::$createCacheItem)($key, $value, $isHit); + return (self::$createCacheItem)($key, $value, $isHit, $this->tags[$key] ?? null); } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { \assert(self::validateKeys($keys)); return $this->generateItems($keys, microtime(true), self::$createCacheItem); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { \assert('' !== CacheItem::validateKey($key)); - unset($this->values[$key], $this->expiries[$key]); + unset($this->values[$key], $this->tags[$key], $this->expiries[$key]); return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { foreach ($keys as $key) { $this->deleteItem($key); @@ -176,12 +153,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { if (!$item instanceof CacheItem) { return false; @@ -213,7 +185,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter } if ($this->maxItems) { - unset($this->values[$key]); + unset($this->values[$key], $this->tags[$key]); // Iterate items and vacuum expired ones while we are at it foreach ($this->values as $k => $v) { @@ -221,49 +193,38 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter break; } - unset($this->values[$k], $this->expiries[$k]); + unset($this->values[$k], $this->tags[$k], $this->expiries[$k]); } } $this->values[$key] = $value; $this->expiries[$key] = $expiry ?? \PHP_INT_MAX; + if (null === $this->tags[$key] = $item["\0*\0newMetadata"][CacheItem::METADATA_TAGS] ?? null) { + unset($this->tags[$key]); + } + return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { return $this->save($item); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { if ('' !== $prefix) { $now = microtime(true); foreach ($this->values as $key => $value) { - if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || 0 === strpos($key, $prefix)) { - unset($this->values[$key], $this->expiries[$key]); + if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || str_starts_with($key, $prefix)) { + unset($this->values[$key], $this->tags[$key], $this->expiries[$key]); } } @@ -272,17 +233,15 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter } } - $this->values = $this->expiries = []; + $this->values = $this->tags = $this->expiries = []; return true; } /** * Returns all cached values, with cache miss as null. - * - * @return array */ - public function getValues() + public function getValues(): array { if (!$this->storeSerialized) { return $this->values; @@ -302,7 +261,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter } /** - * {@inheritdoc} + * @return void */ public function reset() { @@ -331,7 +290,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter } unset($keys[$i]); - yield $key => $f($key, $value, $isHit); + yield $key => $f($key, $value, $isHit, $this->tags[$key] ?? null); } foreach ($keys as $key) { @@ -339,7 +298,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter } } - private function freeze($value, string $key) + private function freeze($value, string $key): string|int|float|bool|array|\UnitEnum|null { if (null === $value) { return 'N;'; @@ -353,12 +312,12 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter try { $serialized = serialize($value); } catch (\Exception $e) { - unset($this->values[$key]); + unset($this->values[$key], $this->tags[$key]); $type = get_debug_type($value); $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage()); CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); - return; + return null; } // Keep value serialized if it contains any objects or any internal references if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) { @@ -369,7 +328,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter return $value; } - private function unfreeze(string $key, bool &$isHit) + private function unfreeze(string $key, bool &$isHit): mixed { if ('N;' === $value = $this->values[$key]) { return null; diff --git a/lib/symfony/cache/Adapter/ChainAdapter.php b/lib/symfony/cache/Adapter/ChainAdapter.php index 059c0ed27..8c2e7e111 100644 --- a/lib/symfony/cache/Adapter/ChainAdapter.php +++ b/lib/symfony/cache/Adapter/ChainAdapter.php @@ -33,11 +33,11 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa { use ContractsTrait; - private $adapters = []; - private $adapterCount; - private $defaultLifetime; + private array $adapters = []; + private int $adapterCount; + private int $defaultLifetime; - private static $syncItem; + private static \Closure $syncItem; /** * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items @@ -53,7 +53,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa if (!$adapter instanceof CacheItemPoolInterface) { throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class)); } - if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { + if ('cli' === \PHP_SAPI && $adapter instanceof ApcuAdapter && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) { continue; // skip putting APCu in the chain when the backend is disabled } @@ -66,18 +66,17 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa $this->adapterCount = \count($this->adapters); $this->defaultLifetime = $defaultLifetime; - self::$syncItem ?? self::$syncItem = \Closure::bind( + self::$syncItem ??= \Closure::bind( static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) { $sourceItem->isTaggable = false; - $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata; - unset($sourceMetadata[CacheItem::METADATA_TAGS]); + $sourceMetadata ??= $sourceItem->metadata; $item->value = $sourceItem->value; $item->isHit = $sourceItem->isHit; $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata; if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) { - $item->expiresAt(\DateTime::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY]))); + $item->expiresAt(\DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY]))); } elseif (0 < $defaultLifetime) { $item->expiresAfter($defaultLifetime); } @@ -89,10 +88,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa ); } - /** - * {@inheritdoc} - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed { $doSave = true; $callback = static function (CacheItem $item, bool &$save) use ($callback, &$doSave) { @@ -102,9 +98,9 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $value; }; - $lastItem = null; - $i = 0; - $wrap = function (CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$i, &$doSave, &$lastItem, &$metadata) { + $wrap = function (CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$doSave, &$metadata) { + static $lastItem; + static $i = 0; $adapter = $this->adapters[$i]; if (isset($this->adapters[++$i])) { $callback = $wrap; @@ -116,7 +112,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); } if (null !== $item) { - (self::$syncItem)($lastItem = $lastItem ?? $item, $item, $this->defaultLifetime, $metadata); + (self::$syncItem)($lastItem ??= $item, $item, $this->defaultLifetime, $metadata); } $save = $doSave; @@ -126,10 +122,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $wrap(); } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { $syncItem = self::$syncItem; $misses = []; @@ -151,10 +144,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $item; } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { return $this->generateItems($this->adapters[0]->getItems($keys), 0); } @@ -190,12 +180,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { foreach ($this->adapters as $adapter) { if ($adapter->hasItem($key)) { @@ -206,12 +191,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return false; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { $cleared = true; $i = $this->adapterCount; @@ -227,12 +207,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $cleared; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { $deleted = true; $i = $this->adapterCount; @@ -244,12 +219,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $deleted; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { $deleted = true; $i = $this->adapterCount; @@ -261,12 +231,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $deleted; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { $saved = true; $i = $this->adapterCount; @@ -278,12 +243,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $saved; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { $saved = true; $i = $this->adapterCount; @@ -295,12 +255,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $saved; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { $committed = true; $i = $this->adapterCount; @@ -312,10 +267,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $committed; } - /** - * {@inheritdoc} - */ - public function prune() + public function prune(): bool { $pruned = true; @@ -329,7 +281,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa } /** - * {@inheritdoc} + * @return void */ public function reset() { diff --git a/lib/symfony/cache/Adapter/CouchbaseBucketAdapter.php b/lib/symfony/cache/Adapter/CouchbaseBucketAdapter.php index 36d5249b4..f8cb92dbf 100644 --- a/lib/symfony/cache/Adapter/CouchbaseBucketAdapter.php +++ b/lib/symfony/cache/Adapter/CouchbaseBucketAdapter.php @@ -36,8 +36,8 @@ class CouchbaseBucketAdapter extends AbstractAdapter 'durabilityTimeout', ]; - private $bucket; - private $marshaller; + private \CouchbaseBucket $bucket; + private MarshallerInterface $marshaller; public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { @@ -54,22 +54,17 @@ class CouchbaseBucketAdapter extends AbstractAdapter $this->marshaller = $marshaller ?? new DefaultMarshaller(); } - /** - * @param array|string $servers - */ - public static function createConnection($servers, array $options = []): \CouchbaseBucket + public static function createConnection(#[\SensitiveParameter] array|string $servers, array $options = []): \CouchbaseBucket { if (\is_string($servers)) { $servers = [$servers]; - } elseif (!\is_array($servers)) { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($servers))); } if (!static::isSupported()) { throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); } - set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?' .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\?]+))(?:\?(?.*))?$/i'; @@ -82,8 +77,8 @@ class CouchbaseBucketAdapter extends AbstractAdapter $password = $options['password']; foreach ($servers as $dsn) { - if (0 !== strpos($dsn, 'couchbase:')) { - throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $dsn)); + if (!str_starts_with($dsn, 'couchbase:')) { + throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); } preg_match($dsnPattern, $dsn, $matches); @@ -146,25 +141,22 @@ class CouchbaseBucketAdapter extends AbstractAdapter private static function initOptions(array $options): array { - $options['username'] = $options['username'] ?? ''; - $options['password'] = $options['password'] ?? ''; - $options['operationTimeout'] = $options['operationTimeout'] ?? 0; - $options['configTimeout'] = $options['configTimeout'] ?? 0; - $options['configNodeTimeout'] = $options['configNodeTimeout'] ?? 0; - $options['n1qlTimeout'] = $options['n1qlTimeout'] ?? 0; - $options['httpTimeout'] = $options['httpTimeout'] ?? 0; - $options['configDelay'] = $options['configDelay'] ?? 0; - $options['htconfigIdleTimeout'] = $options['htconfigIdleTimeout'] ?? 0; - $options['durabilityInterval'] = $options['durabilityInterval'] ?? 0; - $options['durabilityTimeout'] = $options['durabilityTimeout'] ?? 0; + $options['username'] ??= ''; + $options['password'] ??= ''; + $options['operationTimeout'] ??= 0; + $options['configTimeout'] ??= 0; + $options['configNodeTimeout'] ??= 0; + $options['n1qlTimeout'] ??= 0; + $options['httpTimeout'] ??= 0; + $options['configDelay'] ??= 0; + $options['htconfigIdleTimeout'] ??= 0; + $options['durabilityInterval'] ??= 0; + $options['durabilityTimeout'] ??= 0; return $options; } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { $resultsCouchbase = $this->bucket->get($ids); @@ -179,17 +171,11 @@ class CouchbaseBucketAdapter extends AbstractAdapter return $results; } - /** - * {@inheritdoc} - */ protected function doHave(string $id): bool { return false !== $this->bucket->get($id); } - /** - * {@inheritdoc} - */ protected function doClear(string $namespace): bool { if ('' === $namespace) { @@ -201,9 +187,6 @@ class CouchbaseBucketAdapter extends AbstractAdapter return false; } - /** - * {@inheritdoc} - */ protected function doDelete(array $ids): bool { $results = $this->bucket->remove(array_values($ids)); @@ -218,10 +201,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter return 0 === \count($results); } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { if (!$values = $this->marshaller->marshall($values, $failed)) { return $failed; diff --git a/lib/symfony/cache/Adapter/CouchbaseCollectionAdapter.php b/lib/symfony/cache/Adapter/CouchbaseCollectionAdapter.php index 79f648531..aaa8bbdae 100644 --- a/lib/symfony/cache/Adapter/CouchbaseCollectionAdapter.php +++ b/lib/symfony/cache/Adapter/CouchbaseCollectionAdapter.php @@ -29,9 +29,8 @@ class CouchbaseCollectionAdapter extends AbstractAdapter { private const MAX_KEY_LENGTH = 250; - /** @var Collection */ - private $connection; - private $marshaller; + private Collection $connection; + private MarshallerInterface $marshaller; public function __construct(Collection $connection, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { @@ -48,24 +47,17 @@ class CouchbaseCollectionAdapter extends AbstractAdapter $this->marshaller = $marshaller ?? new DefaultMarshaller(); } - /** - * @param array|string $dsn - * - * @return Bucket|Collection - */ - public static function createConnection($dsn, array $options = []) + public static function createConnection(#[\SensitiveParameter] array|string $dsn, array $options = []): Bucket|Collection { if (\is_string($dsn)) { $dsn = [$dsn]; - } elseif (!\is_array($dsn)) { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($dsn))); } if (!static::isSupported()) { throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.'); } - set_error_handler(function ($type, $msg, $file, $line): bool { throw new \ErrorException($msg, 0, $type, $file, $line); }); + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?' .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\/\?]+))(?:(?:\/(?[^\/]+))' @@ -78,8 +70,8 @@ class CouchbaseCollectionAdapter extends AbstractAdapter $password = $options['password'] ?? ''; foreach ($dsn as $server) { - if (0 !== strpos($server, 'couchbase:')) { - throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $server)); + if (!str_starts_with($server, 'couchbase:')) { + throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); } preg_match($dsnPattern, $server, $matches); @@ -139,16 +131,13 @@ class CouchbaseCollectionAdapter extends AbstractAdapter return $results; } - /** - * {@inheritdoc} - */ protected function doFetch(array $ids): array { $results = []; foreach ($ids as $id) { try { $resultCouchbase = $this->connection->get($id); - } catch (DocumentNotFoundException $exception) { + } catch (DocumentNotFoundException) { continue; } @@ -160,25 +149,16 @@ class CouchbaseCollectionAdapter extends AbstractAdapter return $results; } - /** - * {@inheritdoc} - */ protected function doHave($id): bool { return $this->connection->exists($id)->exists(); } - /** - * {@inheritdoc} - */ protected function doClear($namespace): bool { return false; } - /** - * {@inheritdoc} - */ protected function doDelete(array $ids): bool { $idsErrors = []; @@ -189,17 +169,14 @@ class CouchbaseCollectionAdapter extends AbstractAdapter if (null === $result->mutationToken()) { $idsErrors[] = $id; } - } catch (DocumentNotFoundException $exception) { + } catch (DocumentNotFoundException) { } } return 0 === \count($idsErrors); } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, $lifetime) + protected function doSave(array $values, $lifetime): array|bool { if (!$values = $this->marshaller->marshall($values, $failed)) { return $failed; @@ -212,7 +189,7 @@ class CouchbaseCollectionAdapter extends AbstractAdapter foreach ($values as $key => $value) { try { $this->connection->upsert($key, $value, $upsertOptions); - } catch (\Exception $exception) { + } catch (\Exception) { $ko[$key] = ''; } } diff --git a/lib/symfony/cache/Adapter/DoctrineAdapter.php b/lib/symfony/cache/Adapter/DoctrineAdapter.php deleted file mode 100644 index efa30c842..000000000 --- a/lib/symfony/cache/Adapter/DoctrineAdapter.php +++ /dev/null @@ -1,110 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Cache\Adapter; - -use Doctrine\Common\Cache\CacheProvider; -use Doctrine\Common\Cache\Psr6\CacheAdapter; - -/** - * @author Nicolas Grekas - * - * @deprecated Since Symfony 5.4, use Doctrine\Common\Cache\Psr6\CacheAdapter instead - */ -class DoctrineAdapter extends AbstractAdapter -{ - private $provider; - - public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0) - { - trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "%s" instead.', __CLASS__, CacheAdapter::class); - - parent::__construct('', $defaultLifetime); - $this->provider = $provider; - $provider->setNamespace($namespace); - } - - /** - * {@inheritdoc} - */ - public function reset() - { - parent::reset(); - $this->provider->setNamespace($this->provider->getNamespace()); - } - - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) - { - $unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback'); - try { - return $this->provider->fetchMultiple($ids); - } catch (\Error $e) { - $trace = $e->getTrace(); - - if (isset($trace[0]['function']) && !isset($trace[0]['class'])) { - switch ($trace[0]['function']) { - case 'unserialize': - case 'apcu_fetch': - case 'apc_fetch': - throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); - } - } - - throw $e; - } finally { - ini_set('unserialize_callback_func', $unserializeCallbackHandler); - } - } - - /** - * {@inheritdoc} - */ - protected function doHave(string $id) - { - return $this->provider->contains($id); - } - - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) - { - $namespace = $this->provider->getNamespace(); - - return isset($namespace[0]) - ? $this->provider->deleteAll() - : $this->provider->flushAll(); - } - - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) - { - $ok = true; - foreach ($ids as $id) { - $ok = $this->provider->delete($id) && $ok; - } - - return $ok; - } - - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) - { - return $this->provider->saveMultiple($values, $lifetime); - } -} diff --git a/lib/symfony/cache/Adapter/DoctrineDbalAdapter.php b/lib/symfony/cache/Adapter/DoctrineDbalAdapter.php index 73f0ea6bc..a92e6d420 100644 --- a/lib/symfony/cache/Adapter/DoctrineDbalAdapter.php +++ b/lib/symfony/cache/Adapter/DoctrineDbalAdapter.php @@ -11,13 +11,18 @@ namespace Symfony\Component\Cache\Adapter; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\ServerVersionProvider; +use Doctrine\DBAL\Tools\DsnParser; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\Marshaller\MarshallerInterface; @@ -25,18 +30,18 @@ use Symfony\Component\Cache\PruneableInterface; class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface { - protected $maxIdLength = 255; + private const MAX_KEY_LENGTH = 255; - private $marshaller; - private $conn; - private $platformName; - private $serverVersion; - private $table = 'cache_items'; - private $idCol = 'item_id'; - private $dataCol = 'item_data'; - private $lifetimeCol = 'item_lifetime'; - private $timeCol = 'item_time'; - private $namespace; + private MarshallerInterface $marshaller; + private Connection $conn; + private string $platformName; + private string $serverVersion; + private string $table = 'cache_items'; + private string $idCol = 'item_id'; + private string $dataCol = 'item_data'; + private string $lifetimeCol = 'item_lifetime'; + private string $timeCol = 'item_time'; + private string $namespace; /** * You can either pass an existing database Doctrine DBAL Connection or @@ -52,11 +57,9 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] * * db_time_col: The column where to store the timestamp [default: item_time] * - * @param Connection|string $connOrDsn - * * @throws InvalidArgumentException When namespace contains invalid characters */ - public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) + public function __construct(Connection|string $connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) { if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); @@ -64,15 +67,35 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface if ($connOrDsn instanceof Connection) { $this->conn = $connOrDsn; - } elseif (\is_string($connOrDsn)) { - if (!class_exists(DriverManager::class)) { - throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connOrDsn)); - } - $this->conn = DriverManager::getConnection(['url' => $connOrDsn]); } else { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be "%s" or string, "%s" given.', __METHOD__, Connection::class, get_debug_type($connOrDsn))); + if (!class_exists(DriverManager::class)) { + throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".'); + } + if (class_exists(DsnParser::class)) { + $params = (new DsnParser([ + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]))->parse($connOrDsn); + } else { + $params = ['url' => $connOrDsn]; + } + + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $this->conn = DriverManager::getConnection($params, $config); } + $this->maxIdLength = self::MAX_KEY_LENGTH; $this->table = $options['db_table'] ?? $this->table; $this->idCol = $options['db_id_col'] ?? $this->idCol; $this->dataCol = $options['db_data_col'] ?? $this->dataCol; @@ -92,7 +115,7 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface * * @throws DBALException When the table already exists */ - public function createTable() + public function createTable(): void { $schema = new Schema(); $this->addTableToSchema($schema); @@ -103,25 +126,23 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface } /** - * {@inheritdoc} + * @param \Closure $isSameDatabase */ - public function configureSchema(Schema $schema, Connection $forConnection): void + public function configureSchema(Schema $schema, Connection $forConnection/* , \Closure $isSameDatabase */): void { - // only update the schema for this connection - if ($forConnection !== $this->conn) { + if ($schema->hasTable($this->table)) { return; } - if ($schema->hasTable($this->table)) { + $isSameDatabase = 2 < \func_num_args() ? func_get_arg(2) : static fn () => false; + + if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) { return; } $this->addTableToSchema($schema); } - /** - * {@inheritdoc} - */ public function prune(): bool { $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ?"; @@ -136,15 +157,12 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface try { $this->conn->executeStatement($deleteSql, $params, $paramTypes); - } catch (TableNotFoundException $e) { + } catch (TableNotFoundException) { } return true; } - /** - * {@inheritdoc} - */ protected function doFetch(array $ids): iterable { $now = time(); @@ -156,7 +174,7 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface $ids, ], [ ParameterType::INTEGER, - Connection::PARAM_STR_ARRAY, + class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY, ])->iterateNumeric(); foreach ($result as $row) { @@ -174,14 +192,11 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface $expired, ], [ ParameterType::INTEGER, - Connection::PARAM_STR_ARRAY, + class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY, ]); } } - /** - * {@inheritdoc} - */ protected function doHave(string $id): bool { $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = ? AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ?)"; @@ -196,47 +211,34 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface return (bool) $result->fetchOne(); } - /** - * {@inheritdoc} - */ protected function doClear(string $namespace): bool { if ('' === $namespace) { - if ('sqlite' === $this->getPlatformName()) { - $sql = "DELETE FROM $this->table"; - } else { - $sql = "TRUNCATE TABLE $this->table"; - } + $sql = $this->conn->getDatabasePlatform()->getTruncateTableSQL($this->table); } else { $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; } try { $this->conn->executeStatement($sql); - } catch (TableNotFoundException $e) { + } catch (TableNotFoundException) { } return true; } - /** - * {@inheritdoc} - */ protected function doDelete(array $ids): bool { $sql = "DELETE FROM $this->table WHERE $this->idCol IN (?)"; try { - $this->conn->executeStatement($sql, [array_values($ids)], [Connection::PARAM_STR_ARRAY]); - } catch (TableNotFoundException $e) { + $this->conn->executeStatement($sql, [array_values($ids)], [class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY]); + } catch (TableNotFoundException) { } return true; } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { if (!$values = $this->marshaller->marshall($values, $failed)) { return $failed; @@ -278,45 +280,52 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface $lifetime = $lifetime ?: null; try { $stmt = $this->conn->prepare($sql); - } catch (TableNotFoundException $e) { + } catch (TableNotFoundException) { if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { $this->createTable(); } $stmt = $this->conn->prepare($sql); } - // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution. if ('sqlsrv' === $platformName || 'oci' === $platformName) { - $stmt->bindParam(1, $id); - $stmt->bindParam(2, $id); - $stmt->bindParam(3, $data, ParameterType::LARGE_OBJECT); + $bind = static function ($id, $data) use ($stmt) { + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $id); + $stmt->bindValue(3, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(6, $data, ParameterType::LARGE_OBJECT); + }; $stmt->bindValue(4, $lifetime, ParameterType::INTEGER); $stmt->bindValue(5, $now, ParameterType::INTEGER); - $stmt->bindParam(6, $data, ParameterType::LARGE_OBJECT); $stmt->bindValue(7, $lifetime, ParameterType::INTEGER); $stmt->bindValue(8, $now, ParameterType::INTEGER); } elseif (null !== $platformName) { - $stmt->bindParam(1, $id); - $stmt->bindParam(2, $data, ParameterType::LARGE_OBJECT); + $bind = static function ($id, $data) use ($stmt) { + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); + }; $stmt->bindValue(3, $lifetime, ParameterType::INTEGER); $stmt->bindValue(4, $now, ParameterType::INTEGER); } else { - $stmt->bindParam(1, $data, ParameterType::LARGE_OBJECT); $stmt->bindValue(2, $lifetime, ParameterType::INTEGER); $stmt->bindValue(3, $now, ParameterType::INTEGER); - $stmt->bindParam(4, $id); $insertStmt = $this->conn->prepare($insertSql); - $insertStmt->bindParam(1, $id); - $insertStmt->bindParam(2, $data, ParameterType::LARGE_OBJECT); $insertStmt->bindValue(3, $lifetime, ParameterType::INTEGER); $insertStmt->bindValue(4, $now, ParameterType::INTEGER); + + $bind = static function ($id, $data) use ($stmt, $insertStmt) { + $stmt->bindValue(1, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(4, $id); + $insertStmt->bindValue(1, $id); + $insertStmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); + }; } foreach ($values as $id => $data) { + $bind($id, $data); try { $rowCount = $stmt->executeStatement(); - } catch (TableNotFoundException $e) { + } catch (TableNotFoundException) { if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { $this->createTable(); } @@ -325,7 +334,7 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface if (null === $platformName && 0 === $rowCount) { try { $insertStmt->executeStatement(); - } catch (DBALException $e) { + } catch (DBALException) { // A concurrent write won, let it be } } @@ -334,6 +343,22 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface return $failed; } + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ('pgsql' !== $this->platformName ??= $this->getPlatformName()) { + return parent::getId($key); + } + + if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { + $key = rawurlencode($key); + } + + return parent::getId($key); + } + private function getPlatformName(): string { if (isset($this->platformName)) { @@ -342,28 +367,17 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface $platform = $this->conn->getDatabasePlatform(); - switch (true) { - case $platform instanceof \Doctrine\DBAL\Platforms\MySQLPlatform: - case $platform instanceof \Doctrine\DBAL\Platforms\MySQL57Platform: - return $this->platformName = 'mysql'; - - case $platform instanceof \Doctrine\DBAL\Platforms\SqlitePlatform: - return $this->platformName = 'sqlite'; - - case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform: - case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQL94Platform: - return $this->platformName = 'pgsql'; - - case $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform: - return $this->platformName = 'oci'; - - case $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform: - case $platform instanceof \Doctrine\DBAL\Platforms\SQLServer2012Platform: - return $this->platformName = 'sqlsrv'; - - default: - return $this->platformName = \get_class($platform); - } + return $this->platformName = match (true) { + $platform instanceof \Doctrine\DBAL\Platforms\MySQLPlatform, + $platform instanceof \Doctrine\DBAL\Platforms\MySQL57Platform => 'mysql', + $platform instanceof \Doctrine\DBAL\Platforms\SqlitePlatform => 'sqlite', + $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform, + $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQL94Platform => 'pgsql', + $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform => 'oci', + $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform, + $platform instanceof \Doctrine\DBAL\Platforms\SQLServer2012Platform => 'sqlsrv', + default => $platform::class, + }; } private function getServerVersion(): string @@ -372,12 +386,14 @@ class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface return $this->serverVersion; } - $conn = $this->conn->getWrappedConnection(); - if ($conn instanceof ServerInfoAwareConnection) { - return $this->serverVersion = $conn->getServerVersion(); + if ($this->conn instanceof ServerVersionProvider || $this->conn instanceof ServerInfoAwareConnection) { + return $this->serverVersion = $this->conn->getServerVersion(); } - return $this->serverVersion = '0'; + // The condition should be removed once support for DBAL <3.3 is dropped + $conn = method_exists($this->conn, 'getNativeConnection') ? $this->conn->getNativeConnection() : $this->conn->getWrappedConnection(); + + return $this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION); } private function addTableToSchema(Schema $schema): void diff --git a/lib/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/lib/symfony/cache/Adapter/FilesystemTagAwareAdapter.php index afde84375..e78536794 100644 --- a/lib/symfony/cache/Adapter/FilesystemTagAwareAdapter.php +++ b/lib/symfony/cache/Adapter/FilesystemTagAwareAdapter.php @@ -25,6 +25,7 @@ use Symfony\Component\Cache\Traits\FilesystemTrait; class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface { use FilesystemTrait { + prune as private doPrune; doClear as private doClearCache; doSave as private doSaveCache; } @@ -41,10 +42,49 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune $this->init($namespace, $directory); } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + public function prune(): bool + { + $ok = $this->doPrune(); + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + $dir .= \DIRECTORY_SEPARATOR; + $keepDir = false; + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' === $link || '..' === $link) { + continue; + } + if ('_' !== $dir[-2] && realpath($d.\DIRECTORY_SEPARATOR.$link)) { + $keepDir = true; + } else { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + $keepDir ?: rmdir($d); + } + $keepDir ?: rmdir($dir.$chars[$i]); + } + $keepDir ?: rmdir($dir); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + + protected function doClear(string $namespace): bool { $ok = $this->doClearCache($namespace); @@ -55,9 +95,11 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune set_error_handler(static function () {}); $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + try { foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { - if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) { + if (rename($dir, $renamed = substr_replace($dir, $this->tmpSuffix.'_', -9))) { $dir = $renamed.\DIRECTORY_SEPARATOR; } else { $dir .= \DIRECTORY_SEPARATOR; @@ -90,9 +132,6 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune return $ok; } - /** - * {@inheritdoc} - */ protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array { $failed = $this->doSaveCache($values, $lifetime); @@ -129,9 +168,6 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune return $failed; } - /** - * {@inheritdoc} - */ protected function doDeleteYieldTags(array $ids): iterable { foreach ($ids as $id) { @@ -140,7 +176,7 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune continue; } - if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) { + if (!@unlink($file)) { fclose($h); continue; } @@ -159,22 +195,15 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune try { yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta); - } catch (\Exception $e) { + } catch (\Exception) { yield $id => []; } } fclose($h); - - if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) { - @unlink($file); - } } } - /** - * {@inheritdoc} - */ protected function doDeleteTagRelations(array $tagData): bool { foreach ($tagData as $tagId => $idList) { @@ -187,9 +216,6 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune return true; } - /** - * {@inheritdoc} - */ protected function doInvalidate(array $tagIds): bool { foreach ($tagIds as $tagId) { @@ -197,10 +223,12 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune continue; } + $this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + set_error_handler(static function () {}); try { - if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) { + if (rename($tagFolder, $renamed = substr_replace($tagFolder, $this->tmpSuffix.'_', -10))) { $tagFolder = $renamed.\DIRECTORY_SEPARATOR; } else { $renamed = null; diff --git a/lib/symfony/cache/Adapter/MemcachedAdapter.php b/lib/symfony/cache/Adapter/MemcachedAdapter.php index 5c2933fcf..23fc94d45 100644 --- a/lib/symfony/cache/Adapter/MemcachedAdapter.php +++ b/lib/symfony/cache/Adapter/MemcachedAdapter.php @@ -29,19 +29,11 @@ class MemcachedAdapter extends AbstractAdapter */ private const RESERVED_MEMCACHED = " \n\r\t\v\f\0"; private const RESERVED_PSR6 = '@()\{}/'; + private const MAX_KEY_LENGTH = 250; - protected $maxIdLength = 250; - - private const DEFAULT_CLIENT_OPTIONS = [ - 'persistent_id' => null, - 'username' => null, - 'password' => null, - \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP, - ]; - - private $marshaller; - private $client; - private $lazyClient; + private MarshallerInterface $marshaller; + private \Memcached $client; + private \Memcached $lazyClient; /** * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged. @@ -56,9 +48,11 @@ class MemcachedAdapter extends AbstractAdapter public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { if (!static::isSupported()) { - throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.'); + throw new CacheException('Memcached > 3.1.5 is required.'); } - if ('Memcached' === \get_class($client)) { + $this->maxIdLength = self::MAX_KEY_LENGTH; + + if ('Memcached' === $client::class) { $opt = $client->getOption(\Memcached::OPT_SERIALIZER); if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); @@ -74,9 +68,12 @@ class MemcachedAdapter extends AbstractAdapter $this->marshaller = $marshaller ?? new DefaultMarshaller(); } + /** + * @return bool + */ public static function isSupported() { - return \extension_loaded('memcached') && version_compare(phpversion('memcached'), \PHP_VERSION_ID >= 80100 ? '3.1.6' : '2.2.0', '>='); + return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '3.1.6', '>='); } /** @@ -90,26 +87,21 @@ class MemcachedAdapter extends AbstractAdapter * * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs * - * @return \Memcached - * * @throws \ErrorException When invalid options or servers are provided */ - public static function createConnection($servers, array $options = []) + public static function createConnection(#[\SensitiveParameter] array|string $servers, array $options = []): \Memcached { if (\is_string($servers)) { $servers = [$servers]; - } elseif (!\is_array($servers)) { - throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', get_debug_type($servers))); } if (!static::isSupported()) { - throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.'); + throw new CacheException('Memcached > 3.1.5 is required.'); } - set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); try { - $options += static::DEFAULT_CLIENT_OPTIONS; - $client = new \Memcached($options['persistent_id']); - $username = $options['username']; - $password = $options['password']; + $client = new \Memcached($options['persistent_id'] ?? null); + $username = $options['username'] ?? null; + $password = $options['password'] ?? null; // parse any DSN in $servers foreach ($servers as $i => $dsn) { @@ -117,7 +109,7 @@ class MemcachedAdapter extends AbstractAdapter continue; } if (!str_starts_with($dsn, 'memcached:')) { - throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached:".', $dsn)); + throw new InvalidArgumentException('Invalid Memcached DSN: it does not start with "memcached:".'); } $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { if (!empty($m[2])) { @@ -127,7 +119,7 @@ class MemcachedAdapter extends AbstractAdapter return 'file:'.($m[1] ?? ''); }, $dsn); if (false === $params = parse_url($params)) { - throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + throw new InvalidArgumentException('Invalid Memcached DSN.'); } $query = $hosts = []; if (isset($params['query'])) { @@ -135,7 +127,7 @@ class MemcachedAdapter extends AbstractAdapter if (isset($query['host'])) { if (!\is_array($hosts = $query['host'])) { - throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + throw new InvalidArgumentException('Invalid Memcached DSN: query parameter "host" must be an array.'); } foreach ($hosts as $host => $weight) { if (false === $port = strrpos($host, ':')) { @@ -154,7 +146,7 @@ class MemcachedAdapter extends AbstractAdapter } } if (!isset($params['host']) && !isset($params['path'])) { - throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + throw new InvalidArgumentException('Invalid Memcached DSN: missing host or path.'); } if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) { $params['weight'] = $m[1]; @@ -199,7 +191,7 @@ class MemcachedAdapter extends AbstractAdapter $options[\constant('Memcached::OPT_'.$name)] = $value; } } - $client->setOptions($options); + $client->setOptions($options + [\Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP]); // set client's servers, taking care of persistent connections if (!$client->isPristine()) { @@ -239,10 +231,7 @@ class MemcachedAdapter extends AbstractAdapter } } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { if (!$values = $this->marshaller->marshall($values, $failed)) { return $failed; @@ -260,10 +249,7 @@ class MemcachedAdapter extends AbstractAdapter return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false; } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { try { $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids); @@ -281,18 +267,12 @@ class MemcachedAdapter extends AbstractAdapter } } - /** - * {@inheritdoc} - */ - protected function doHave(string $id) + protected function doHave(string $id): bool { return false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); } - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) + protected function doDelete(array $ids): bool { $ok = true; $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids); @@ -305,15 +285,12 @@ class MemcachedAdapter extends AbstractAdapter return $ok; } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + protected function doClear(string $namespace): bool { return '' === $namespace && $this->getClient()->flush(); } - private function checkResultCode($result) + private function checkResultCode(mixed $result): mixed { $code = $this->client->getResultCode(); @@ -326,7 +303,7 @@ class MemcachedAdapter extends AbstractAdapter private function getClient(): \Memcached { - if ($this->client) { + if (isset($this->client)) { return $this->client; } diff --git a/lib/symfony/cache/Adapter/NullAdapter.php b/lib/symfony/cache/Adapter/NullAdapter.php index 15f7f8c45..07c7af816 100644 --- a/lib/symfony/cache/Adapter/NullAdapter.php +++ b/lib/symfony/cache/Adapter/NullAdapter.php @@ -20,11 +20,11 @@ use Symfony\Contracts\Cache\CacheInterface; */ class NullAdapter implements AdapterInterface, CacheInterface { - private static $createCacheItem; + private static \Closure $createCacheItem; public function __construct() { - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + self::$createCacheItem ??= \Closure::bind( static function ($key) { $item = new CacheItem(); $item->key = $key; @@ -37,105 +37,58 @@ class NullAdapter implements AdapterInterface, CacheInterface ); } - /** - * {@inheritdoc} - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed { $save = true; return $callback((self::$createCacheItem)($key), $save); } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { return (self::$createCacheItem)($key); } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { return $this->generateItems($keys); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { return false; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { return true; } - /** - * {@inheritdoc} - */ public function delete(string $key): bool { return $this->deleteItem($key); diff --git a/lib/symfony/cache/Adapter/ParameterNormalizer.php b/lib/symfony/cache/Adapter/ParameterNormalizer.php index e33ae9f46..a6896402f 100644 --- a/lib/symfony/cache/Adapter/ParameterNormalizer.php +++ b/lib/symfony/cache/Adapter/ParameterNormalizer.php @@ -27,7 +27,7 @@ final class ParameterNormalizer } try { - return \DateTime::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp(); + return \DateTimeImmutable::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp(); } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('Cannot parse date interval "%s".', $duration), 0, $e); } diff --git a/lib/symfony/cache/Adapter/PdoAdapter.php b/lib/symfony/cache/Adapter/PdoAdapter.php index 5d1072443..815c622b0 100644 --- a/lib/symfony/cache/Adapter/PdoAdapter.php +++ b/lib/symfony/cache/Adapter/PdoAdapter.php @@ -11,10 +11,6 @@ namespace Symfony\Component\Cache\Adapter; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Schema\Schema; -use Psr\Cache\CacheItemInterface; -use Psr\Log\LoggerInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\Marshaller\MarshallerInterface; @@ -22,24 +18,22 @@ use Symfony\Component\Cache\PruneableInterface; class PdoAdapter extends AbstractAdapter implements PruneableInterface { - protected $maxIdLength = 255; + private const MAX_KEY_LENGTH = 255; - private $marshaller; - private $conn; - private $dsn; - private $driver; - private $serverVersion; - private $table = 'cache_items'; - private $idCol = 'item_id'; - private $dataCol = 'item_data'; - private $lifetimeCol = 'item_lifetime'; - private $timeCol = 'item_time'; - private $username = ''; - private $password = ''; - private $connectionOptions = []; - private $namespace; - - private $dbalAdapter; + private MarshallerInterface $marshaller; + private \PDO $conn; + private string $dsn; + private string $driver; + private string $serverVersion; + private string $table = 'cache_items'; + private string $idCol = 'item_id'; + private string $dataCol = 'item_data'; + private string $lifetimeCol = 'item_lifetime'; + private string $timeCol = 'item_time'; + private ?string $username = null; + private ?string $password = null; + private array $connectionOptions = []; + private string $namespace; /** * You can either pass an existing database connection as PDO instance or @@ -56,19 +50,14 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface * * db_password: The password when lazy-connect [default: ''] * * db_connection_options: An array of driver-specific connection options [default: []] * - * @param \PDO|string $connOrDsn - * * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION * @throws InvalidArgumentException When namespace contains invalid characters */ - public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) + public function __construct(#[\SensitiveParameter] \PDO|string $connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) { - if ($connOrDsn instanceof Connection || (\is_string($connOrDsn) && str_contains($connOrDsn, '://'))) { - trigger_deprecation('symfony/cache', '5.4', 'Usage of a DBAL Connection with "%s" is deprecated and will be removed in symfony 6.0. Use "%s" instead.', __CLASS__, DoctrineDbalAdapter::class); - $this->dbalAdapter = new DoctrineDbalAdapter($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller); - - return; + if (\is_string($connOrDsn) && str_contains($connOrDsn, '://')) { + throw new InvalidArgumentException(sprintf('Usage of Doctrine DBAL URL with "%s" is not supported. Use a PDO DSN or "%s" instead.', __CLASS__, DoctrineDbalAdapter::class)); } if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { @@ -81,12 +70,11 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface } $this->conn = $connOrDsn; - } elseif (\is_string($connOrDsn)) { - $this->dsn = $connOrDsn; } else { - throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, get_debug_type($connOrDsn))); + $this->dsn = $connOrDsn; } + $this->maxIdLength = self::MAX_KEY_LENGTH; $this->table = $options['db_table'] ?? $this->table; $this->idCol = $options['db_id_col'] ?? $this->idCol; $this->dataCol = $options['db_data_col'] ?? $this->dataCol; @@ -101,235 +89,38 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface parent::__construct($namespace, $defaultLifetime); } - /** - * {@inheritDoc} - */ - public function getItem($key) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->getItem($key); - } - - return parent::getItem($key); - } - - /** - * {@inheritDoc} - */ - public function getItems(array $keys = []) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->getItems($keys); - } - - return parent::getItems($keys); - } - - /** - * {@inheritDoc} - */ - public function hasItem($key) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->hasItem($key); - } - - return parent::hasItem($key); - } - - /** - * {@inheritDoc} - */ - public function deleteItem($key) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->deleteItem($key); - } - - return parent::deleteItem($key); - } - - /** - * {@inheritDoc} - */ - public function deleteItems(array $keys) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->deleteItems($keys); - } - - return parent::deleteItems($keys); - } - - /** - * {@inheritDoc} - */ - public function clear(string $prefix = '') - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->clear($prefix); - } - - return parent::clear($prefix); - } - - /** - * {@inheritDoc} - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->get($key, $callback, $beta, $metadata); - } - - return parent::get($key, $callback, $beta, $metadata); - } - - /** - * {@inheritDoc} - */ - public function delete(string $key): bool - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->delete($key); - } - - return parent::delete($key); - } - - /** - * {@inheritDoc} - */ - public function save(CacheItemInterface $item) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->save($item); - } - - return parent::save($item); - } - - /** - * {@inheritDoc} - */ - public function saveDeferred(CacheItemInterface $item) - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->saveDeferred($item); - } - - return parent::saveDeferred($item); - } - - /** - * {@inheritDoc} - */ - public function setLogger(LoggerInterface $logger): void - { - if (isset($this->dbalAdapter)) { - $this->dbalAdapter->setLogger($logger); - - return; - } - - parent::setLogger($logger); - } - - /** - * {@inheritDoc} - */ - public function commit() - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->commit(); - } - - return parent::commit(); - } - - /** - * {@inheritDoc} - */ - public function reset() - { - if (isset($this->dbalAdapter)) { - $this->dbalAdapter->reset(); - - return; - } - - parent::reset(); - } - /** * Creates the table to store cache items which can be called once for setup. * * Cache ID are saved in a column of maximum length 255. Cache data is * saved in a BLOB. * + * @return void + * * @throws \PDOException When the table already exists * @throws \DomainException When an unsupported PDO driver is used */ public function createTable() { - if (isset($this->dbalAdapter)) { - $this->dbalAdapter->createTable(); + $sql = match ($driver = $this->getDriver()) { + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like Ă© == e + 'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB", + 'sqlite' => "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + default => throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $driver)), + }; - return; - } - - // connect if we are not yet - $conn = $this->getConnection(); - - switch ($this->driver) { - case 'mysql': - // We use varbinary for the ID column because it prevents unwanted conversions: - // - character set conversions between server and client - // - trailing space removal - // - case-insensitivity - // - language processing like Ă© == e - $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; - break; - case 'sqlite': - $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; - break; - case 'pgsql': - $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; - break; - case 'oci': - $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; - break; - case 'sqlsrv': - $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; - break; - default: - throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver)); - } - - $conn->exec($sql); + $this->getConnection()->exec($sql); } - /** - * Adds the Table to the Schema if the adapter uses this Connection. - * - * @deprecated since symfony/cache 5.4 use DoctrineDbalAdapter instead - */ - public function configureSchema(Schema $schema, Connection $forConnection): void + public function prune(): bool { - if (isset($this->dbalAdapter)) { - $this->dbalAdapter->configureSchema($schema, $forConnection); - } - } - - /** - * {@inheritdoc} - */ - public function prune() - { - if (isset($this->dbalAdapter)) { - return $this->dbalAdapter->prune(); - } - $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time"; if ('' !== $this->namespace) { @@ -340,7 +131,7 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface try { $delete = $connection->prepare($deleteSql); - } catch (\PDOException $e) { + } catch (\PDOException) { return true; } $delete->bindValue(':time', time(), \PDO::PARAM_INT); @@ -350,15 +141,12 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface } try { return $delete->execute(); - } catch (\PDOException $e) { + } catch (\PDOException) { return true; } } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { $connection = $this->getConnection(); @@ -401,10 +189,7 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface } } - /** - * {@inheritdoc} - */ - protected function doHave(string $id) + protected function doHave(string $id): bool { $connection = $this->getConnection(); @@ -418,15 +203,12 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface return (bool) $stmt->fetchColumn(); } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + protected function doClear(string $namespace): bool { $conn = $this->getConnection(); if ('' === $namespace) { - if ('sqlite' === $this->driver) { + if ('sqlite' === $this->getDriver()) { $sql = "DELETE FROM $this->table"; } else { $sql = "TRUNCATE TABLE $this->table"; @@ -437,32 +219,26 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface try { $conn->exec($sql); - } catch (\PDOException $e) { + } catch (\PDOException) { } return true; } - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) + protected function doDelete(array $ids): bool { $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)"; try { $stmt = $this->getConnection()->prepare($sql); $stmt->execute(array_values($ids)); - } catch (\PDOException $e) { + } catch (\PDOException) { } return true; } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { if (!$values = $this->marshaller->marshall($values, $failed)) { return $failed; @@ -470,7 +246,7 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface $conn = $this->getConnection(); - $driver = $this->driver; + $driver = $this->getDriver(); $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; switch (true) { @@ -507,7 +283,7 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface try { $stmt = $conn->prepare($sql); } catch (\PDOException $e) { - if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { $this->createTable(); } $stmt = $conn->prepare($sql); @@ -542,7 +318,7 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface try { $stmt->execute(); } catch (\PDOException $e) { - if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { $this->createTable(); } $stmt->execute(); @@ -550,7 +326,7 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface if (null === $driver && !$stmt->rowCount()) { try { $insertStmt->execute(); - } catch (\PDOException $e) { + } catch (\PDOException) { // A concurrent write won, let it be } } @@ -559,25 +335,54 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface return $failed; } + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ('pgsql' !== $this->getDriver()) { + return parent::getId($key); + } + + if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { + $key = rawurlencode($key); + } + + return parent::getId($key); + } + private function getConnection(): \PDO { - if (null === $this->conn) { + if (!isset($this->conn)) { $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions); $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); } - if (null === $this->driver) { - $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME); - } return $this->conn; } + private function getDriver(): string + { + return $this->driver ??= $this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + private function getServerVersion(): string { - if (null === $this->serverVersion) { - $this->serverVersion = $this->conn->getAttribute(\PDO::ATTR_SERVER_VERSION); - } + return $this->serverVersion ??= $this->getConnection()->getAttribute(\PDO::ATTR_SERVER_VERSION); + } - return $this->serverVersion; + private function isTableMissing(\PDOException $exception): bool + { + $driver = $this->getDriver(); + $code = $exception->getCode(); + + return match ($driver) { + 'pgsql' => '42P01' === $code, + 'sqlite' => str_contains($exception->getMessage(), 'no such table:'), + 'oci' => 942 === $code, + 'sqlsrv' => 208 === $code, + 'mysql' => 1146 === $code, + default => false, + }; } } diff --git a/lib/symfony/cache/Adapter/PhpArrayAdapter.php b/lib/symfony/cache/Adapter/PhpArrayAdapter.php index 8c8fb9164..f6decd848 100644 --- a/lib/symfony/cache/Adapter/PhpArrayAdapter.php +++ b/lib/symfony/cache/Adapter/PhpArrayAdapter.php @@ -34,12 +34,12 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte use ContractsTrait; use ProxyTrait; - private $file; - private $keys; - private $values; + private string $file; + private array $keys; + private array $values; - private static $createCacheItem; - private static $valuesCache = []; + private static \Closure $createCacheItem; + private static array $valuesCache = []; /** * @param string $file The PHP file were values are cached @@ -49,7 +49,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte { $this->file = $file; $this->pool = $fallbackPool; - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + self::$createCacheItem ??= \Closure::bind( static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; @@ -68,10 +68,8 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte * * @param string $file The PHP file were values are cached * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit - * - * @return CacheItemPoolInterface */ - public static function create(string $file, CacheItemPoolInterface $fallbackPool) + public static function create(string $file, CacheItemPoolInterface $fallbackPool): CacheItemPoolInterface { if (!$fallbackPool instanceof AdapterInterface) { $fallbackPool = new ProxyAdapter($fallbackPool); @@ -80,12 +78,9 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte return new static($file, $fallbackPool); } - /** - * {@inheritdoc} - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed { - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } if (!isset($this->keys[$key])) { @@ -105,7 +100,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte if ($value instanceof \Closure) { return $value(); } - } catch (\Throwable $e) { + } catch (\Throwable) { unset($this->keys[$key]); goto get_from_pool; } @@ -113,15 +108,12 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte return $value; } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); } - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } if (!isset($this->keys[$key])) { @@ -136,7 +128,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte } elseif ($value instanceof \Closure) { try { $value = $value(); - } catch (\Throwable $e) { + } catch (\Throwable) { $value = null; $isHit = false; } @@ -145,63 +137,45 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte return (self::$createCacheItem)($key, $value, $isHit); } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { foreach ($keys as $key) { if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); } } - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } return $this->generateItems($keys); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); } - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } return isset($this->keys[$key]) || $this->pool->hasItem($key); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); } - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } return !isset($this->keys[$key]) && $this->pool->deleteItem($key); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { $deleted = true; $fallbackKeys = []; @@ -217,7 +191,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte $fallbackKeys[] = $key; } } - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } @@ -228,50 +202,30 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte return $deleted; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } return !isset($this->keys[$item->getKey()]) && $this->pool->save($item); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { - if (null === $this->values) { + if (!isset($this->values)) { $this->initialize(); } return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { return $this->pool->commit(); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { $this->keys = $this->values = []; @@ -292,7 +246,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte * * @return string[] A list of classes to preload on PHP 7.4+ */ - public function warmUp(array $values) + public function warmUp(array $values): array { if (file_exists($this->file)) { if (!is_file($this->file)) { @@ -355,7 +309,7 @@ EOF; $value = str_replace("\n", "\n ", $value); $value = "static function () {\n return {$value};\n}"; } - $hash = hash('md5', $value); + $hash = hash('xxh128', $value); if (null === $id = $dumpedMap[$hash] ?? null) { $id = $dumpedMap[$hash] = \count($dumpedMap); @@ -384,7 +338,7 @@ EOF; /** * Load the cache file. */ - private function initialize() + private function initialize(): void { if (isset(self::$valuesCache[$this->file])) { $values = self::$valuesCache[$this->file]; @@ -417,7 +371,7 @@ EOF; } elseif ($value instanceof \Closure) { try { yield $key => $f($key, $value(), true); - } catch (\Throwable $e) { + } catch (\Throwable) { yield $key => $f($key, null, false); } } else { diff --git a/lib/symfony/cache/Adapter/PhpFilesAdapter.php b/lib/symfony/cache/Adapter/PhpFilesAdapter.php index 2fb08375a..41ea8604c 100644 --- a/lib/symfony/cache/Adapter/PhpFilesAdapter.php +++ b/lib/symfony/cache/Adapter/PhpFilesAdapter.php @@ -29,13 +29,13 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface doDelete as private doCommonDelete; } - private $includeHandler; - private $appendOnly; - private $values = []; - private $files = []; + private \Closure $includeHandler; + private bool $appendOnly; + private array $values = []; + private array $files = []; - private static $startTime; - private static $valuesCache = []; + private static int $startTime; + private static array $valuesCache = []; /** * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire. @@ -46,7 +46,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false) { $this->appendOnly = $appendOnly; - self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); + self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); parent::__construct('', $defaultLifetime); $this->init($namespace, $directory); $this->includeHandler = static function ($type, $msg, $file, $line) { @@ -54,17 +54,17 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface }; } - public static function isSupported() - { - self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); - - return \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN)); - } - /** * @return bool */ - public function prune() + public static function isSupported() + { + self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); + + return \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL)); + } + + public function prune(): bool { $time = time(); $pruned = true; @@ -82,7 +82,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface } if ($time >= $expiresAt) { - $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned; + $pruned = ($this->doUnlink($file) || !file_exists($file)) && $pruned; } } } finally { @@ -92,10 +92,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface return $pruned; } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { if ($this->appendOnly) { $now = 0; @@ -138,7 +135,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface foreach ($missingIds as $k => $id) { try { - $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + $file = $this->files[$id] ??= $this->getFile($id); if (isset(self::$valuesCache[$file])) { [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; @@ -168,10 +165,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface goto begin; } - /** - * {@inheritdoc} - */ - protected function doHave(string $id) + protected function doHave(string $id): bool { if ($this->appendOnly && isset($this->values[$id])) { return true; @@ -179,7 +173,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface set_error_handler($this->includeHandler); try { - $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + $file = $this->files[$id] ??= $this->getFile($id); $getExpiry = true; if (isset(self::$valuesCache[$file])) { @@ -193,7 +187,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface } elseif ($this->appendOnly) { $value = new LazyValue($file); } - } catch (\ErrorException $e) { + } catch (\ErrorException) { return false; } finally { restore_error_handler(); @@ -208,10 +202,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface return $now < $expiresAt; } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { $ok = true; $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX'; @@ -245,7 +236,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface if ($isStaticValue) { $value = "return [{$expiry}, {$value}];"; } elseif ($this->appendOnly) { - $value = "return [{$expiry}, static function () { return {$value}; }];"; + $value = "return [{$expiry}, static fn () => {$value}];"; } else { // We cannot use a closure here because of https://bugs.php.net/76982 $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value); @@ -270,20 +261,14 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface return $ok; } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + protected function doClear(string $namespace): bool { $this->values = []; return $this->doCommonClear($namespace); } - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) + protected function doDelete(array $ids): bool { foreach ($ids as $id) { unset($this->values[$id]); @@ -292,6 +277,9 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface return $this->doCommonDelete($ids); } + /** + * @return bool + */ protected function doUnlink(string $file) { unset(self::$valuesCache[$file]); @@ -321,7 +309,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface */ class LazyValue { - public $file; + public string $file; public function __construct(string $file) { diff --git a/lib/symfony/cache/Adapter/ProxyAdapter.php b/lib/symfony/cache/Adapter/ProxyAdapter.php index c715cade5..88fccde4a 100644 --- a/lib/symfony/cache/Adapter/ProxyAdapter.php +++ b/lib/symfony/cache/Adapter/ProxyAdapter.php @@ -28,25 +28,25 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa use ContractsTrait; use ProxyTrait; - private $namespace = ''; - private $namespaceLen; - private $poolHash; - private $defaultLifetime; + private string $namespace = ''; + private int $namespaceLen; + private string $poolHash; + private int $defaultLifetime; - private static $createCacheItem; - private static $setInnerItem; + private static \Closure $createCacheItem; + private static \Closure $setInnerItem; public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) { $this->pool = $pool; - $this->poolHash = $poolHash = spl_object_hash($pool); + $this->poolHash = spl_object_hash($pool); if ('' !== $namespace) { \assert('' !== CacheItem::validateKey($namespace)); $this->namespace = $namespace; } $this->namespaceLen = \strlen($namespace); $this->defaultLifetime = $defaultLifetime; - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + self::$createCacheItem ??= \Closure::bind( static function ($key, $innerItem, $poolHash) { $item = new CacheItem(); $item->key = $key; @@ -55,20 +55,12 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $item; } - $item->value = $v = $innerItem->get(); + $item->value = $innerItem->get(); $item->isHit = $innerItem->isHit(); $item->innerItem = $innerItem; $item->poolHash = $poolHash; - // Detect wrapped values that encode for their expiry and creation duration - // For compactness, these values are packed in the key of an array using - // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F - if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { - $item->value = $v[$k]; - $v = unpack('Ve/Nc', substr($k, 1, -1)); - $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; - $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; - } elseif ($innerItem instanceof CacheItem) { + if (!$item->unpack() && $innerItem instanceof CacheItem) { $item->metadata = $innerItem->metadata; } $innerItem->set(null); @@ -78,31 +70,17 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa null, CacheItem::class ); - self::$setInnerItem ?? self::$setInnerItem = \Closure::bind( - /** - * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix - */ - static function (CacheItemInterface $innerItem, array $item) { - // Tags are stored separately, no need to account for them when considering this item's newly set metadata - if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) { - unset($metadata[CacheItem::METADATA_TAGS]); - } - if ($metadata) { - // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators - $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]]; - } - $innerItem->set($item["\0*\0value"]); - $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $item["\0*\0expiry"])) : null); + self::$setInnerItem ??= \Closure::bind( + static function (CacheItemInterface $innerItem, CacheItem $item, $expiry = null) { + $innerItem->set($item->pack()); + $innerItem->expiresAt(($expiry ?? $item->expiry) ? \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $expiry ?? $item->expiry)) : null); }, null, CacheItem::class ); } - /** - * {@inheritdoc} - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed { if (!$this->pool instanceof CacheInterface) { return $this->doGet($this, $key, $callback, $beta, $metadata); @@ -111,26 +89,20 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) { $item = (self::$createCacheItem)($key, $innerItem, $this->poolHash); $item->set($value = $callback($item, $save)); - (self::$setInnerItem)($innerItem, (array) $item); + (self::$setInnerItem)($innerItem, $item); return $value; }, $beta, $metadata); } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { $item = $this->pool->getItem($this->getId($key)); return (self::$createCacheItem)($key, $item, $this->poolHash); } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { if ($this->namespaceLen) { foreach ($keys as $i => $key) { @@ -141,22 +113,12 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $this->generateItems($this->pool->getItems($keys)); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { return $this->pool->hasItem($this->getId($key)); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { if ($this->pool instanceof AdapterInterface) { return $this->pool->clear($this->namespace.$prefix); @@ -165,22 +127,12 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $this->pool->clear(); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { return $this->pool->deleteItem($this->getId($key)); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { if ($this->namespaceLen) { foreach ($keys as $i => $key) { @@ -191,57 +143,43 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa return $this->pool->deleteItems($keys); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { return $this->doSave($item, __FUNCTION__); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { return $this->doSave($item, __FUNCTION__); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { return $this->pool->commit(); } - private function doSave(CacheItemInterface $item, string $method) + private function doSave(CacheItemInterface $item, string $method): bool { if (!$item instanceof CacheItem) { return false; } - $item = (array) $item; - if (null === $item["\0*\0expiry"] && 0 < $this->defaultLifetime) { - $item["\0*\0expiry"] = microtime(true) + $this->defaultLifetime; + $castItem = (array) $item; + + if (null === $castItem["\0*\0expiry"] && 0 < $this->defaultLifetime) { + $castItem["\0*\0expiry"] = microtime(true) + $this->defaultLifetime; } - if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) { - $innerItem = $item["\0*\0innerItem"]; + if ($castItem["\0*\0poolHash"] === $this->poolHash && $castItem["\0*\0innerItem"]) { + $innerItem = $castItem["\0*\0innerItem"]; } elseif ($this->pool instanceof AdapterInterface) { // this is an optimization specific for AdapterInterface implementations // so we can save a round-trip to the backend by just creating a new item - $innerItem = (self::$createCacheItem)($this->namespace.$item["\0*\0key"], null, $this->poolHash); + $innerItem = (self::$createCacheItem)($this->namespace.$castItem["\0*\0key"], null, $this->poolHash); } else { - $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]); + $innerItem = $this->pool->getItem($this->namespace.$castItem["\0*\0key"]); } - (self::$setInnerItem)($innerItem, $item); + (self::$setInnerItem)($innerItem, $item, $castItem["\0*\0expiry"]); return $this->pool->$method($innerItem); } @@ -259,7 +197,7 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa } } - private function getId($key): string + private function getId(mixed $key): string { \assert('' !== CacheItem::validateKey($key)); diff --git a/lib/symfony/cache/Adapter/Psr16Adapter.php b/lib/symfony/cache/Adapter/Psr16Adapter.php index a56aa3930..a72b037b0 100644 --- a/lib/symfony/cache/Adapter/Psr16Adapter.php +++ b/lib/symfony/cache/Adapter/Psr16Adapter.php @@ -30,7 +30,7 @@ class Psr16Adapter extends AbstractAdapter implements PruneableInterface, Resett */ protected const NS_SEPARATOR = '_'; - private $miss; + private object $miss; public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0) { @@ -40,10 +40,7 @@ class Psr16Adapter extends AbstractAdapter implements PruneableInterface, Resett $this->miss = new \stdClass(); } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) { if ($this->miss !== $value) { @@ -52,34 +49,22 @@ class Psr16Adapter extends AbstractAdapter implements PruneableInterface, Resett } } - /** - * {@inheritdoc} - */ - protected function doHave(string $id) + protected function doHave(string $id): bool { return $this->pool->has($id); } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + protected function doClear(string $namespace): bool { return $this->pool->clear(); } - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) + protected function doDelete(array $ids): bool { return $this->pool->deleteMultiple($ids); } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); } diff --git a/lib/symfony/cache/Adapter/RedisAdapter.php b/lib/symfony/cache/Adapter/RedisAdapter.php index eb5950e53..d8e37b1d7 100644 --- a/lib/symfony/cache/Adapter/RedisAdapter.php +++ b/lib/symfony/cache/Adapter/RedisAdapter.php @@ -12,20 +12,13 @@ namespace Symfony\Component\Cache\Adapter; use Symfony\Component\Cache\Marshaller\MarshallerInterface; -use Symfony\Component\Cache\Traits\RedisClusterProxy; -use Symfony\Component\Cache\Traits\RedisProxy; use Symfony\Component\Cache\Traits\RedisTrait; class RedisAdapter extends AbstractAdapter { use RedisTrait; - /** - * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client - * @param string $namespace The default namespace - * @param int $defaultLifetime The default lifetime - */ - public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { $this->init($redis, $namespace, $defaultLifetime, $marshaller); } diff --git a/lib/symfony/cache/Adapter/RedisTagAwareAdapter.php b/lib/symfony/cache/Adapter/RedisTagAwareAdapter.php index 865491e19..a3ef9f109 100644 --- a/lib/symfony/cache/Adapter/RedisTagAwareAdapter.php +++ b/lib/symfony/cache/Adapter/RedisTagAwareAdapter.php @@ -16,14 +16,13 @@ use Predis\Connection\Aggregate\PredisCluster; use Predis\Connection\Aggregate\ReplicationInterface; use Predis\Response\ErrorInterface; use Predis\Response\Status; +use Relay\Relay; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Exception\LogicException; use Symfony\Component\Cache\Marshaller\DeflateMarshaller; use Symfony\Component\Cache\Marshaller\MarshallerInterface; use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; -use Symfony\Component\Cache\Traits\RedisClusterProxy; -use Symfony\Component\Cache\Traits\RedisProxy; use Symfony\Component\Cache\Traits\RedisTrait; /** @@ -56,28 +55,24 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter private const DEFAULT_CACHE_TTL = 8640000; /** - * @var string|null detected eviction policy used on Redis server + * detected eviction policy used on Redis server. */ - private $redisEvictionPolicy; - private $namespace; + private string $redisEvictionPolicy; + private string $namespace; - /** - * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client - * @param string $namespace The default namespace - * @param int $defaultLifetime The default lifetime - */ - public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); } - if (\defined('Redis::OPT_COMPRESSION') && ($redis instanceof \Redis || $redis instanceof \RedisArray || $redis instanceof \RedisCluster)) { - $compression = $redis->getOption(\Redis::OPT_COMPRESSION); + $isRelay = $redis instanceof Relay; + if ($isRelay || \defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { + $compression = $redis->getOption($isRelay ? Relay::OPT_COMPRESSION : \Redis::OPT_COMPRESSION); foreach (\is_array($compression) ? $compression : [$compression] as $c) { - if (\Redis::COMPRESSION_NONE !== $c) { - throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); + if ($isRelay ? Relay::COMPRESSION_NONE : \Redis::COMPRESSION_NONE !== $c) { + throw new InvalidArgumentException(sprintf('redis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); } } } @@ -86,9 +81,6 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter $this->namespace = $namespace; } - /** - * {@inheritdoc} - */ protected function doSave(array $values, int $lifetime, array $addTagData = [], array $delTagData = []): array { $eviction = $this->getRedisEvictionPolicy(); @@ -140,9 +132,6 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter return $failed; } - /** - * {@inheritdoc} - */ protected function doDeleteYieldTags(array $ids): iterable { $lua = <<<'EOLUA' @@ -167,7 +156,7 @@ EOLUA; }); foreach ($results as $id => $result) { - if ($result instanceof \RedisException || $result instanceof ErrorInterface) { + if ($result instanceof \RedisException || $result instanceof \Relay\Exception || $result instanceof ErrorInterface) { CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]); continue; @@ -175,15 +164,12 @@ EOLUA; try { yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result); - } catch (\Exception $e) { + } catch (\Exception) { yield $id => []; } } } - /** - * {@inheritdoc} - */ protected function doDeleteTagRelations(array $tagData): bool { $results = $this->pipeline(static function () use ($tagData) { @@ -199,16 +185,13 @@ EOLUA; return true; } - /** - * {@inheritdoc} - */ protected function doInvalidate(array $tagIds): bool { // This script scans the set of items linked to tag: it empties the set // and removes the linked items. When the set is still not empty after // the scan, it means we're in cluster mode and that the linked items // are on other nodes: we move the links to a temporary set and we - // gargage collect that set from the client side. + // garbage collect that set from the client side. $lua = <<<'EOLUA' redis.replicate_commands() @@ -240,7 +223,7 @@ EOLUA; $results = $this->pipeline(function () use ($tagIds, $lua) { if ($this->redis instanceof \Predis\ClientInterface) { $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; - } elseif (\is_array($prefix = $this->redis->getOption(\Redis::OPT_PREFIX) ?? '')) { + } elseif (\is_array($prefix = $this->redis->getOption($this->redis instanceof Relay ? Relay::OPT_PREFIX : \Redis::OPT_PREFIX) ?? '')) { $prefix = current($prefix); } @@ -261,7 +244,7 @@ EOLUA; $success = true; foreach ($results as $id => $values) { - if ($values instanceof \RedisException || $values instanceof ErrorInterface) { + if ($values instanceof \RedisException || $values instanceof \Relay\Exception || $values instanceof ErrorInterface) { CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]); $success = false; @@ -297,7 +280,7 @@ EOLUA; private function getRedisEvictionPolicy(): string { - if (null !== $this->redisEvictionPolicy) { + if (isset($this->redisEvictionPolicy)) { return $this->redisEvictionPolicy; } @@ -311,13 +294,13 @@ EOLUA; foreach ($hosts as $host) { $info = $host->info('Memory'); - if ($info instanceof ErrorInterface) { + if (false === $info || null === $info || $info instanceof ErrorInterface) { continue; } $info = $info['Memory'] ?? $info; - return $this->redisEvictionPolicy = $info['maxmemory_policy']; + return $this->redisEvictionPolicy = $info['maxmemory_policy'] ?? ''; } return $this->redisEvictionPolicy = ''; diff --git a/lib/symfony/cache/Adapter/TagAwareAdapter.php b/lib/symfony/cache/Adapter/TagAwareAdapter.php index ff22e5a8a..187539acc 100644 --- a/lib/symfony/cache/Adapter/TagAwareAdapter.php +++ b/lib/symfony/cache/Adapter/TagAwareAdapter.php @@ -19,70 +19,73 @@ use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\ContractsTrait; -use Symfony\Component\Cache\Traits\ProxyTrait; use Symfony\Contracts\Cache\TagAwareCacheInterface; /** + * Implements simple and robust tag-based invalidation suitable for use with volatile caches. + * + * This adapter works by storing a version for each tags. When saving an item, it is stored together with its tags and + * their corresponding versions. When retrieving an item, those tag versions are compared to the current version of + * each tags. Invalidation is achieved by deleting tags, thereby ensuring that their versions change even when the + * storage is out of space. When versions of non-existing tags are requested for item commits, this adapter assigns a + * new random version to them. + * * @author Nicolas Grekas + * @author Sergey Belyshkin */ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface { use ContractsTrait; use LoggerAwareTrait; - use ProxyTrait; - public const TAGS_PREFIX = "\0tags\0"; + public const TAGS_PREFIX = "\1tags\1"; - private $deferred = []; - private $tags; - private $knownTagVersions = []; - private $knownTagVersionsTtl; + private array $deferred = []; + private AdapterInterface $pool; + private AdapterInterface $tags; + private array $knownTagVersions = []; + private float $knownTagVersionsTtl; - private static $createCacheItem; - private static $setCacheItemTags; - private static $getTagsByKey; - private static $saveTags; + private static \Closure $setCacheItemTags; + private static \Closure $setTagVersions; + private static \Closure $getTagsByKey; + private static \Closure $saveTags; public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15) { $this->pool = $itemsPool; - $this->tags = $tagsPool ?: $itemsPool; + $this->tags = $tagsPool ?? $itemsPool; $this->knownTagVersionsTtl = $knownTagVersionsTtl; - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( - static function ($key, $value, CacheItem $protoItem) { - $item = new CacheItem(); - $item->key = $key; - $item->value = $value; - $item->expiry = $protoItem->expiry; - $item->poolHash = $protoItem->poolHash; + self::$setCacheItemTags ??= \Closure::bind( + static function (array $items, array $itemTags) { + foreach ($items as $key => $item) { + $item->isTaggable = true; - return $item; - }, - null, - CacheItem::class - ); - self::$setCacheItemTags ?? self::$setCacheItemTags = \Closure::bind( - static function (CacheItem $item, $key, array &$itemTags) { - $item->isTaggable = true; - if (!$item->isHit) { - return $item; - } - if (isset($itemTags[$key])) { - foreach ($itemTags[$key] as $tag => $version) { - $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag; + if (isset($itemTags[$key])) { + $tags = array_keys($itemTags[$key]); + $item->metadata[CacheItem::METADATA_TAGS] = array_combine($tags, $tags); + } else { + $item->value = null; + $item->isHit = false; + $item->metadata = []; } - unset($itemTags[$key]); - } else { - $item->value = null; - $item->isHit = false; } - return $item; + return $items; }, null, CacheItem::class ); - self::$getTagsByKey ?? self::$getTagsByKey = \Closure::bind( + self::$setTagVersions ??= \Closure::bind( + static function (array $items, array $tagVersions) { + foreach ($items as $item) { + $item->newMetadata[CacheItem::METADATA_TAGS] = array_intersect_key($tagVersions, $item->newMetadata[CacheItem::METADATA_TAGS] ?? []); + } + }, + null, + CacheItem::class + ); + self::$getTagsByKey ??= \Closure::bind( static function ($deferred) { $tagsByKey = []; foreach ($deferred as $key => $item) { @@ -95,7 +98,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac null, CacheItem::class ); - self::$saveTags ?? self::$saveTags = \Closure::bind( + self::$saveTags ??= \Closure::bind( static function (AdapterInterface $tagsAdapter, array $tags) { ksort($tags); @@ -111,10 +114,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac ); } - /** - * {@inheritdoc} - */ - public function invalidateTags(array $tags) + public function invalidateTags(array $tags): bool { $ids = []; foreach ($tags as $tag) { @@ -126,56 +126,19 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac return !$tags || $this->tags->deleteItems($ids); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { - if (\is_string($key) && isset($this->deferred[$key])) { - $this->commit(); - } - - if (!$this->pool->hasItem($key)) { - return false; - } - - $itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key); - - if (!$itemTags->isHit()) { - return false; - } - - if (!$itemTags = $itemTags->get()) { - return true; - } - - foreach ($this->getTagVersions([$itemTags]) as $tag => $version) { - if ($itemTags[$tag] !== $version) { - return false; - } - } - - return true; + return $this->getItem($key)->isHit(); } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { foreach ($this->getItems([$key]) as $item) { return $item; } - - return null; } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { $tagKeys = []; $commit = false; @@ -184,7 +147,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac if ('' !== $key && \is_string($key)) { $commit = $commit || isset($this->deferred[$key]); $key = static::TAGS_PREFIX.$key; - $tagKeys[$key] = $key; + $tagKeys[$key] = $key; // BC with pools populated before v6.1 } } @@ -200,15 +163,38 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac throw $e; } - return $this->generateItems($items, $tagKeys); + $bufferedItems = $itemTags = []; + + foreach ($items as $key => $item) { + if (isset($tagKeys[$key])) { // BC with pools populated before v6.1 + if ($item->isHit()) { + $itemTags[substr($key, \strlen(static::TAGS_PREFIX))] = $item->get() ?: []; + } + continue; + } + + if (null !== $tags = $item->getMetadata()[CacheItem::METADATA_TAGS] ?? null) { + $itemTags[$key] = $tags; + } + + $bufferedItems[$key] = $item; + } + + $tagVersions = $this->getTagVersions($itemTags, false); + foreach ($itemTags as $key => $tags) { + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version) { + unset($itemTags[$key]); + continue 2; + } + } + } + $tagVersions = null; + + return (self::$setCacheItemTags)($bufferedItems, $itemTags); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { if ('' !== $prefix) { foreach ($this->deferred as $key => $item) { @@ -227,38 +213,23 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac return $this->pool->clear(); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { return $this->deleteItems([$key]); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { foreach ($keys as $key) { if ('' !== $key && \is_string($key)) { - $keys[] = static::TAGS_PREFIX.$key; + $keys[] = static::TAGS_PREFIX.$key; // BC with pools populated before v6.1 } } return $this->pool->deleteItems($keys); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { if (!$item instanceof CacheItem) { return false; @@ -268,12 +239,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac return $this->commit(); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { if (!$item instanceof CacheItem) { return false; @@ -283,47 +249,55 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac return true; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { - if (!$this->deferred) { + if (!$items = $this->deferred) { return true; } + $tagVersions = $this->getTagVersions((self::$getTagsByKey)($items), true); + (self::$setTagVersions)($items, $tagVersions); + $ok = true; - foreach ($this->deferred as $key => $item) { - if (!$this->pool->saveDeferred($item)) { + foreach ($items as $key => $item) { + if ($this->pool->saveDeferred($item)) { unset($this->deferred[$key]); + } else { $ok = false; } } + $ok = $this->pool->commit() && $ok; - $items = $this->deferred; - $tagsByKey = (self::$getTagsByKey)($items); - $this->deferred = []; + $tagVersions = array_keys($tagVersions); + (self::$setTagVersions)($items, array_combine($tagVersions, $tagVersions)); - $tagVersions = $this->getTagVersions($tagsByKey); - $f = self::$createCacheItem; + return $ok; + } - foreach ($tagsByKey as $key => $tags) { - $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key])); - } - - return $this->pool->commit() && $ok; + public function prune(): bool + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); } /** - * @return array + * @return void */ - public function __sleep() + public function reset() + { + $this->commit(); + $this->knownTagVersions = []; + $this->pool instanceof ResettableInterface && $this->pool->reset(); + $this->tags instanceof ResettableInterface && $this->tags->reset(); + } + + public function __sleep(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } + /** + * @return void + */ public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); @@ -334,59 +308,19 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac $this->commit(); } - private function generateItems(iterable $items, array $tagKeys): \Generator - { - $bufferedItems = $itemTags = []; - $f = self::$setCacheItemTags; - - foreach ($items as $key => $item) { - if (!$tagKeys) { - yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags); - continue; - } - if (!isset($tagKeys[$key])) { - $bufferedItems[$key] = $item; - continue; - } - - unset($tagKeys[$key]); - - if ($item->isHit()) { - $itemTags[$key] = $item->get() ?: []; - } - - if (!$tagKeys) { - $tagVersions = $this->getTagVersions($itemTags); - - foreach ($itemTags as $key => $tags) { - foreach ($tags as $tag => $version) { - if ($tagVersions[$tag] !== $version) { - unset($itemTags[$key]); - continue 2; - } - } - } - $tagVersions = $tagKeys = null; - - foreach ($bufferedItems as $key => $item) { - yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags); - } - $bufferedItems = null; - } - } - } - - private function getTagVersions(array $tagsByKey) + private function getTagVersions(array $tagsByKey, bool $persistTags): array { $tagVersions = []; - $fetchTagVersions = false; + $fetchTagVersions = $persistTags; foreach ($tagsByKey as $tags) { $tagVersions += $tags; - + if ($fetchTagVersions) { + continue; + } foreach ($tags as $tag => $version) { if ($tagVersions[$tag] !== $version) { - unset($this->knownTagVersions[$tag]); + $fetchTagVersions = true; } } } @@ -399,8 +333,9 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac $tags = []; foreach ($tagVersions as $tag => $version) { $tags[$tag.static::TAGS_PREFIX] = $tag; - if ($fetchTagVersions || ($this->knownTagVersions[$tag][1] ?? null) !== $version || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) { - // reuse previously fetched tag versions up to the ttl + $knownTagVersion = $this->knownTagVersions[$tag] ?? [0, null]; + if ($fetchTagVersions || $now > $knownTagVersion[0] || $knownTagVersion[1] !== $version) { + // reuse previously fetched tag versions until the expiration $fetchTagVersions = true; } } @@ -411,18 +346,26 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac $newTags = []; $newVersion = null; + $expiration = $now + $this->knownTagVersionsTtl; foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) { - if (!$version->isHit()) { - $newTags[$tag] = $version->set($newVersion ?? $newVersion = random_int(\PHP_INT_MIN, \PHP_INT_MAX)); + unset($this->knownTagVersions[$tag = $tags[$tag]]); // update FIFO + if (null !== $tagVersions[$tag] = $version->get()) { + $this->knownTagVersions[$tag] = [$expiration, $tagVersions[$tag]]; + } elseif ($persistTags) { + $newTags[$tag] = $version->set($newVersion ??= random_bytes(6)); + $tagVersions[$tag] = $newVersion; + $this->knownTagVersions[$tag] = [$expiration, $newVersion]; } - $tagVersions[$tag = $tags[$tag]] = $version->get(); - $this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]]; } if ($newTags) { (self::$saveTags)($this->tags, $newTags); } + while ($now > ($this->knownTagVersions[$tag = array_key_first($this->knownTagVersions)][0] ?? \INF)) { + unset($this->knownTagVersions[$tag]); + } + return $tagVersions; } } diff --git a/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php b/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php index afa18d3b5..9242779c1 100644 --- a/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php +++ b/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php @@ -25,9 +25,7 @@ interface TagAwareAdapterInterface extends AdapterInterface * * @param string[] $tags An array of tags to invalidate * - * @return bool - * * @throws InvalidArgumentException When $tags is not valid */ - public function invalidateTags(array $tags); + public function invalidateTags(array $tags): bool; } diff --git a/lib/symfony/cache/Adapter/TraceableAdapter.php b/lib/symfony/cache/Adapter/TraceableAdapter.php index 4b06557f8..118b00909 100644 --- a/lib/symfony/cache/Adapter/TraceableAdapter.php +++ b/lib/symfony/cache/Adapter/TraceableAdapter.php @@ -28,17 +28,14 @@ use Symfony\Contracts\Service\ResetInterface; class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { protected $pool; - private $calls = []; + private array $calls = []; public function __construct(AdapterInterface $pool) { $this->pool = $pool; } - /** - * {@inheritdoc} - */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed { if (!$this->pool instanceof CacheInterface) { throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); @@ -67,10 +64,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt return $value; } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { $event = $this->start(__FUNCTION__); try { @@ -87,12 +81,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt return $item; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { $event = $this->start(__FUNCTION__); try { @@ -102,12 +91,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { $event = $this->start(__FUNCTION__); try { @@ -117,12 +101,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { $event = $this->start(__FUNCTION__); try { @@ -132,12 +111,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { $event = $this->start(__FUNCTION__); try { @@ -147,10 +121,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { $event = $this->start(__FUNCTION__); try { @@ -173,12 +144,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt return $f(); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { $event = $this->start(__FUNCTION__); try { @@ -192,12 +158,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { $event = $this->start(__FUNCTION__); $event->result['keys'] = $keys; @@ -208,12 +169,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function commit() + public function commit(): bool { $event = $this->start(__FUNCTION__); try { @@ -223,10 +179,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } - /** - * {@inheritdoc} - */ - public function prune() + public function prune(): bool { if (!$this->pool instanceof PruneableInterface) { return false; @@ -240,7 +193,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } /** - * {@inheritdoc} + * @return void */ public function reset() { @@ -251,9 +204,6 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt $this->clearCalls(); } - /** - * {@inheritdoc} - */ public function delete(string $key): bool { $event = $this->start(__FUNCTION__); @@ -264,16 +214,30 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } + /** + * @return array + */ public function getCalls() { return $this->calls; } + /** + * @return void + */ public function clearCalls() { $this->calls = []; } + public function getPool(): AdapterInterface + { + return $this->pool; + } + + /** + * @return TraceableAdapterEvent + */ protected function start(string $name) { $this->calls[] = $event = new TraceableAdapterEvent(); @@ -284,12 +248,15 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt } } +/** + * @internal + */ class TraceableAdapterEvent { - public $name; - public $start; - public $end; - public $result; - public $hits = 0; - public $misses = 0; + public string $name; + public float $start; + public float $end; + public array|bool $result; + public int $hits = 0; + public int $misses = 0; } diff --git a/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php index 69461b8b6..c85d199e4 100644 --- a/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php +++ b/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php @@ -23,10 +23,7 @@ class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapt parent::__construct($pool); } - /** - * {@inheritdoc} - */ - public function invalidateTags(array $tags) + public function invalidateTags(array $tags): bool { $event = $this->start(__FUNCTION__); try { diff --git a/lib/symfony/cache/CHANGELOG.md b/lib/symfony/cache/CHANGELOG.md index 60a862740..4c1c9d420 100644 --- a/lib/symfony/cache/CHANGELOG.md +++ b/lib/symfony/cache/CHANGELOG.md @@ -1,6 +1,30 @@ CHANGELOG ========= +6.4 +--- + + * `EarlyExpirationHandler` no longer implements `MessageHandlerInterface`, rely on `AsMessageHandler` instead + +6.3 +--- + + * Add support for Relay PHP extension for Redis + * Updates to allow Redis cluster connections using predis/predis:^2.0 + * Add optional parameter `$isSameDatabase` to `DoctrineDbalAdapter::configureSchema()` + +6.1 +--- + + * Add support for ACL auth in RedisAdapter + * Improve reliability and performance of `TagAwareAdapter` by making tag versions an integral part of item value + +6.0 +--- + + * Remove `DoctrineProvider` and `DoctrineAdapter` + * Remove support of Doctrine DBAL in `PdoAdapter` + 5.4 --- diff --git a/lib/symfony/cache/CacheItem.php b/lib/symfony/cache/CacheItem.php index 091d9e9b8..1a81706da 100644 --- a/lib/symfony/cache/CacheItem.php +++ b/lib/symfony/cache/CacheItem.php @@ -22,49 +22,37 @@ use Symfony\Contracts\Cache\ItemInterface; final class CacheItem implements ItemInterface { private const METADATA_EXPIRY_OFFSET = 1527506807; + private const VALUE_WRAPPER = "\xA9"; - protected $key; - protected $value; - protected $isHit = false; - protected $expiry; - protected $metadata = []; - protected $newMetadata = []; - protected $innerItem; - protected $poolHash; - protected $isTaggable = false; + protected string $key; + protected mixed $value = null; + protected bool $isHit = false; + protected float|int|null $expiry = null; + protected array $metadata = []; + protected array $newMetadata = []; + protected ?ItemInterface $innerItem = null; + protected ?string $poolHash = null; + protected bool $isTaggable = false; - /** - * {@inheritdoc} - */ public function getKey(): string { return $this->key; } - /** - * {@inheritdoc} - * - * @return mixed - */ - public function get() + public function get(): mixed { return $this->value; } - /** - * {@inheritdoc} - */ public function isHit(): bool { return $this->isHit; } /** - * {@inheritdoc} - * * @return $this */ - public function set($value): self + public function set($value): static { $this->value = $value; @@ -72,34 +60,24 @@ final class CacheItem implements ItemInterface } /** - * {@inheritdoc} - * * @return $this */ - public function expiresAt($expiration): self + public function expiresAt(?\DateTimeInterface $expiration): static { - if (null === $expiration) { - $this->expiry = null; - } elseif ($expiration instanceof \DateTimeInterface) { - $this->expiry = (float) $expiration->format('U.u'); - } else { - throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', get_debug_type($expiration))); - } + $this->expiry = null !== $expiration ? (float) $expiration->format('U.u') : null; return $this; } /** - * {@inheritdoc} - * * @return $this */ - public function expiresAfter($time): self + public function expiresAfter(mixed $time): static { if (null === $time) { $this->expiry = null; } elseif ($time instanceof \DateInterval) { - $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); + $this->expiry = microtime(true) + \DateTimeImmutable::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (\is_int($time)) { $this->expiry = $time + microtime(true); } else { @@ -109,20 +87,17 @@ final class CacheItem implements ItemInterface return $this; } - /** - * {@inheritdoc} - */ - public function tag($tags): ItemInterface + public function tag(mixed $tags): static { if (!$this->isTaggable) { throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); } - if (!is_iterable($tags)) { + if (!\is_array($tags) && !$tags instanceof \Traversable) { // don't use is_iterable(), it's slow $tags = [$tags]; } foreach ($tags as $tag) { - if (!\is_string($tag) && !(\is_object($tag) && method_exists($tag, '__toString'))) { - throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag))); + if (!\is_string($tag) && !$tag instanceof \Stringable) { + throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', get_debug_type($tag))); } $tag = (string) $tag; if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { @@ -140,9 +115,6 @@ final class CacheItem implements ItemInterface return $this; } - /** - * {@inheritdoc} - */ public function getMetadata(): array { return $this->metadata; @@ -175,7 +147,7 @@ final class CacheItem implements ItemInterface * * @internal */ - public static function log(?LoggerInterface $logger, string $message, array $context = []) + public static function log(?LoggerInterface $logger, string $message, array $context = []): void { if ($logger) { $logger->warning($message, $context); @@ -189,4 +161,38 @@ final class CacheItem implements ItemInterface @trigger_error(strtr($message, $replace), \E_USER_WARNING); } } + + private function pack(): mixed + { + if (!$m = $this->newMetadata) { + return $this->value; + } + $valueWrapper = self::VALUE_WRAPPER; + + return new $valueWrapper($this->value, $m + ['expiry' => $this->expiry]); + } + + private function unpack(): bool + { + $v = $this->value; + $valueWrapper = self::VALUE_WRAPPER; + + if ($v instanceof $valueWrapper) { + $this->value = $v->value; + $this->metadata = $v->metadata; + + return true; + } + + if (!\is_array($v) || 1 !== \count($v) || 10 !== \strlen($k = (string) array_key_first($v)) || "\x9D" !== $k[0] || "\0" !== $k[5] || "\x5F" !== $k[9]) { + return false; + } + + // BC with pools populated before v6.1 + $this->value = $v[$k]; + $this->metadata = unpack('Vexpiry/Nctime', substr($k, 1, -1)); + $this->metadata['expiry'] += self::METADATA_EXPIRY_OFFSET; + + return true; + } } diff --git a/lib/symfony/cache/DataCollector/CacheDataCollector.php b/lib/symfony/cache/DataCollector/CacheDataCollector.php index 9590436dc..08ab8816c 100644 --- a/lib/symfony/cache/DataCollector/CacheDataCollector.php +++ b/lib/symfony/cache/DataCollector/CacheDataCollector.php @@ -29,29 +29,27 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter /** * @var TraceableAdapter[] */ - private $instances = []; + private array $instances = []; - public function addInstance(string $name, TraceableAdapter $instance) + public function addInstance(string $name, TraceableAdapter $instance): void { $this->instances[$name] = $instance; } - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { - $empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []]; + $empty = ['calls' => [], 'adapters' => [], 'config' => [], 'options' => [], 'statistics' => []]; $this->data = ['instances' => $empty, 'total' => $empty]; foreach ($this->instances as $name => $instance) { $this->data['instances']['calls'][$name] = $instance->getCalls(); + $this->data['instances']['adapters'][$name] = get_debug_type($instance->getPool()); } $this->data['instances']['statistics'] = $this->calculateStatistics(); $this->data['total']['statistics'] = $this->calculateTotalStatistics(); } - public function reset() + public function reset(): void { $this->data = []; foreach ($this->instances as $instance) { @@ -59,14 +57,11 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter } } - public function lateCollect() + public function lateCollect(): void { $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'cache'; @@ -90,14 +85,20 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter /** * Method returns all logged Cache call objects. - * - * @return mixed */ - public function getCalls() + public function getCalls(): mixed { return $this->data['instances']['calls']; } + /** + * Method returns all logged Cache adapter classes. + */ + public function getAdapters(): array + { + return $this->data['instances']['adapters']; + } + private function calculateStatistics(): array { $statistics = []; @@ -114,7 +115,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter /** @var TraceableAdapterEvent $call */ foreach ($calls as $call) { ++$statistics[$name]['calls']; - $statistics[$name]['time'] += $call->end - $call->start; + $statistics[$name]['time'] += ($call->end ?? microtime(true)) - $call->start; if ('get' === $call->name) { ++$statistics[$name]['reads']; if ($call->hits) { @@ -136,10 +137,8 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter $statistics[$name]['misses'] += $call->misses; } elseif ('hasItem' === $call->name) { ++$statistics[$name]['reads']; - if (false === $call->result) { - ++$statistics[$name]['misses']; - } else { - ++$statistics[$name]['hits']; + foreach ($call->result ?? [] as $result) { + ++$statistics[$name][$result ? 'hits' : 'misses']; } } elseif ('save' === $call->name) { ++$statistics[$name]['writes']; diff --git a/lib/symfony/cache/DependencyInjection/CacheCollectorPass.php b/lib/symfony/cache/DependencyInjection/CacheCollectorPass.php index 843232e44..17507f1fb 100644 --- a/lib/symfony/cache/DependencyInjection/CacheCollectorPass.php +++ b/lib/symfony/cache/DependencyInjection/CacheCollectorPass.php @@ -26,51 +26,36 @@ use Symfony\Component\DependencyInjection\Reference; */ class CacheCollectorPass implements CompilerPassInterface { - private $dataCollectorCacheId; - private $cachePoolTag; - private $cachePoolRecorderInnerSuffix; - - public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->dataCollectorCacheId = $dataCollectorCacheId; - $this->cachePoolTag = $cachePoolTag; - $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix; - } - /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->dataCollectorCacheId)) { + if (!$container->hasDefinition('data_collector.cache')) { return; } - foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) { + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $attributes) { $poolName = $attributes[0]['name'] ?? $id; $this->addToCollector($id, $poolName, $container); } } - private function addToCollector(string $id, string $name, ContainerBuilder $container) + private function addToCollector(string $id, string $name, ContainerBuilder $container): void { $definition = $container->getDefinition($id); if ($definition->isAbstract()) { return; } - $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId); + $collectorDefinition = $container->getDefinition('data_collector.cache'); $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); $recorder->setTags($definition->getTags()); if (!$definition->isPublic() || !$definition->isPrivate()) { $recorder->setPublic($definition->isPublic()); } - $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]); + $recorder->setArguments([new Reference($innerId = $id.'.recorder_inner')]); foreach ($definition->getMethodCalls() as [$method, $args]) { if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) { @@ -89,6 +74,5 @@ class CacheCollectorPass implements CompilerPassInterface // Tell the collector to add the new instance $collectorDefinition->addMethodCall('addInstance', [$name, new Reference($id)]); - $collectorDefinition->setPublic(false); } } diff --git a/lib/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/lib/symfony/cache/DependencyInjection/CachePoolClearerPass.php index c9b04adda..6793bea94 100644 --- a/lib/symfony/cache/DependencyInjection/CachePoolClearerPass.php +++ b/lib/symfony/cache/DependencyInjection/CachePoolClearerPass.php @@ -20,25 +20,14 @@ use Symfony\Component\DependencyInjection\Reference; */ class CachePoolClearerPass implements CompilerPassInterface { - private $cachePoolClearerTag; - - public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->cachePoolClearerTag = $cachePoolClearerTag; - } - /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { $container->getParameterBag()->remove('cache.prefix.seed'); - foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) { + foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) { $clearer = $container->getDefinition($id); $pools = []; foreach ($clearer->getArgument(0) as $name => $ref) { diff --git a/lib/symfony/cache/DependencyInjection/CachePoolPass.php b/lib/symfony/cache/DependencyInjection/CachePoolPass.php index 14ac2bde4..9c280abbe 100644 --- a/lib/symfony/cache/DependencyInjection/CachePoolPass.php +++ b/lib/symfony/cache/DependencyInjection/CachePoolPass.php @@ -29,35 +29,8 @@ use Symfony\Component\DependencyInjection\Reference; */ class CachePoolPass implements CompilerPassInterface { - private $cachePoolTag; - private $kernelResetTag; - private $cacheClearerId; - private $cachePoolClearerTag; - private $cacheSystemClearerId; - private $cacheSystemClearerTag; - private $reverseContainerId; - private $reversibleTag; - private $messageHandlerId; - - public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer', string $reverseContainerId = 'reverse_container', string $reversibleTag = 'container.reversible', string $messageHandlerId = 'cache.early_expiration_handler') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->cachePoolTag = $cachePoolTag; - $this->kernelResetTag = $kernelResetTag; - $this->cacheClearerId = $cacheClearerId; - $this->cachePoolClearerTag = $cachePoolClearerTag; - $this->cacheSystemClearerId = $cacheSystemClearerId; - $this->cacheSystemClearerTag = $cacheSystemClearerTag; - $this->reverseContainerId = $reverseContainerId; - $this->reversibleTag = $reversibleTag; - $this->messageHandlerId = $messageHandlerId; - } - /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -79,7 +52,7 @@ class CachePoolPass implements CompilerPassInterface 'early_expiration_message_bus', 'reset', ]; - foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) { $adapter = $pool = $container->getDefinition($id); if ($pool->isAbstract()) { continue; @@ -88,7 +61,7 @@ class CachePoolPass implements CompilerPassInterface while ($adapter instanceof ChildDefinition) { $adapter = $container->findDefinition($adapter->getParent()); $class = $class ?: $adapter->getClass(); - if ($t = $adapter->getTag($this->cachePoolTag)) { + if ($t = $adapter->getTag('cache.pool')) { $tags[0] += $t[0]; } } @@ -130,7 +103,7 @@ class CachePoolPass implements CompilerPassInterface while ($adapter instanceof ChildDefinition) { $adapter = $container->findDefinition($adapter->getParent()); $chainedClass = $chainedClass ?: $adapter->getClass(); - if ($t = $adapter->getTag($this->cachePoolTag)) { + if ($t = $adapter->getTag('cache.pool')) { $chainedTags[0] += $t[0]; } } @@ -168,19 +141,19 @@ class CachePoolPass implements CompilerPassInterface // no-op } elseif ('reset' === $attr) { if ($tags[0][$attr]) { - $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]); + $pool->addTag('kernel.reset', ['method' => $tags[0][$attr]]); } } elseif ('early_expiration_message_bus' === $attr) { $needsMessageHandler = true; $pool->addMethodCall('setCallbackWrapper', [(new Definition(EarlyExpirationDispatcher::class)) ->addArgument(new Reference($tags[0]['early_expiration_message_bus'])) - ->addArgument(new Reference($this->reverseContainerId)) + ->addArgument(new Reference('reverse_container')) ->addArgument((new Definition('callable')) ->setFactory([new Reference($id), 'setCallbackWrapper']) ->addArgument(null) ), ]); - $pool->addTag($this->reversibleTag); + $pool->addTag('container.reversible'); } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) { $argument = $tags[0][$attr]; @@ -194,7 +167,7 @@ class CachePoolPass implements CompilerPassInterface unset($tags[0][$attr]); } if (!empty($tags[0])) { - throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0])))); + throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); } if (null !== $clearer) { @@ -205,14 +178,14 @@ class CachePoolPass implements CompilerPassInterface } if (!$needsMessageHandler) { - $container->removeDefinition($this->messageHandlerId); + $container->removeDefinition('cache.early_expiration_handler'); } - $notAliasedCacheClearerId = $this->cacheClearerId; - while ($container->hasAlias($this->cacheClearerId)) { - $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId); + $notAliasedCacheClearerId = $aliasedCacheClearerId = 'cache.global_clearer'; + while ($container->hasAlias('cache.global_clearer')) { + $aliasedCacheClearerId = (string) $container->getAlias('cache.global_clearer'); } - if ($container->hasDefinition($this->cacheClearerId)) { + if ($container->hasDefinition($aliasedCacheClearerId)) { $clearers[$notAliasedCacheClearerId] = $allPools; } @@ -223,10 +196,10 @@ class CachePoolPass implements CompilerPassInterface } else { $clearer->setArgument(0, $pools); } - $clearer->addTag($this->cachePoolClearerTag); + $clearer->addTag('cache.pool.clearer'); - if ($this->cacheSystemClearerId === $id) { - $clearer->addTag($this->cacheSystemClearerTag); + if ('cache.system_clearer' === $id) { + $clearer->addTag('kernel.cache_clearer'); } } @@ -245,7 +218,7 @@ class CachePoolPass implements CompilerPassInterface } } - private function getNamespace(string $seed, string $id) + private function getNamespace(string $seed, string $id): string { return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10); } @@ -253,7 +226,7 @@ class CachePoolPass implements CompilerPassInterface /** * @internal */ - public static function getServiceProvider(ContainerBuilder $container, string $name) + public static function getServiceProvider(ContainerBuilder $container, string $name): string { $container->resolveEnvPlaceholders($name, null, $usedEnvs); @@ -262,7 +235,6 @@ class CachePoolPass implements CompilerPassInterface if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) { $definition = new Definition(AbstractAdapter::class); - $definition->setPublic(false); $definition->setFactory([AbstractAdapter::class, 'createConnection']); $definition->setArguments([$dsn, ['lazy' => true]]); $container->setDefinition($name, $definition); diff --git a/lib/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/lib/symfony/cache/DependencyInjection/CachePoolPrunerPass.php index 86a1906ad..00e912686 100644 --- a/lib/symfony/cache/DependencyInjection/CachePoolPrunerPass.php +++ b/lib/symfony/cache/DependencyInjection/CachePoolPrunerPass.php @@ -23,31 +23,18 @@ use Symfony\Component\DependencyInjection\Reference; */ class CachePoolPrunerPass implements CompilerPassInterface { - private $cacheCommandServiceId; - private $cachePoolTag; - - public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->cacheCommandServiceId = $cacheCommandServiceId; - $this->cachePoolTag = $cachePoolTag; - } - /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->cacheCommandServiceId)) { + if (!$container->hasDefinition('console.command.cache_pool_prune')) { return; } $services = []; - foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) { $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass()); if (!$reflection = $container->getReflectionClass($class)) { @@ -59,6 +46,6 @@ class CachePoolPrunerPass implements CompilerPassInterface } } - $container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services)); + $container->getDefinition('console.command.cache_pool_prune')->replaceArgument(0, new IteratorArgument($services)); } } diff --git a/lib/symfony/cache/DoctrineProvider.php b/lib/symfony/cache/DoctrineProvider.php deleted file mode 100644 index 7b55aae23..000000000 --- a/lib/symfony/cache/DoctrineProvider.php +++ /dev/null @@ -1,124 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Cache; - -use Doctrine\Common\Cache\CacheProvider; -use Psr\Cache\CacheItemPoolInterface; -use Symfony\Contracts\Service\ResetInterface; - -if (!class_exists(CacheProvider::class)) { - return; -} - -/** - * @author Nicolas Grekas - * - * @deprecated Use Doctrine\Common\Cache\Psr6\DoctrineProvider instead - */ -class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface -{ - private $pool; - - public function __construct(CacheItemPoolInterface $pool) - { - trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "Doctrine\Common\Cache\Psr6\DoctrineProvider" instead.', __CLASS__); - - $this->pool = $pool; - } - - /** - * {@inheritdoc} - */ - public function prune() - { - return $this->pool instanceof PruneableInterface && $this->pool->prune(); - } - - /** - * {@inheritdoc} - */ - public function reset() - { - if ($this->pool instanceof ResetInterface) { - $this->pool->reset(); - } - $this->setNamespace($this->getNamespace()); - } - - /** - * {@inheritdoc} - * - * @return mixed - */ - protected function doFetch($id) - { - $item = $this->pool->getItem(rawurlencode($id)); - - return $item->isHit() ? $item->get() : false; - } - - /** - * {@inheritdoc} - * - * @return bool - */ - protected function doContains($id) - { - return $this->pool->hasItem(rawurlencode($id)); - } - - /** - * {@inheritdoc} - * - * @return bool - */ - protected function doSave($id, $data, $lifeTime = 0) - { - $item = $this->pool->getItem(rawurlencode($id)); - - if (0 < $lifeTime) { - $item->expiresAfter($lifeTime); - } - - return $this->pool->save($item->set($data)); - } - - /** - * {@inheritdoc} - * - * @return bool - */ - protected function doDelete($id) - { - return $this->pool->deleteItem(rawurlencode($id)); - } - - /** - * {@inheritdoc} - * - * @return bool - */ - protected function doFlush() - { - return $this->pool->clear(); - } - - /** - * {@inheritdoc} - * - * @return array|null - */ - protected function doGetStats() - { - return null; - } -} diff --git a/lib/symfony/cache/LICENSE b/lib/symfony/cache/LICENSE index 7fa953905..0223acd4a 100644 --- a/lib/symfony/cache/LICENSE +++ b/lib/symfony/cache/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2022 Fabien Potencier +Copyright (c) 2016-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/cache/LockRegistry.php b/lib/symfony/cache/LockRegistry.php index 65f20bb73..4b750cb44 100644 --- a/lib/symfony/cache/LockRegistry.php +++ b/lib/symfony/cache/LockRegistry.php @@ -26,15 +26,15 @@ use Symfony\Contracts\Cache\ItemInterface; */ final class LockRegistry { - private static $openedFiles = []; - private static $lockedFiles; - private static $signalingException; - private static $signalingCallback; + private static array $openedFiles = []; + private static ?array $lockedFiles = null; + private static \Exception $signalingException; + private static \Closure $signalingCallback; /** * The number of items in this list controls the max number of concurrent processes. */ - private static $files = [ + private static array $files = [ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php', __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php', __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php', @@ -43,7 +43,6 @@ final class LockRegistry __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php', __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseBucketAdapter.php', __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseCollectionAdapter.php', - __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php', __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineDbalAdapter.php', __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php', __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php', @@ -84,7 +83,7 @@ final class LockRegistry return $previousFiles; } - public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null) + public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null): mixed { if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) { // disable locking on Windows by default @@ -97,17 +96,16 @@ final class LockRegistry return $callback($item, $save); } - self::$signalingException ?? self::$signalingException = unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); - self::$signalingCallback ?? self::$signalingCallback = function () { throw self::$signalingException; }; + self::$signalingException ??= unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); + self::$signalingCallback ??= fn () => throw self::$signalingException; while (true) { try { - $locked = false; // race to get the lock in non-blocking mode $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); if ($locked || !$wouldBlock) { - $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); + $logger?->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); self::$lockedFiles[$key] = true; $value = $callback($item, $save); @@ -124,7 +122,7 @@ final class LockRegistry return $value; } // if we failed the race, retry locking in blocking mode to wait for the winner - $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); + $logger?->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); flock($lock, \LOCK_SH); } finally { flock($lock, \LOCK_UN); @@ -133,7 +131,7 @@ final class LockRegistry try { $value = $pool->get($item->getKey(), self::$signalingCallback, 0); - $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); + $logger?->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); $save = false; return $value; @@ -141,19 +139,22 @@ final class LockRegistry if (self::$signalingException !== $e) { throw $e; } - $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); + $logger?->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); } } return null; } + /** + * @return resource|false + */ private static function open(int $key) { if (null !== $h = self::$openedFiles[$key] ?? null) { return $h; } - set_error_handler(function () {}); + set_error_handler(static fn () => null); try { $h = fopen(self::$files[$key], 'r+'); } finally { diff --git a/lib/symfony/cache/Marshaller/DefaultMarshaller.php b/lib/symfony/cache/Marshaller/DefaultMarshaller.php index 3202dd69c..973b137ae 100644 --- a/lib/symfony/cache/Marshaller/DefaultMarshaller.php +++ b/lib/symfony/cache/Marshaller/DefaultMarshaller.php @@ -20,23 +20,20 @@ use Symfony\Component\Cache\Exception\CacheException; */ class DefaultMarshaller implements MarshallerInterface { - private $useIgbinarySerialize = true; - private $throwOnSerializationFailure; + private bool $useIgbinarySerialize = true; + private bool $throwOnSerializationFailure = false; public function __construct(bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false) { if (null === $useIgbinarySerialize) { - $useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.6', phpversion('igbinary'), '<=')); - } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.6', phpversion('igbinary'), '>')))) { - throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.'); + $useIgbinarySerialize = \extension_loaded('igbinary') && version_compare('3.1.6', phpversion('igbinary'), '<='); + } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || version_compare('3.1.6', phpversion('igbinary'), '>'))) { + throw new CacheException(\extension_loaded('igbinary') ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.'); } $this->useIgbinarySerialize = $useIgbinarySerialize; $this->throwOnSerializationFailure = $throwOnSerializationFailure; } - /** - * {@inheritdoc} - */ public function marshall(array $values, ?array &$failed): array { $serialized = $failed = []; @@ -59,10 +56,7 @@ class DefaultMarshaller implements MarshallerInterface return $serialized; } - /** - * {@inheritdoc} - */ - public function unmarshall(string $value) + public function unmarshall(string $value): mixed { if ('b:0;' === $value) { return false; @@ -71,7 +65,7 @@ class DefaultMarshaller implements MarshallerInterface return null; } static $igbinaryNull; - if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') ? igbinary_serialize(null) : false)) { + if ($value === $igbinaryNull ??= \extension_loaded('igbinary') ? igbinary_serialize(null) : false) { return null; } $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); @@ -97,7 +91,7 @@ class DefaultMarshaller implements MarshallerInterface /** * @internal */ - public static function handleUnserializeCallback(string $class) + public static function handleUnserializeCallback(string $class): never { throw new \DomainException('Class not found: '.$class); } diff --git a/lib/symfony/cache/Marshaller/DeflateMarshaller.php b/lib/symfony/cache/Marshaller/DeflateMarshaller.php index 554480611..35237de68 100644 --- a/lib/symfony/cache/Marshaller/DeflateMarshaller.php +++ b/lib/symfony/cache/Marshaller/DeflateMarshaller.php @@ -20,7 +20,7 @@ use Symfony\Component\Cache\Exception\CacheException; */ class DeflateMarshaller implements MarshallerInterface { - private $marshaller; + private MarshallerInterface $marshaller; public function __construct(MarshallerInterface $marshaller) { @@ -31,18 +31,12 @@ class DeflateMarshaller implements MarshallerInterface $this->marshaller = $marshaller; } - /** - * {@inheritdoc} - */ public function marshall(array $values, ?array &$failed): array { return array_map('gzdeflate', $this->marshaller->marshall($values, $failed)); } - /** - * {@inheritdoc} - */ - public function unmarshall(string $value) + public function unmarshall(string $value): mixed { if (false !== $inflatedValue = @gzinflate($value)) { $value = $inflatedValue; diff --git a/lib/symfony/cache/Marshaller/MarshallerInterface.php b/lib/symfony/cache/Marshaller/MarshallerInterface.php index cdd6c4022..5b81aad2d 100644 --- a/lib/symfony/cache/Marshaller/MarshallerInterface.php +++ b/lib/symfony/cache/Marshaller/MarshallerInterface.php @@ -32,9 +32,7 @@ interface MarshallerInterface /** * Unserializes a single value and throws an exception if anything goes wrong. * - * @return mixed - * * @throws \Exception Whenever unserialization fails */ - public function unmarshall(string $value); + public function unmarshall(string $value): mixed; } diff --git a/lib/symfony/cache/Marshaller/SodiumMarshaller.php b/lib/symfony/cache/Marshaller/SodiumMarshaller.php index dbf486a72..ee64c949a 100644 --- a/lib/symfony/cache/Marshaller/SodiumMarshaller.php +++ b/lib/symfony/cache/Marshaller/SodiumMarshaller.php @@ -21,8 +21,8 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException; */ class SodiumMarshaller implements MarshallerInterface { - private $marshaller; - private $decryptionKeys; + private MarshallerInterface $marshaller; + private array $decryptionKeys; /** * @param string[] $decryptionKeys The key at index "0" is required and is used to decrypt and encrypt values; @@ -48,9 +48,6 @@ class SodiumMarshaller implements MarshallerInterface return \function_exists('sodium_crypto_box_seal'); } - /** - * {@inheritdoc} - */ public function marshall(array $values, ?array &$failed): array { $encryptionKey = sodium_crypto_box_publickey($this->decryptionKeys[0]); @@ -63,10 +60,7 @@ class SodiumMarshaller implements MarshallerInterface return $encryptedValues; } - /** - * {@inheritdoc} - */ - public function unmarshall(string $value) + public function unmarshall(string $value): mixed { foreach ($this->decryptionKeys as $k) { if (false !== $decryptedValue = @sodium_crypto_box_seal_open($value, $k)) { diff --git a/lib/symfony/cache/Marshaller/TagAwareMarshaller.php b/lib/symfony/cache/Marshaller/TagAwareMarshaller.php index 5d1e303b4..f5c2867af 100644 --- a/lib/symfony/cache/Marshaller/TagAwareMarshaller.php +++ b/lib/symfony/cache/Marshaller/TagAwareMarshaller.php @@ -18,16 +18,13 @@ namespace Symfony\Component\Cache\Marshaller; */ class TagAwareMarshaller implements MarshallerInterface { - private $marshaller; + private MarshallerInterface $marshaller; public function __construct(MarshallerInterface $marshaller = null) { $this->marshaller = $marshaller ?? new DefaultMarshaller(); } - /** - * {@inheritdoc} - */ public function marshall(array $values, ?array &$failed): array { $failed = $notSerialized = $serialized = []; @@ -51,7 +48,7 @@ class TagAwareMarshaller implements MarshallerInterface $serialized[$id][9] = "\x5F"; } } else { - // other arbitratry values are serialized using the decorated marshaller below + // other arbitrary values are serialized using the decorated marshaller below $notSerialized[$id] = $value; } } @@ -64,10 +61,7 @@ class TagAwareMarshaller implements MarshallerInterface return $serialized; } - /** - * {@inheritdoc} - */ - public function unmarshall(string $value) + public function unmarshall(string $value): mixed { // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) { diff --git a/lib/symfony/cache/Messenger/EarlyExpirationDispatcher.php b/lib/symfony/cache/Messenger/EarlyExpirationDispatcher.php index 6f11b8b5a..db2dd97d8 100644 --- a/lib/symfony/cache/Messenger/EarlyExpirationDispatcher.php +++ b/lib/symfony/cache/Messenger/EarlyExpirationDispatcher.php @@ -23,22 +23,25 @@ use Symfony\Component\Messenger\Stamp\HandledStamp; */ class EarlyExpirationDispatcher { - private $bus; - private $reverseContainer; - private $callbackWrapper; + private MessageBusInterface $bus; + private ReverseContainer $reverseContainer; + private ?\Closure $callbackWrapper; public function __construct(MessageBusInterface $bus, ReverseContainer $reverseContainer, callable $callbackWrapper = null) { $this->bus = $bus; $this->reverseContainer = $reverseContainer; - $this->callbackWrapper = $callbackWrapper; + $this->callbackWrapper = null === $callbackWrapper ? null : $callbackWrapper(...); } + /** + * @return mixed + */ public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, LoggerInterface $logger = null) { if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) { // The item is stale or the callback cannot be reversed: we must compute the value now - $logger && $logger->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); + $logger?->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save); } diff --git a/lib/symfony/cache/Messenger/EarlyExpirationHandler.php b/lib/symfony/cache/Messenger/EarlyExpirationHandler.php index 1f0bd565c..b7eab8064 100644 --- a/lib/symfony/cache/Messenger/EarlyExpirationHandler.php +++ b/lib/symfony/cache/Messenger/EarlyExpirationHandler.php @@ -13,21 +13,25 @@ namespace Symfony\Component\Cache\Messenger; use Symfony\Component\Cache\CacheItem; use Symfony\Component\DependencyInjection\ReverseContainer; -use Symfony\Component\Messenger\Handler\MessageHandlerInterface; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; /** * Computes cached values sent to a message bus. */ -class EarlyExpirationHandler implements MessageHandlerInterface +#[AsMessageHandler] +class EarlyExpirationHandler { - private $reverseContainer; - private $processedNonces = []; + private ReverseContainer $reverseContainer; + private array $processedNonces = []; public function __construct(ReverseContainer $reverseContainer) { $this->reverseContainer = $reverseContainer; } + /** + * @return void + */ public function __invoke(EarlyExpirationMessage $message) { $item = $message->getItem(); @@ -59,7 +63,7 @@ class EarlyExpirationHandler implements MessageHandlerInterface static $setMetadata; - $setMetadata ?? $setMetadata = \Closure::bind( + $setMetadata ??= \Closure::bind( function (CacheItem $item, float $startTime) { if ($item->expiry > $endTime = microtime(true)) { $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry; @@ -73,7 +77,8 @@ class EarlyExpirationHandler implements MessageHandlerInterface $startTime = microtime(true); $pool = $message->findPool($this->reverseContainer); $callback = $message->findCallback($this->reverseContainer); - $value = $callback($item); + $save = true; + $value = $callback($item, $save); $setMetadata($item, $startTime); $pool->save($item->set($value)); } diff --git a/lib/symfony/cache/Messenger/EarlyExpirationMessage.php b/lib/symfony/cache/Messenger/EarlyExpirationMessage.php index e25c07e9a..6056ebab4 100644 --- a/lib/symfony/cache/Messenger/EarlyExpirationMessage.php +++ b/lib/symfony/cache/Messenger/EarlyExpirationMessage.php @@ -20,16 +20,16 @@ use Symfony\Component\DependencyInjection\ReverseContainer; */ final class EarlyExpirationMessage { - private $item; - private $pool; - private $callback; + private CacheItem $item; + private string $pool; + private string|array $callback; public static function create(ReverseContainer $reverseContainer, callable $callback, CacheItem $item, AdapterInterface $pool): ?self { try { $item = clone $item; $item->set(null); - } catch (\Exception $e) { + } catch (\Exception) { return null; } @@ -66,7 +66,10 @@ final class EarlyExpirationMessage return $this->pool; } - public function getCallback() + /** + * @return string|string[] + */ + public function getCallback(): string|array { return $this->callback; } @@ -88,7 +91,7 @@ final class EarlyExpirationMessage return $callback; } - private function __construct(CacheItem $item, string $pool, $callback) + private function __construct(CacheItem $item, string $pool, string|array $callback) { $this->item = $item; $this->pool = $pool; diff --git a/lib/symfony/cache/PruneableInterface.php b/lib/symfony/cache/PruneableInterface.php index 426152536..3095c80f4 100644 --- a/lib/symfony/cache/PruneableInterface.php +++ b/lib/symfony/cache/PruneableInterface.php @@ -16,8 +16,5 @@ namespace Symfony\Component\Cache; */ interface PruneableInterface { - /** - * @return bool - */ - public function prune(); + public function prune(): bool; } diff --git a/lib/symfony/cache/Psr16Cache.php b/lib/symfony/cache/Psr16Cache.php index 28c7de605..f21384fee 100644 --- a/lib/symfony/cache/Psr16Cache.php +++ b/lib/symfony/cache/Psr16Cache.php @@ -19,10 +19,6 @@ use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Traits\ProxyTrait; -if (null !== (new \ReflectionMethod(CacheInterface::class, 'get'))->getReturnType()) { - throw new \LogicException('psr/simple-cache 3.0+ is not compatible with this version of symfony/cache. Please upgrade symfony/cache to 6.0+ or downgrade psr/simple-cache to 1.x or 2.x.'); -} - /** * Turns a PSR-6 cache into a PSR-16 one. * @@ -32,10 +28,9 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf { use ProxyTrait; - private const METADATA_EXPIRY_OFFSET = 1527506807; - - private $createCacheItem; - private $cacheItemPrototype; + private ?\Closure $createCacheItem = null; + private ?CacheItem $cacheItemPrototype = null; + private static \Closure $packCacheItem; public function __construct(CacheItemPoolInterface $pool) { @@ -71,14 +66,18 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf return $createCacheItem($key, null, $allowInt)->set($value); }; + self::$packCacheItem ??= \Closure::bind( + static function (CacheItem $item) { + $item->newMetadata = $item->metadata; + + return $item->pack(); + }, + null, + CacheItem::class + ); } - /** - * {@inheritdoc} - * - * @return mixed - */ - public function get($key, $default = null) + public function get($key, $default = null): mixed { try { $item = $this->pool->getItem($key); @@ -95,12 +94,7 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf return $item->isHit() ? $item->get() : $default; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function set($key, $value, $ttl = null) + public function set($key, $value, $ttl = null): bool { try { if (null !== $f = $this->createCacheItem) { @@ -120,12 +114,7 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf return $this->pool->save($item); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function delete($key) + public function delete($key): bool { try { return $this->pool->deleteItem($key); @@ -136,22 +125,12 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear() + public function clear(): bool { return $this->pool->clear(); } - /** - * {@inheritdoc} - * - * @return iterable - */ - public function getMultiple($keys, $default = null) + public function getMultiple($keys, $default = null): iterable { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); @@ -177,31 +156,13 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf } foreach ($items as $key => $item) { - if (!$item->isHit()) { - $values[$key] = $default; - continue; - } - $values[$key] = $item->get(); - - if (!$metadata = $item->getMetadata()) { - continue; - } - unset($metadata[CacheItem::METADATA_TAGS]); - - if ($metadata) { - $values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]]; - } + $values[$key] = $item->isHit() ? (self::$packCacheItem)($item) : $default; } return $values; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function setMultiple($values, $ttl = null) + public function setMultiple($values, $ttl = null): bool { $valuesIsArray = \is_array($values); if (!$valuesIsArray && !$values instanceof \Traversable) { @@ -249,12 +210,7 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf return $this->pool->commit() && $ok; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteMultiple($keys) + public function deleteMultiple($keys): bool { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); @@ -271,12 +227,7 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function has($key) + public function has($key): bool { try { return $this->pool->hasItem($key); diff --git a/lib/symfony/cache/README.md b/lib/symfony/cache/README.md index 74052052c..c466d5788 100644 --- a/lib/symfony/cache/README.md +++ b/lib/symfony/cache/README.md @@ -1,13 +1,13 @@ Symfony PSR-6 implementation for caching ======================================== -The Cache component provides an extended -[PSR-6](http://www.php-fig.org/psr/psr-6/) implementation for adding cache to +The Cache component provides extended +[PSR-6](https://www.php-fig.org/psr/psr-6/) implementations for adding cache to your applications. It is designed to have a low overhead so that caching is -fastest. It ships with a few caching adapters for the most widespread and -suited to caching backends. It also provides a `doctrine/cache` proxy adapter -to cover more advanced caching needs and a proxy adapter for greater -interoperability between PSR-6 implementations. +fastest. It ships with adapters for the most widespread caching backends. +It also provides a [PSR-16](https://www.php-fig.org/psr/psr-16/) adapter, +and implementations for [symfony/cache-contracts](https://github.com/symfony/cache-contracts)' +`CacheInterface` and `TagAwareCacheInterface`. Resources --------- diff --git a/lib/symfony/cache/Traits/AbstractAdapterTrait.php b/lib/symfony/cache/Traits/AbstractAdapterTrait.php index f0173c422..4ab2537db 100644 --- a/lib/symfony/cache/Traits/AbstractAdapterTrait.php +++ b/lib/symfony/cache/Traits/AbstractAdapterTrait.php @@ -26,21 +26,21 @@ trait AbstractAdapterTrait use LoggerAwareTrait; /** - * @var \Closure needs to be set by class, signature is function(string , mixed , bool ) + * needs to be set by class, signature is function(string , mixed , bool ). */ - private static $createCacheItem; + private static \Closure $createCacheItem; /** - * @var \Closure needs to be set by class, signature is function(array , string , array <&expiredIds>) + * needs to be set by class, signature is function(array , string , array <&expiredIds>). */ - private static $mergeByLifetime; + private static \Closure $mergeByLifetime; - private $namespace = ''; - private $defaultLifetime; - private $namespaceVersion = ''; - private $versioningIsEnabled = false; - private $deferred = []; - private $ids = []; + private string $namespace = ''; + private int $defaultLifetime; + private string $namespaceVersion = ''; + private bool $versioningIsEnabled = false; + private array $deferred = []; + private array $ids = []; /** * @var int|null The maximum length to enforce for identifiers or null when no limit applies @@ -51,37 +51,29 @@ trait AbstractAdapterTrait * Fetches several cache items. * * @param array $ids The cache identifiers to fetch - * - * @return array|\Traversable */ - abstract protected function doFetch(array $ids); + abstract protected function doFetch(array $ids): iterable; /** * Confirms if the cache contains specified cache item. * * @param string $id The identifier for which to check existence - * - * @return bool */ - abstract protected function doHave(string $id); + abstract protected function doHave(string $id): bool; /** * Deletes all items in the pool. * * @param string $namespace The prefix used for all identifiers managed by this pool - * - * @return bool */ - abstract protected function doClear(string $namespace); + abstract protected function doClear(string $namespace): bool; /** * Removes multiple items from the pool. * * @param array $ids An array of identifiers that should be removed from the pool - * - * @return bool */ - abstract protected function doDelete(array $ids); + abstract protected function doDelete(array $ids): bool; /** * Persists several cache items immediately. @@ -91,14 +83,9 @@ trait AbstractAdapterTrait * * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not */ - abstract protected function doSave(array $values, int $lifetime); + abstract protected function doSave(array $values, int $lifetime): array|bool; - /** - * {@inheritdoc} - * - * @return bool - */ - public function hasItem($key) + public function hasItem(mixed $key): bool { $id = $this->getId($key); @@ -115,12 +102,7 @@ trait AbstractAdapterTrait } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function clear(string $prefix = '') + public function clear(string $prefix = ''): bool { $this->deferred = []; if ($cleared = $this->versioningIsEnabled) { @@ -156,22 +138,12 @@ trait AbstractAdapterTrait } } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItem($key) + public function deleteItem(mixed $key): bool { return $this->deleteItems([$key]); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function deleteItems(array $keys) + public function deleteItems(array $keys): bool { $ids = []; @@ -184,7 +156,7 @@ trait AbstractAdapterTrait if ($this->doDelete($ids)) { return true; } - } catch (\Exception $e) { + } catch (\Exception) { } $ok = true; @@ -206,10 +178,7 @@ trait AbstractAdapterTrait return $ok; } - /** - * {@inheritdoc} - */ - public function getItem($key) + public function getItem(mixed $key): CacheItem { $id = $this->getId($key); @@ -233,10 +202,7 @@ trait AbstractAdapterTrait return (self::$createCacheItem)($key, null, false); } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) + public function getItems(array $keys = []): iterable { $ids = []; $commit = false; @@ -261,12 +227,7 @@ trait AbstractAdapterTrait return $this->generateItems($items, $ids); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function save(CacheItemInterface $item) + public function save(CacheItemInterface $item): bool { if (!$item instanceof CacheItem) { return false; @@ -276,12 +237,7 @@ trait AbstractAdapterTrait return $this->commit(); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function saveDeferred(CacheItemInterface $item) + public function saveDeferred(CacheItemInterface $item): bool { if (!$item instanceof CacheItem) { return false; @@ -297,11 +253,11 @@ trait AbstractAdapterTrait * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed, * but old keys may need garbage collection and extra round-trips to the back-end are required. * - * Calling this method also clears the memoized namespace version and thus forces a resynchonization of it. + * Calling this method also clears the memoized namespace version and thus forces a resynchronization of it. * * @return bool the previous state of versioning */ - public function enableVersioning(bool $enable = true) + public function enableVersioning(bool $enable = true): bool { $wasEnabled = $this->versioningIsEnabled; $this->versioningIsEnabled = $enable; @@ -311,10 +267,7 @@ trait AbstractAdapterTrait return $wasEnabled; } - /** - * {@inheritdoc} - */ - public function reset() + public function reset(): void { if ($this->deferred) { $this->commit(); @@ -323,14 +276,14 @@ trait AbstractAdapterTrait $this->ids = []; } - /** - * @return array - */ - public function __sleep() + public function __sleep(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } + /** + * @return void + */ public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); @@ -365,7 +318,10 @@ trait AbstractAdapterTrait } } - private function getId($key) + /** + * @internal + */ + protected function getId(mixed $key): string { if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { $this->ids = []; @@ -394,15 +350,15 @@ trait AbstractAdapterTrait $this->ids[$key] = $key; if (\count($this->ids) > 1000) { - array_splice($this->ids, 0, 500); // stop memory leak if there are many keys + $this->ids = \array_slice($this->ids, 500, null, true); // stop memory leak if there are many keys } if (null === $this->maxIdLength) { return $this->namespace.$this->namespaceVersion.$key; } if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { - // Use MD5 to favor speed over security, which is not an issue here - $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); + // Use xxh128 to favor speed over security, which is not an issue here + $this->ids[$key] = $id = substr_replace(base64_encode(hash('xxh128', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); $id = $this->namespace.$this->namespaceVersion.$id; } @@ -412,7 +368,7 @@ trait AbstractAdapterTrait /** * @internal */ - public static function handleUnserializeCallback(string $class) + public static function handleUnserializeCallback(string $class): never { throw new \DomainException('Class not found: '.$class); } diff --git a/lib/symfony/cache/Traits/ContractsTrait.php b/lib/symfony/cache/Traits/ContractsTrait.php index 9a491adb5..083ce1f9d 100644 --- a/lib/symfony/cache/Traits/ContractsTrait.php +++ b/lib/symfony/cache/Traits/ContractsTrait.php @@ -31,8 +31,8 @@ trait ContractsTrait doGet as private contractsGet; } - private $callbackWrapper; - private $computing = []; + private \Closure $callbackWrapper; + private array $computing = []; /** * Wraps the callback passed to ->get() in a callable. @@ -42,42 +42,46 @@ trait ContractsTrait public function setCallbackWrapper(?callable $callbackWrapper): callable { if (!isset($this->callbackWrapper)) { - $this->callbackWrapper = \Closure::fromCallable([LockRegistry::class, 'compute']); + $this->callbackWrapper = LockRegistry::compute(...); - if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { $this->setCallbackWrapper(null); } } + if (null !== $callbackWrapper && !$callbackWrapper instanceof \Closure) { + $callbackWrapper = $callbackWrapper(...); + } + $previousWrapper = $this->callbackWrapper; - $this->callbackWrapper = $callbackWrapper ?? static function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { - return $callback($item, $save); - }; + $this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) => $callback($item, $save); return $previousWrapper; } - private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null) + private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null): mixed { - if (0 > $beta = $beta ?? 1.0) { + if (0 > $beta ??= 1.0) { throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); } static $setMetadata; - $setMetadata ?? $setMetadata = \Closure::bind( + $setMetadata ??= \Closure::bind( static function (CacheItem $item, float $startTime, ?array &$metadata) { if ($item->expiry > $endTime = microtime(true)) { $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); } else { - unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]); + unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME], $metadata[CacheItem::METADATA_TAGS]); } }, null, CacheItem::class ); + $this->callbackWrapper ??= LockRegistry::compute(...); + return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) { // don't wrap nor save recursive calls if (isset($this->computing[$key])) { diff --git a/lib/symfony/cache/Traits/FilesystemCommonTrait.php b/lib/symfony/cache/Traits/FilesystemCommonTrait.php index c06cc309a..8772c30d5 100644 --- a/lib/symfony/cache/Traits/FilesystemCommonTrait.php +++ b/lib/symfony/cache/Traits/FilesystemCommonTrait.php @@ -20,10 +20,10 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException; */ trait FilesystemCommonTrait { - private $directory; - private $tmp; + private string $directory; + private string $tmpSuffix; - private function init(string $namespace, ?string $directory) + private function init(string $namespace, ?string $directory): void { if (!isset($directory[0])) { $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache'; @@ -50,10 +50,7 @@ trait FilesystemCommonTrait $this->directory = $directory; } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + protected function doClear(string $namespace): bool { $ok = true; @@ -68,10 +65,7 @@ trait FilesystemCommonTrait return $ok; } - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) + protected function doDelete(array $ids): bool { $ok = true; @@ -83,45 +77,55 @@ trait FilesystemCommonTrait return $ok; } + /** + * @return bool + */ protected function doUnlink(string $file) { return @unlink($file); } - private function write(string $file, string $data, int $expiresAt = null) + private function write(string $file, string $data, int $expiresAt = null): bool { - set_error_handler(__CLASS__.'::throwError'); + $unlink = false; + set_error_handler(static fn ($type, $message, $file, $line) => throw new \ErrorException($message, 0, $type, $file, $line)); try { - if (null === $this->tmp) { - $this->tmp = $this->directory.bin2hex(random_bytes(6)); - } + $tmp = $this->directory.$this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); try { - $h = fopen($this->tmp, 'x'); + $h = fopen($tmp, 'x'); } catch (\ErrorException $e) { if (!str_contains($e->getMessage(), 'File exists')) { throw $e; } - $this->tmp = $this->directory.bin2hex(random_bytes(6)); - $h = fopen($this->tmp, 'x'); + $tmp = $this->directory.$this->tmpSuffix = str_replace('/', '-', base64_encode(random_bytes(6))); + $h = fopen($tmp, 'x'); } fwrite($h, $data); fclose($h); + $unlink = true; if (null !== $expiresAt) { - touch($this->tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds + touch($tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds } - return rename($this->tmp, $file); + $success = rename($tmp, $file); + $unlink = !$success; + + return $success; } finally { restore_error_handler(); + + if ($unlink) { + @unlink($tmp); + } } } - private function getFile(string $id, bool $mkdir = false, string $directory = null) + private function getFile(string $id, bool $mkdir = false, string $directory = null): string { - // Use MD5 to favor speed over security, which is not an issue here - $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true))); + // Use xxh128 to favor speed over security, which is not an issue here + $hash = str_replace('/', '-', base64_encode(hash('xxh128', static::class.$id, true))); $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR); if ($mkdir && !is_dir($dir)) { @@ -163,22 +167,14 @@ trait FilesystemCommonTrait } } - /** - * @internal - */ - public static function throwError(int $type, string $message, string $file, int $line) - { - throw new \ErrorException($message, 0, $type, $file, $line); - } - - /** - * @return array - */ - public function __sleep() + public function __sleep(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } + /** + * @return void + */ public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); @@ -189,8 +185,8 @@ trait FilesystemCommonTrait if (method_exists(parent::class, '__destruct')) { parent::__destruct(); } - if (null !== $this->tmp && is_file($this->tmp)) { - unlink($this->tmp); + if (isset($this->tmpSuffix) && is_file($this->directory.$this->tmpSuffix)) { + unlink($this->directory.$this->tmpSuffix); } } } diff --git a/lib/symfony/cache/Traits/FilesystemTrait.php b/lib/symfony/cache/Traits/FilesystemTrait.php index 38b741f1c..47e9b838f 100644 --- a/lib/symfony/cache/Traits/FilesystemTrait.php +++ b/lib/symfony/cache/Traits/FilesystemTrait.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Cache\Traits; use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Nicolas Grekas @@ -23,12 +24,9 @@ trait FilesystemTrait { use FilesystemCommonTrait; - private $marshaller; + private MarshallerInterface $marshaller; - /** - * @return bool - */ - public function prune() + public function prune(): bool { $time = time(); $pruned = true; @@ -40,7 +38,7 @@ trait FilesystemTrait if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) { fclose($h); - $pruned = @unlink($file) && !file_exists($file) && $pruned; + $pruned = (@unlink($file) || !file_exists($file)) && $pruned; } else { fclose($h); } @@ -49,10 +47,7 @@ trait FilesystemTrait return $pruned; } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { $values = []; $now = time(); @@ -78,20 +73,14 @@ trait FilesystemTrait return $values; } - /** - * {@inheritdoc} - */ - protected function doHave(string $id) + protected function doHave(string $id): bool { $file = $this->getFile($id); return is_file($file) && (@filemtime($file) > time() || $this->doFetch([$id])); } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { $expiresAt = $lifetime ? (time() + $lifetime) : 0; $values = $this->marshaller->marshall($values, $failed); diff --git a/lib/symfony/cache/Traits/ProxyTrait.php b/lib/symfony/cache/Traits/ProxyTrait.php index c86f360ab..ba7c11fbe 100644 --- a/lib/symfony/cache/Traits/ProxyTrait.php +++ b/lib/symfony/cache/Traits/ProxyTrait.php @@ -21,20 +21,14 @@ use Symfony\Contracts\Service\ResetInterface; */ trait ProxyTrait { - private $pool; + private object $pool; - /** - * {@inheritdoc} - */ - public function prune() + public function prune(): bool { return $this->pool instanceof PruneableInterface && $this->pool->prune(); } - /** - * {@inheritdoc} - */ - public function reset() + public function reset(): void { if ($this->pool instanceof ResetInterface) { $this->pool->reset(); diff --git a/lib/symfony/cache/Traits/Redis5Proxy.php b/lib/symfony/cache/Traits/Redis5Proxy.php new file mode 100644 index 000000000..06130cc33 --- /dev/null +++ b/lib/symfony/cache/Traits/Redis5Proxy.php @@ -0,0 +1,1228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class Redis5Proxy extends \Redis implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _prefix($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _serialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _pack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function _compress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function acl($subcmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function auth($auth) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->auth(...\func_get_args()); + } + + public function bgSave() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgSave(...\func_get_args()); + } + + public function bgrewriteaof() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bitcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $ret_key, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blPop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blPop(...\func_get_args()); + } + + public function brPop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brPop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function bzPopMax($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMax(...\func_get_args()); + } + + public function bzPopMin($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMin(...\func_get_args()); + } + + public function clearLastError() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearLastError(...\func_get_args()); + } + + public function client($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function command(...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($cmd, $key, $value = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function connect($host, $port = null, $timeout = null, $retry_interval = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->connect(...\func_get_args()); + } + + public function dbSize() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbSize(...\func_get_args()); + } + + public function debug($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->debug(...\func_get_args()); + } + + public function decr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrBy($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrBy(...\func_get_args()); + } + + public function del($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function discard() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function dump($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function echo($msg) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function evalsha($script_sha, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function exec() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function expire($key, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireAt($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireAt(...\func_get_args()); + } + + public function flushAll($async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushAll(...\func_get_args()); + } + + public function flushDB($async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushDB(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function get($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getAuth() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getAuth(...\func_get_args()); + } + + public function getBit($key, $offset) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getBit(...\func_get_args()); + } + + public function getDBNum() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDBNum(...\func_get_args()); + } + + public function getHost() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getHost(...\func_get_args()); + } + + public function getLastError() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getLastError(...\func_get_args()); + } + + public function getMode() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMode(...\func_get_args()); + } + + public function getOption($option) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getOption(...\func_get_args()); + } + + public function getPersistentID() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPersistentID(...\func_get_args()); + } + + public function getPort() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPort(...\func_get_args()); + } + + public function getRange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getRange(...\func_get_args()); + } + + public function getReadTimeout() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getReadTimeout(...\func_get_args()); + } + + public function getSet($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getSet(...\func_get_args()); + } + + public function getTimeout() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTimeout(...\func_get_args()); + } + + public function hDel($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hDel(...\func_get_args()); + } + + public function hExists($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hExists(...\func_get_args()); + } + + public function hGet($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGet(...\func_get_args()); + } + + public function hGetAll($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGetAll(...\func_get_args()); + } + + public function hIncrBy($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrBy(...\func_get_args()); + } + + public function hIncrByFloat($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrByFloat(...\func_get_args()); + } + + public function hKeys($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hKeys(...\func_get_args()); + } + + public function hLen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hLen(...\func_get_args()); + } + + public function hMget($key, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMget(...\func_get_args()); + } + + public function hMset($key, $pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMset(...\func_get_args()); + } + + public function hSet($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSet(...\func_get_args()); + } + + public function hSetNx($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSetNx(...\func_get_args()); + } + + public function hStrLen($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hStrLen(...\func_get_args()); + } + + public function hVals($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hVals(...\func_get_args()); + } + + public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function incr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrBy($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrBy(...\func_get_args()); + } + + public function incrByFloat($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrByFloat(...\func_get_args()); + } + + public function info($option = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function isConnected() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->isConnected(...\func_get_args()); + } + + public function keys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lInsert($key, $position, $pivot, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lInsert(...\func_get_args()); + } + + public function lLen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lLen(...\func_get_args()); + } + + public function lPop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPop(...\func_get_args()); + } + + public function lPush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPush(...\func_get_args()); + } + + public function lPushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPushx(...\func_get_args()); + } + + public function lSet($key, $index, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lSet(...\func_get_args()); + } + + public function lastSave() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastSave(...\func_get_args()); + } + + public function lindex($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function lrange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function ltrim($key, $start, $stop) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function mget($keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function migrate($host, $port, $key, $db, $timeout, $copy = null, $replace = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->migrate(...\func_get_args()); + } + + public function move($key, $dbindex) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->move(...\func_get_args()); + } + + public function mset($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi($mode = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($field, $key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function pconnect($host, $port = null, $timeout = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pconnect(...\func_get_args()); + } + + public function persist($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireAt($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireAt(...\func_get_args()); + } + + public function pfadd($key, $elements) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dstkey, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function pipeline() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pipeline(...\func_get_args()); + } + + public function psetex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function publish($channel, $message) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function rPop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPop(...\func_get_args()); + } + + public function rPush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPush(...\func_get_args()); + } + + public function rPushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPushx(...\func_get_args()); + } + + public function randomKey() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomKey(...\func_get_args()); + } + + public function rawcommand($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renameNx($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renameNx(...\func_get_args()); + } + + public function restore($ttl, $key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpoplpush($src, $dst) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function sAdd($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAdd(...\func_get_args()); + } + + public function sAddArray($key, $options) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAddArray(...\func_get_args()); + } + + public function sDiff($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiff(...\func_get_args()); + } + + public function sDiffStore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiffStore(...\func_get_args()); + } + + public function sInter($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInter(...\func_get_args()); + } + + public function sInterStore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInterStore(...\func_get_args()); + } + + public function sMembers($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMembers(...\func_get_args()); + } + + public function sMisMember($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMisMember(...\func_get_args()); + } + + public function sMove($src, $dst, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMove(...\func_get_args()); + } + + public function sPop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sPop(...\func_get_args()); + } + + public function sRandMember($key, $count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sRandMember(...\func_get_args()); + } + + public function sUnion($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnion(...\func_get_args()); + } + + public function sUnionStore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnionStore(...\func_get_args()); + } + + public function save() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($i_iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function select($dbindex) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->select(...\func_get_args()); + } + + public function set($key, $value, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setBit($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setBit(...\func_get_args()); + } + + public function setOption($option, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setOption(...\func_get_args()); + } + + public function setRange($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setRange(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function sismember($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function slaveof($host = null, $port = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slaveof(...\func_get_args()); + } + + public function slowlog($arg, $option = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function sort($key, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sortAsc($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAsc(...\func_get_args()); + } + + public function sortAscAlpha($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAscAlpha(...\func_get_args()); + } + + public function sortDesc($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDesc(...\func_get_args()); + } + + public function sortDescAlpha($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDescAlpha(...\func_get_args()); + } + + public function srem($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function swapdb($srcdb, $dstdb) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->swapdb(...\func_get_args()); + } + + public function time() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unlink($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unsubscribe($channel, ...$other_channels) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unwatch() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function wait($numslaves, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function xack($str_key, $str_group, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($str_key, $str_id, $arr_fields, $i_maxlen = null, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($str_key, $str_group, $str_consumer, $i_min_idle, $arr_ids, $arr_opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($str_key, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($str_operation, $str_key = null, $str_arg1 = null, $str_arg2 = null, $str_arg3 = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xinfo($str_cmd, $str_key = null, $str_group = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($str_key, $str_group, $str_start = null, $str_end = null, $i_count = null, $str_consumer = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($str_group, $str_consumer, $arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($str_key, $i_maxlen, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zAdd($key, $score, $value, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zAdd(...\func_get_args()); + } + + public function zCard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCard(...\func_get_args()); + } + + public function zCount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCount(...\func_get_args()); + } + + public function zIncrBy($key, $value, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zIncrBy(...\func_get_args()); + } + + public function zLexCount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zLexCount(...\func_get_args()); + } + + public function zPopMax($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMax(...\func_get_args()); + } + + public function zPopMin($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMin(...\func_get_args()); + } + + public function zRange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRange(...\func_get_args()); + } + + public function zRangeByLex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByLex(...\func_get_args()); + } + + public function zRangeByScore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByScore(...\func_get_args()); + } + + public function zRank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRank(...\func_get_args()); + } + + public function zRem($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRem(...\func_get_args()); + } + + public function zRemRangeByLex($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByLex(...\func_get_args()); + } + + public function zRemRangeByRank($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByRank(...\func_get_args()); + } + + public function zRemRangeByScore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByScore(...\func_get_args()); + } + + public function zRevRange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRange(...\func_get_args()); + } + + public function zRevRangeByLex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByLex(...\func_get_args()); + } + + public function zRevRangeByScore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByScore(...\func_get_args()); + } + + public function zRevRank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRank(...\func_get_args()); + } + + public function zScore($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zScore(...\func_get_args()); + } + + public function zinterstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zunionstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } + + public function delete($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->delete(...\func_get_args()); + } + + public function evaluate($script, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evaluate(...\func_get_args()); + } + + public function evaluateSha($script_sha, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evaluateSha(...\func_get_args()); + } + + public function getKeys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getKeys(...\func_get_args()); + } + + public function getMultiple($keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMultiple(...\func_get_args()); + } + + public function lGet($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lGet(...\func_get_args()); + } + + public function lGetRange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lGetRange(...\func_get_args()); + } + + public function lRemove($key, $value, $count) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lRemove(...\func_get_args()); + } + + public function lSize($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lSize(...\func_get_args()); + } + + public function listTrim($key, $start, $stop) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->listTrim(...\func_get_args()); + } + + public function open($host, $port = null, $timeout = null, $retry_interval = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->open(...\func_get_args()); + } + + public function popen($host, $port = null, $timeout = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->popen(...\func_get_args()); + } + + public function renameKey($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renameKey(...\func_get_args()); + } + + public function sContains($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sContains(...\func_get_args()); + } + + public function sGetMembers($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sGetMembers(...\func_get_args()); + } + + public function sRemove($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sRemove(...\func_get_args()); + } + + public function sSize($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sSize(...\func_get_args()); + } + + public function sendEcho($msg) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sendEcho(...\func_get_args()); + } + + public function setTimeout($key, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setTimeout(...\func_get_args()); + } + + public function substr($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->substr(...\func_get_args()); + } + + public function zDelete($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zDelete(...\func_get_args()); + } + + public function zDeleteRangeByRank($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zDeleteRangeByRank(...\func_get_args()); + } + + public function zDeleteRangeByScore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zDeleteRangeByScore(...\func_get_args()); + } + + public function zInter($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zInter(...\func_get_args()); + } + + public function zRemove($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemove(...\func_get_args()); + } + + public function zRemoveRangeByScore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemoveRangeByScore(...\func_get_args()); + } + + public function zReverseRange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zReverseRange(...\func_get_args()); + } + + public function zSize($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zSize(...\func_get_args()); + } + + public function zUnion($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zUnion(...\func_get_args()); + } +} diff --git a/lib/symfony/cache/Traits/Redis6Proxy.php b/lib/symfony/cache/Traits/Redis6Proxy.php new file mode 100644 index 000000000..0680404fc --- /dev/null +++ b/lib/symfony/cache/Traits/Redis6Proxy.php @@ -0,0 +1,1293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class Redis6Proxy extends \Redis implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _compress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _prefix($key): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _serialize($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _pack($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function acl($subcmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $credentials): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->auth(...\func_get_args()); + } + + public function bgSave(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgSave(...\func_get_args()); + } + + public function bgrewriteaof(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $bybit = false): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $deskey, $srckey, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = 0, $end = -1, $bybit = false): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blPop(...\func_get_args()); + } + + public function brPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brPop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function bzPopMax($key, $timeout_or_key, ...$extra_args): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMax(...\func_get_args()); + } + + public function bzPopMin($key, $timeout_or_key, ...$extra_args): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMin(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmpop(...\func_get_args()); + } + + public function clearLastError(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearLastError(...\func_get_args()); + } + + public function client($opt, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function command($opt = null, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($operation, $key_or_settings = null, $value = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->connect(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->copy(...\func_get_args()); + } + + public function dbSize(): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbSize(...\func_get_args()); + } + + public function debug($key): \Redis|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->debug(...\func_get_args()); + } + + public function decr($key, $by = 1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrBy($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrBy(...\func_get_args()); + } + + public function del($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function delete($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->delete(...\func_get_args()); + } + + public function discard(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function dump($key): \Redis|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function echo($str): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function eval_ro($script_sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval_ro(...\func_get_args()); + } + + public function evalsha($sha1, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha1, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha_ro(...\func_get_args()); + } + + public function exec(): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys): \Redis|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function expire($key, $timeout, $mode = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireAt($key, $timestamp, $mode = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireAt(...\func_get_args()); + } + + public function failover($to = null, $abort = false, $timeout = 0): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->failover(...\func_get_args()); + } + + public function expiretime($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expiretime(...\func_get_args()); + } + + public function pexpiretime($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpiretime(...\func_get_args()); + } + + public function fcall($fn, $keys = [], $args = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall(...\func_get_args()); + } + + public function fcall_ro($fn, $keys = [], $args = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall_ro(...\func_get_args()); + } + + public function flushAll($sync = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushAll(...\func_get_args()); + } + + public function flushDB($sync = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushDB(...\func_get_args()); + } + + public function function($operation, ...$args): \Redis|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->function(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Redis|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getAuth(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getAuth(...\func_get_args()); + } + + public function getBit($key, $idx): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getBit(...\func_get_args()); + } + + public function getEx($key, $options = []): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getEx(...\func_get_args()); + } + + public function getDBNum(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDBNum(...\func_get_args()); + } + + public function getDel($key): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDel(...\func_get_args()); + } + + public function getHost(): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getHost(...\func_get_args()); + } + + public function getLastError(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getLastError(...\func_get_args()); + } + + public function getMode(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMode(...\func_get_args()); + } + + public function getOption($option): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getOption(...\func_get_args()); + } + + public function getPersistentID(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPersistentID(...\func_get_args()); + } + + public function getPort(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPort(...\func_get_args()); + } + + public function getRange($key, $start, $end): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getRange(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): \Redis|array|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lcs(...\func_get_args()); + } + + public function getReadTimeout(): float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getReadTimeout(...\func_get_args()); + } + + public function getset($key, $value): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTimeout(...\func_get_args()); + } + + public function getTransferredBytes(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTransferredBytes(...\func_get_args()); + } + + public function clearTransferredBytes(): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearTransferredBytes(...\func_get_args()); + } + + public function hDel($key, $field, ...$other_fields): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hDel(...\func_get_args()); + } + + public function hExists($key, $field): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hExists(...\func_get_args()); + } + + public function hGet($key, $member): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGet(...\func_get_args()); + } + + public function hGetAll($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGetAll(...\func_get_args()); + } + + public function hIncrBy($key, $field, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrBy(...\func_get_args()); + } + + public function hIncrByFloat($key, $field, $value): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrByFloat(...\func_get_args()); + } + + public function hKeys($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hKeys(...\func_get_args()); + } + + public function hLen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hLen(...\func_get_args()); + } + + public function hMget($key, $fields): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMget(...\func_get_args()); + } + + public function hMset($key, $fieldvals): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMset(...\func_get_args()); + } + + public function hRandField($key, $options = null): \Redis|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hRandField(...\func_get_args()); + } + + public function hSet($key, $member, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSet(...\func_get_args()); + } + + public function hSetNx($key, $field, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSetNx(...\func_get_args()); + } + + public function hStrLen($key, $field): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hStrLen(...\func_get_args()); + } + + public function hVals($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hVals(...\func_get_args()); + } + + public function hscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function incr($key, $by = 1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrBy($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrBy(...\func_get_args()); + } + + public function incrByFloat($key, $value): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrByFloat(...\func_get_args()); + } + + public function info(...$sections): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function isConnected(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->isConnected(...\func_get_args()); + } + + public function keys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lInsert($key, $pos, $pivot, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lInsert(...\func_get_args()); + } + + public function lLen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lLen(...\func_get_args()); + } + + public function lMove($src, $dst, $wherefrom, $whereto): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lMove(...\func_get_args()); + } + + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmove(...\func_get_args()); + } + + public function lPop($key, $count = 0): \Redis|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPop(...\func_get_args()); + } + + public function lPos($key, $value, $options = null): \Redis|array|bool|int|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPos(...\func_get_args()); + } + + public function lPush($key, ...$elements): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPush(...\func_get_args()); + } + + public function rPush($key, ...$elements): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPush(...\func_get_args()); + } + + public function lPushx($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPushx(...\func_get_args()); + } + + public function rPushx($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPushx(...\func_get_args()); + } + + public function lSet($key, $index, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lSet(...\func_get_args()); + } + + public function lastSave(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastSave(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function lrange($key, $start, $end): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count = 0): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function mget($keys): \Redis|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->migrate(...\func_get_args()); + } + + public function move($key, $index): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->move(...\func_get_args()); + } + + public function mset($key_values): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($key_values): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi($value = \Redis::MULTI): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($subcommand, $key): \Redis|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function open($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->open(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pconnect(...\func_get_args()); + } + + public function persist($key): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timeout, $mode = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireAt($key, $timestamp, $mode = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireAt(...\func_get_args()); + } + + public function pfadd($key, $elements): \Redis|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key_or_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping($message = null): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function pipeline(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pipeline(...\func_get_args()); + } + + public function popen($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->popen(...\func_get_args()); + } + + public function psetex($key, $expire, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $cb): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function publish($channel, $message): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($command, $arg = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($patterns): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function rPop($key, $count = 0): \Redis|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPop(...\func_get_args()); + } + + public function randomKey(): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomKey(...\func_get_args()); + } + + public function rawcommand($command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($old_name, $new_name): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renameNx($key_src, $key_dst): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renameNx(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpoplpush($srckey, $dstkey): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function sAdd($key, $value, ...$other_values): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAdd(...\func_get_args()); + } + + public function sAddArray($key, $values): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAddArray(...\func_get_args()); + } + + public function sDiff($key, ...$other_keys): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiff(...\func_get_args()); + } + + public function sDiffStore($dst, $key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiffStore(...\func_get_args()); + } + + public function sInter($key, ...$other_keys): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sintercard(...\func_get_args()); + } + + public function sInterStore($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInterStore(...\func_get_args()); + } + + public function sMembers($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMembers(...\func_get_args()); + } + + public function sMisMember($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMisMember(...\func_get_args()); + } + + public function sMove($src, $dst, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMove(...\func_get_args()); + } + + public function sPop($key, $count = 0): \Redis|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sPop(...\func_get_args()); + } + + public function sRandMember($key, $count = 0): \Redis|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sRandMember(...\func_get_args()); + } + + public function sUnion($key, ...$other_keys): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnion(...\func_get_args()); + } + + public function sUnionStore($dst, $key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnionStore(...\func_get_args()); + } + + public function save(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$iterator, $pattern = null, $count = 0, $type = null): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function select($db): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->select(...\func_get_args()); + } + + public function set($key, $value, $options = null): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setBit($key, $idx, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setBit(...\func_get_args()); + } + + public function setRange($key, $index, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setRange(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setOption(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function sismember($key, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function slaveof($host = null, $port = 6379): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slaveof(...\func_get_args()); + } + + public function replicaof($host = null, $port = 6379): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->replicaof(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->touch(...\func_get_args()); + } + + public function slowlog($operation, $length = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function sort($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort_ro(...\func_get_args()); + } + + public function sortAsc($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAsc(...\func_get_args()); + } + + public function sortAscAlpha($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAscAlpha(...\func_get_args()); + } + + public function sortDesc($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDesc(...\func_get_args()); + } + + public function sortDescAlpha($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDescAlpha(...\func_get_args()); + } + + public function srem($key, $value, ...$other_values): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($key, &$iterator, $pattern = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function ssubscribe($channels, $cb): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ssubscribe(...\func_get_args()); + } + + public function strlen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $cb): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function sunsubscribe($channels): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunsubscribe(...\func_get_args()); + } + + public function swapdb($src, $dst): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->swapdb(...\func_get_args()); + } + + public function time(): \Redis|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unlink($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unsubscribe($channels): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unwatch(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function wait($numreplicas, $timeout): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->wait(...\func_get_args()); + } + + public function xack($key, $group, $ids): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xautoclaim(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($key, $ids): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zAdd($key, $score_or_options, ...$more_scores_and_mems): \Redis|false|float|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zAdd(...\func_get_args()); + } + + public function zCard($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCard(...\func_get_args()); + } + + public function zCount($key, $start, $end): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCount(...\func_get_args()); + } + + public function zIncrBy($key, $value, $member): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zIncrBy(...\func_get_args()); + } + + public function zLexCount($key, $min, $max): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zLexCount(...\func_get_args()); + } + + public function zMscore($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zMscore(...\func_get_args()); + } + + public function zPopMax($key, $count = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMax(...\func_get_args()); + } + + public function zPopMin($key, $count = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMin(...\func_get_args()); + } + + public function zRange($key, $start, $end, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRange(...\func_get_args()); + } + + public function zRangeByLex($key, $min, $max, $offset = -1, $count = -1): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByLex(...\func_get_args()); + } + + public function zRangeByScore($key, $start, $end, $options = []): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByScore(...\func_get_args()); + } + + public function zrangestore($dstkey, $srckey, $start, $end, $options = null): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangestore(...\func_get_args()); + } + + public function zRandMember($key, $options = null): \Redis|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRandMember(...\func_get_args()); + } + + public function zRank($key, $member): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRank(...\func_get_args()); + } + + public function zRem($key, $member, ...$other_members): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRem(...\func_get_args()); + } + + public function zRemRangeByLex($key, $min, $max): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByLex(...\func_get_args()); + } + + public function zRemRangeByRank($key, $start, $end): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByRank(...\func_get_args()); + } + + public function zRemRangeByScore($key, $start, $end): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByScore(...\func_get_args()); + } + + public function zRevRange($key, $start, $end, $scores = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRange(...\func_get_args()); + } + + public function zRevRangeByLex($key, $max, $min, $offset = -1, $count = -1): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByLex(...\func_get_args()); + } + + public function zRevRangeByScore($key, $max, $min, $options = []): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByScore(...\func_get_args()); + } + + public function zRevRank($key, $member): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRank(...\func_get_args()); + } + + public function zScore($key, $member): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zScore(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiffstore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $aggregate = null): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zunion($keys, $weights = null, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $aggregate = null): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } +} diff --git a/lib/symfony/cache/Traits/RedisCluster5Proxy.php b/lib/symfony/cache/Traits/RedisCluster5Proxy.php new file mode 100644 index 000000000..da23e0f88 --- /dev/null +++ b/lib/symfony/cache/Traits/RedisCluster5Proxy.php @@ -0,0 +1,983 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RedisCluster5Proxy extends \RedisCluster implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($name, $seeds = null, $timeout = null, $read_timeout = null, $persistent = null, $auth = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _masters() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_masters(...\func_get_args()); + } + + public function _prefix($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _redir() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_redir(...\func_get_args()); + } + + public function _serialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _compress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _pack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function acl($key_or_address, $subcmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function bgrewriteaof($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bgsave($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgsave(...\func_get_args()); + } + + public function bitcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $ret_key, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function clearlasterror() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearlasterror(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmin(...\func_get_args()); + } + + public function client($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function cluster($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cluster(...\func_get_args()); + } + + public function command(...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function dbsize($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbsize(...\func_get_args()); + } + + public function decr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrby($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrby(...\func_get_args()); + } + + public function del($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function discard() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function dump($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function echo($msg) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function evalsha($script_sha, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function exec() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function expire($key, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireat($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireat(...\func_get_args()); + } + + public function flushall($key_or_address, $async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushall(...\func_get_args()); + } + + public function flushdb($key_or_address, $async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushdb(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function get($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getbit($key, $offset) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getbit(...\func_get_args()); + } + + public function getlasterror() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getlasterror(...\func_get_args()); + } + + public function getmode() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getmode(...\func_get_args()); + } + + public function getoption($option) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getoption(...\func_get_args()); + } + + public function getrange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getrange(...\func_get_args()); + } + + public function getset($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function hdel($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hdel(...\func_get_args()); + } + + public function hexists($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexists(...\func_get_args()); + } + + public function hget($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hget(...\func_get_args()); + } + + public function hgetall($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetall(...\func_get_args()); + } + + public function hincrby($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrbyfloat(...\func_get_args()); + } + + public function hkeys($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hkeys(...\func_get_args()); + } + + public function hlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hlen(...\func_get_args()); + } + + public function hmget($key, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmget(...\func_get_args()); + } + + public function hmset($key, $pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmset(...\func_get_args()); + } + + public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function hset($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hset(...\func_get_args()); + } + + public function hsetnx($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hsetnx(...\func_get_args()); + } + + public function hstrlen($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hstrlen(...\func_get_args()); + } + + public function hvals($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hvals(...\func_get_args()); + } + + public function incr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrby($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrbyfloat(...\func_get_args()); + } + + public function info($key_or_address, $option = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function keys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lastsave($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastsave(...\func_get_args()); + } + + public function lget($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lget(...\func_get_args()); + } + + public function lindex($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function linsert($key, $position, $pivot, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->linsert(...\func_get_args()); + } + + public function llen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->llen(...\func_get_args()); + } + + public function lpop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpop(...\func_get_args()); + } + + public function lpush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpush(...\func_get_args()); + } + + public function lpushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpushx(...\func_get_args()); + } + + public function lrange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function lset($key, $index, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lset(...\func_get_args()); + } + + public function ltrim($key, $start, $stop) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function mget($keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function mset($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($field, $key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function persist($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireat($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireat(...\func_get_args()); + } + + public function pfadd($key, $elements) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dstkey, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function psetex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function publish($channel, $message) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function randomkey($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomkey(...\func_get_args()); + } + + public function rawcommand($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renamenx(...\func_get_args()); + } + + public function restore($ttl, $key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpop(...\func_get_args()); + } + + public function rpoplpush($src, $dst) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function rpush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpush(...\func_get_args()); + } + + public function rpushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpushx(...\func_get_args()); + } + + public function sadd($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sadd(...\func_get_args()); + } + + public function saddarray($key, $options) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->saddarray(...\func_get_args()); + } + + public function save($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$i_iterator, $str_node, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($i_iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiff(...\func_get_args()); + } + + public function sdiffstore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiffstore(...\func_get_args()); + } + + public function set($key, $value, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setbit($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setbit(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function setoption($option, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setoption(...\func_get_args()); + } + + public function setrange($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setrange(...\func_get_args()); + } + + public function sinter($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinter(...\func_get_args()); + } + + public function sinterstore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinterstore(...\func_get_args()); + } + + public function sismember($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function slowlog($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function smembers($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smembers(...\func_get_args()); + } + + public function smove($src, $dst, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smove(...\func_get_args()); + } + + public function sort($key, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function spop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spop(...\func_get_args()); + } + + public function srandmember($key, $count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srandmember(...\func_get_args()); + } + + public function srem($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function sunion($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunion(...\func_get_args()); + } + + public function sunionstore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunionstore(...\func_get_args()); + } + + public function time() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unsubscribe($channel, ...$other_channels) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unlink($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unwatch() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function xack($str_key, $str_group, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($str_key, $str_id, $arr_fields, $i_maxlen = null, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($str_key, $str_group, $str_consumer, $i_min_idle, $arr_ids, $arr_opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($str_key, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($str_operation, $str_key = null, $str_arg1 = null, $str_arg2 = null, $str_arg3 = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xinfo($str_cmd, $str_key = null, $str_group = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($str_key, $str_group, $str_start = null, $str_end = null, $i_count = null, $str_consumer = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($str_group, $str_consumer, $arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($str_key, $i_maxlen, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zadd($key, $score, $value, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zadd(...\func_get_args()); + } + + public function zcard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcount(...\func_get_args()); + } + + public function zincrby($key, $value, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zincrby(...\func_get_args()); + } + + public function zinterstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zlexcount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zlexcount(...\func_get_args()); + } + + public function zpopmax($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmax(...\func_get_args()); + } + + public function zpopmin($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmin(...\func_get_args()); + } + + public function zrange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrange(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebylex(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebyscore(...\func_get_args()); + } + + public function zrank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrank(...\func_get_args()); + } + + public function zrem($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyscore(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrange(...\func_get_args()); + } + + public function zrevrangebylex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebylex(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebyscore(...\func_get_args()); + } + + public function zrevrank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrank(...\func_get_args()); + } + + public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscore($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscore(...\func_get_args()); + } + + public function zunionstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } +} diff --git a/lib/symfony/cache/Traits/RedisCluster6Proxy.php b/lib/symfony/cache/Traits/RedisCluster6Proxy.php new file mode 100644 index 000000000..fafc4acf2 --- /dev/null +++ b/lib/symfony/cache/Traits/RedisCluster6Proxy.php @@ -0,0 +1,1143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RedisCluster6Proxy extends \RedisCluster implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($name, $seeds = null, $timeout = 0, $read_timeout = 0, $persistent = false, #[\SensitiveParameter] $auth = null, $context = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _compress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _serialize($value): bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _pack($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function _prefix($key): bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _masters(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_masters(...\func_get_args()); + } + + public function _redir(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_redir(...\func_get_args()); + } + + public function acl($key_or_address, $subcmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function bgrewriteaof($key_or_address): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bgsave($key_or_address): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgsave(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $bybit = false): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $deskey, $srckey, ...$otherkeys): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = 0, $end = -1, $bybit = false): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpop(...\func_get_args()); + } + + public function brpoplpush($srckey, $deskey, $timeout): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function lmove($src, $dst, $wherefrom, $whereto): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmove(...\func_get_args()); + } + + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmove(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmin(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmpop(...\func_get_args()); + } + + public function clearlasterror(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearlasterror(...\func_get_args()); + } + + public function client($key_or_address, $subcommand, $arg = null): array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function cluster($key_or_address, $command, ...$extra_args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cluster(...\func_get_args()); + } + + public function command(...$extra_args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($key_or_address, $subcommand, ...$extra_args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function dbsize($key_or_address): \RedisCluster|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbsize(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->copy(...\func_get_args()); + } + + public function decr($key, $by = 1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrby($key, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrby(...\func_get_args()); + } + + public function decrbyfloat($key, $value): float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrbyfloat(...\func_get_args()); + } + + public function del($key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function discard(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function dump($key): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function echo($key_or_address, $msg): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval_ro(...\func_get_args()); + } + + public function evalsha($script_sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function evalsha_ro($script_sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha_ro(...\func_get_args()); + } + + public function exec(): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function touch($key, ...$other_keys): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->touch(...\func_get_args()); + } + + public function expire($key, $timeout, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireat($key, $timestamp, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireat(...\func_get_args()); + } + + public function expiretime($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expiretime(...\func_get_args()); + } + + public function pexpiretime($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpiretime(...\func_get_args()); + } + + public function flushall($key_or_address, $async = false): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushall(...\func_get_args()); + } + + public function flushdb($key_or_address, $async = false): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushdb(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dest, $unit = null): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): \RedisCluster|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \RedisCluster|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getbit($key, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getbit(...\func_get_args()); + } + + public function getlasterror(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getlasterror(...\func_get_args()); + } + + public function getmode(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getmode(...\func_get_args()); + } + + public function getoption($option): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getoption(...\func_get_args()); + } + + public function getrange($key, $start, $end): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getrange(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): \RedisCluster|array|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lcs(...\func_get_args()); + } + + public function getset($key, $value): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function gettransferredbytes(): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->gettransferredbytes(...\func_get_args()); + } + + public function cleartransferredbytes(): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cleartransferredbytes(...\func_get_args()); + } + + public function hdel($key, $member, ...$other_members): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hdel(...\func_get_args()); + } + + public function hexists($key, $member): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexists(...\func_get_args()); + } + + public function hget($key, $member): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hget(...\func_get_args()); + } + + public function hgetall($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetall(...\func_get_args()); + } + + public function hincrby($key, $member, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $member, $value): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrbyfloat(...\func_get_args()); + } + + public function hkeys($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hkeys(...\func_get_args()); + } + + public function hlen($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hlen(...\func_get_args()); + } + + public function hmget($key, $keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmget(...\func_get_args()); + } + + public function hmset($key, $key_values): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmset(...\func_get_args()); + } + + public function hscan($key, &$iterator, $pattern = null, $count = 0): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function hrandfield($key, $options = null): \RedisCluster|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hrandfield(...\func_get_args()); + } + + public function hset($key, $member, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hset(...\func_get_args()); + } + + public function hsetnx($key, $member, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hsetnx(...\func_get_args()); + } + + public function hstrlen($key, $field): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hstrlen(...\func_get_args()); + } + + public function hvals($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hvals(...\func_get_args()); + } + + public function incr($key, $by = 1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrby($key, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrbyfloat(...\func_get_args()); + } + + public function info($key_or_address, ...$sections): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function keys($pattern): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lastsave($key_or_address): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastsave(...\func_get_args()); + } + + public function lget($key, $index): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lget(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function linsert($key, $pos, $pivot, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->linsert(...\func_get_args()); + } + + public function llen($key): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->llen(...\func_get_args()); + } + + public function lpop($key, $count = 0): \RedisCluster|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Redis|array|bool|int|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpos(...\func_get_args()); + } + + public function lpush($key, $value, ...$other_values): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpush(...\func_get_args()); + } + + public function lpushx($key, $value): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpushx(...\func_get_args()); + } + + public function lrange($key, $start, $end): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count = 0): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function lset($key, $index, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lset(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function mget($keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function mset($key_values): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($key_values): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi($value = \Redis::MULTI): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($subcommand, $key): \RedisCluster|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function persist($key): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timeout, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireat($key, $timestamp, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireat(...\func_get_args()); + } + + public function pfadd($key, $elements): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($key, $keys): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping($key_or_address, $message = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function psetex($key, $timeout, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function publish($channel, $message): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($key_or_address, ...$values): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function randomkey($key_or_address): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomkey(...\func_get_args()); + } + + public function rawcommand($key_or_address, $command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($key_src, $key_dst): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renamenx(...\func_get_args()); + } + + public function restore($key, $timeout, $value, $options = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role($key_or_address): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpop($key, $count = 0): \RedisCluster|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpop(...\func_get_args()); + } + + public function rpoplpush($src, $dst): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function rpush($key, ...$elements): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpush(...\func_get_args()); + } + + public function rpushx($key, $value): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpushx(...\func_get_args()); + } + + public function sadd($key, $value, ...$other_values): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sadd(...\func_get_args()); + } + + public function saddarray($key, $values): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->saddarray(...\func_get_args()); + } + + public function save($key_or_address): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$iterator, $key_or_address, $pattern = null, $count = 0): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($key_or_address, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiff(...\func_get_args()); + } + + public function sdiffstore($dst, $key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiffstore(...\func_get_args()); + } + + public function set($key, $value, $options = null): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setbit($key, $offset, $onoff): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setbit(...\func_get_args()); + } + + public function setex($key, $expire, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function setoption($option, $value): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setoption(...\func_get_args()); + } + + public function setrange($key, $offset, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setrange(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinterstore(...\func_get_args()); + } + + public function sismember($key, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function smismember($key, $member, ...$other_members): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smismember(...\func_get_args()); + } + + public function slowlog($key_or_address, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function smembers($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smembers(...\func_get_args()); + } + + public function smove($src, $dst, $member): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smove(...\func_get_args()); + } + + public function sort($key, $options = null): \RedisCluster|array|bool|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = null): \RedisCluster|array|bool|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort_ro(...\func_get_args()); + } + + public function spop($key, $count = 0): \RedisCluster|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spop(...\func_get_args()); + } + + public function srandmember($key, $count = 0): \RedisCluster|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srandmember(...\func_get_args()); + } + + public function srem($key, $value, ...$other_values): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($key, &$iterator, $pattern = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $cb): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunion(...\func_get_args()); + } + + public function sunionstore($dst, $key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunionstore(...\func_get_args()); + } + + public function time($key_or_address): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unsubscribe($channels): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unlink($key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unwatch(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function xack($key, $group, $ids): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_iddle, $ids, $options): \RedisCluster|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($key, $ids): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xautoclaim(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($key, $start, $end, $count = -1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($key, $maxlen, $approx = false, $minid = false, $limit = -1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zadd($key, $score_or_options, ...$more_scores_and_mems): \RedisCluster|false|float|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zadd(...\func_get_args()); + } + + public function zcard($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcard(...\func_get_args()); + } + + public function zcount($key, $start, $end): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcount(...\func_get_args()); + } + + public function zincrby($key, $value, $member): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zincrby(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $aggregate = null): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zintercard(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zlexcount(...\func_get_args()); + } + + public function zpopmax($key, $value = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmax(...\func_get_args()); + } + + public function zpopmin($key, $value = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmin(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrange(...\func_get_args()); + } + + public function zrangestore($dstkey, $srckey, $start, $end, $options = null): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangestore(...\func_get_args()); + } + + public function zrandmember($key, $options = null): \RedisCluster|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrandmember(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebylex(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = []): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebyscore(...\func_get_args()); + } + + public function zrank($key, $member): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrank(...\func_get_args()); + } + + public function zrem($key, $value, ...$other_values): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyscore(...\func_get_args()); + } + + public function zrevrange($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrange(...\func_get_args()); + } + + public function zrevrangebylex($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebylex(...\func_get_args()); + } + + public function zrevrangebyscore($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebyscore(...\func_get_args()); + } + + public function zrevrank($key, $member): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrank(...\func_get_args()); + } + + public function zscan($key, &$iterator, $pattern = null, $count = 0): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscore($key, $member): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscore(...\func_get_args()); + } + + public function zmscore($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmscore(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $aggregate = null): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinter(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiffstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunion(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiff(...\func_get_args()); + } +} diff --git a/lib/symfony/cache/Traits/RedisClusterNodeProxy.php b/lib/symfony/cache/Traits/RedisClusterNodeProxy.php index deba74f6a..f5c0baa3e 100644 --- a/lib/symfony/cache/Traits/RedisClusterNodeProxy.php +++ b/lib/symfony/cache/Traits/RedisClusterNodeProxy.php @@ -24,16 +24,10 @@ namespace Symfony\Component\Cache\Traits; */ class RedisClusterNodeProxy { - private $host; - private $redis; - - /** - * @param \RedisCluster|RedisClusterProxy $redis - */ - public function __construct(array $host, $redis) - { - $this->host = $host; - $this->redis = $redis; + public function __construct( + private array $host, + private \RedisCluster $redis, + ) { } public function __call(string $method, array $args) diff --git a/lib/symfony/cache/Traits/RedisClusterProxy.php b/lib/symfony/cache/Traits/RedisClusterProxy.php index 73c6a4fdb..c67d5341c 100644 --- a/lib/symfony/cache/Traits/RedisClusterProxy.php +++ b/lib/symfony/cache/Traits/RedisClusterProxy.php @@ -11,53 +11,13 @@ namespace Symfony\Component\Cache\Traits; -/** - * @author Alessandro Chitolina - * - * @internal - */ -class RedisClusterProxy -{ - private $redis; - private $initializer; +class_alias(6.0 <= (float) phpversion('redis') ? RedisCluster6Proxy::class : RedisCluster5Proxy::class, RedisClusterProxy::class); - public function __construct(\Closure $initializer) +if (false) { + /** + * @internal + */ + class RedisClusterProxy extends \RedisCluster { - $this->initializer = $initializer; - } - - public function __call(string $method, array $args) - { - $this->redis ?: $this->redis = $this->initializer->__invoke(); - - return $this->redis->{$method}(...$args); - } - - public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) - { - $this->redis ?: $this->redis = $this->initializer->__invoke(); - - return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); - } - - public function scan(&$iIterator, $strPattern = null, $iCount = null) - { - $this->redis ?: $this->redis = $this->initializer->__invoke(); - - return $this->redis->scan($iIterator, $strPattern, $iCount); - } - - public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) - { - $this->redis ?: $this->redis = $this->initializer->__invoke(); - - return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); - } - - public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) - { - $this->redis ?: $this->redis = $this->initializer->__invoke(); - - return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); } } diff --git a/lib/symfony/cache/Traits/RedisProxy.php b/lib/symfony/cache/Traits/RedisProxy.php index ec5cfabb3..7f4537b15 100644 --- a/lib/symfony/cache/Traits/RedisProxy.php +++ b/lib/symfony/cache/Traits/RedisProxy.php @@ -11,55 +11,13 @@ namespace Symfony\Component\Cache\Traits; -/** - * @author Nicolas Grekas - * - * @internal - */ -class RedisProxy -{ - private $redis; - private $initializer; - private $ready = false; +class_alias(6.0 <= (float) phpversion('redis') ? Redis6Proxy::class : Redis5Proxy::class, RedisProxy::class); - public function __construct(\Redis $redis, \Closure $initializer) +if (false) { + /** + * @internal + */ + class RedisProxy extends \Redis { - $this->redis = $redis; - $this->initializer = $initializer; - } - - public function __call(string $method, array $args) - { - $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); - - return $this->redis->{$method}(...$args); - } - - public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) - { - $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); - - return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); - } - - public function scan(&$iIterator, $strPattern = null, $iCount = null) - { - $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); - - return $this->redis->scan($iIterator, $strPattern, $iCount); - } - - public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) - { - $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); - - return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); - } - - public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) - { - $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); - - return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); } } diff --git a/lib/symfony/cache/Traits/RedisTrait.php b/lib/symfony/cache/Traits/RedisTrait.php index accee44bc..4928db07f 100644 --- a/lib/symfony/cache/Traits/RedisTrait.php +++ b/lib/symfony/cache/Traits/RedisTrait.php @@ -15,8 +15,12 @@ use Predis\Command\Redis\UNLINK; use Predis\Connection\Aggregate\ClusterInterface; use Predis\Connection\Aggregate\RedisCluster; use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Connection\Cluster\ClusterInterface as Predis2ClusterInterface; +use Predis\Connection\Cluster\RedisCluster as Predis2RedisCluster; use Predis\Response\ErrorInterface; use Predis\Response\Status; +use Relay\Relay; +use Relay\Sentinel; use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Marshaller\DefaultMarshaller; @@ -30,7 +34,7 @@ use Symfony\Component\Cache\Marshaller\MarshallerInterface; */ trait RedisTrait { - private static $defaultConnectionOptions = [ + private static array $defaultConnectionOptions = [ 'class' => null, 'persistent' => 0, 'persistent_id' => null, @@ -45,13 +49,10 @@ trait RedisTrait 'failover' => 'none', 'ssl' => null, // see https://php.net/context.ssl ]; - private $redis; - private $marshaller; + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; + private MarshallerInterface $marshaller; - /** - * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis - */ - private function init($redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) + private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller): void { parent::__construct($namespace, $defaultLifetime); @@ -59,10 +60,6 @@ trait RedisTrait throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); } - if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \RedisCluster && !$redis instanceof \Predis\ClientInterface && !$redis instanceof RedisProxy && !$redis instanceof RedisClusterProxy) { - throw new InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis))); - } - if ($redis instanceof \Predis\ClientInterface && $redis->getOptions()->exceptions) { $options = clone $redis->getOptions(); \Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)(); @@ -85,27 +82,29 @@ trait RedisTrait * * @param array $options See self::$defaultConnectionOptions * - * @return \Redis|\RedisArray|\RedisCluster|RedisClusterProxy|RedisProxy|\Predis\ClientInterface According to the "class" option - * * @throws InvalidArgumentException when the DSN is invalid */ - public static function createConnection(string $dsn, array $options = []) + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay { if (str_starts_with($dsn, 'redis:')) { $scheme = 'redis'; } elseif (str_starts_with($dsn, 'rediss:')) { $scheme = 'rediss'; } else { - throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis:" or "rediss".', $dsn)); + throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:".'); } if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) { - throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: "%s".', $dsn)); + throw new CacheException('Cannot find the "redis" extension nor the "predis/predis" package.'); } - $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) { - if (isset($m[2])) { - $auth = $m[2]; + $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:(?[^:@]*+):)?(?[^@]*+)@)?#', function ($m) use (&$auth) { + if (isset($m['password'])) { + if (\in_array($m['user'], ['', 'default'], true)) { + $auth = rawurldecode($m['password']); + } else { + $auth = [rawurldecode($m['user']), rawurldecode($m['password'])]; + } if ('' === $auth) { $auth = null; @@ -116,7 +115,7 @@ trait RedisTrait }, $dsn); if (false === $params = parse_url($params)) { - throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + throw new InvalidArgumentException('Invalid Redis DSN.'); } $query = $hosts = []; @@ -129,7 +128,7 @@ trait RedisTrait if (isset($query['host'])) { if (!\is_array($hosts = $query['host'])) { - throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + throw new InvalidArgumentException('Invalid Redis DSN: query parameter "host" must be an array.'); } foreach ($hosts as $host => $parameters) { if (\is_string($parameters)) { @@ -149,11 +148,11 @@ trait RedisTrait if (isset($params['host']) || isset($params['path'])) { if (!isset($params['dbindex']) && isset($params['path'])) { - if (preg_match('#/(\d+)$#', $params['path'], $m)) { - $params['dbindex'] = $m[1]; + if (preg_match('#/(\d+)?$#', $params['path'], $m)) { + $params['dbindex'] = $m[1] ?? '0'; $params['path'] = substr($params['path'], 0, -\strlen($m[0])); } elseif (isset($params['host'])) { - throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s", the "dbindex" parameter must be a number.', $dsn)); + throw new InvalidArgumentException('Invalid Redis DSN: query parameter "dbindex" must be a number.'); } } @@ -165,38 +164,55 @@ trait RedisTrait } if (!$hosts) { - throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + throw new InvalidArgumentException('Invalid Redis DSN: missing host.'); } $params += $query + $options + self::$defaultConnectionOptions; - if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) { - throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher: "%s".', $dsn)); + if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".'); } + if (isset($params['lazy'])) { + $params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); + } + $params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN); + if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { - throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".', $dsn)); + throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); } - if (null === $params['class'] && \extension_loaded('redis')) { - $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) && !isset($params['redis_sentinel']) ? \RedisArray::class : \Redis::class); - } else { - $class = $params['class'] ?? \Predis\Client::class; + $class = $params['class'] ?? match (true) { + $params['redis_cluster'] => \extension_loaded('redis') ? \RedisCluster::class : \Predis\Client::class, + isset($params['redis_sentinel']) => match (true) { + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }, + 1 < \count($hosts) && \extension_loaded('redis') => 1 < \count($hosts) ? \RedisArray::class : \Redis::class, + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }; - if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class)) { - throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and ext-redis >= 5.2 not found: "%s".', $class, $dsn)); - } + if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and neither ext-redis >= 5.2 nor ext-relay have been found.', $class)); } - if (is_a($class, \Redis::class, true)) { + $isRedisExt = is_a($class, \Redis::class, true); + $isRelayExt = !$isRedisExt && is_a($class, Relay::class, true); + + if ($isRedisExt || $isRelayExt) { $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; - $redis = new $class(); - $initializer = static function ($redis) use ($connect, $params, $dsn, $auth, $hosts, $tls) { + $initializer = static function () use ($class, $isRedisExt, $connect, $params, $auth, $hosts, $tls) { + $sentinelClass = $isRedisExt ? \RedisSentinel::class : Sentinel::class; + $redis = new $class(); $hostIndex = 0; do { $host = $hosts[$hostIndex]['host'] ?? $hosts[$hostIndex]['path']; $port = $hosts[$hostIndex]['port'] ?? 0; + $passAuth = isset($params['auth']) && (!$isRedisExt || \defined('Redis::OPT_NULL_MULTIBULK_AS_NULL')); $address = false; if (isset($hosts[$hostIndex]['host']) && $tls) { @@ -207,19 +223,47 @@ trait RedisTrait break; } - $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout']); + try { + if (version_compare(phpversion('redis'), '6.0.0', '>=') && $isRedisExt) { + $options = [ + 'host' => $host, + 'port' => $port, + 'connectTimeout' => $params['timeout'], + 'persistent' => $params['persistent_id'], + 'retryInterval' => $params['retry_interval'], + 'readTimeout' => $params['read_timeout'], + ]; - if ($address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { - [$host, $port] = $address; + if ($passAuth) { + $options['auth'] = $params['auth']; + } + + $sentinel = new \RedisSentinel($options); + } else { + $extra = $passAuth ? [$params['auth']] : []; + + $sentinel = new $sentinelClass($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); + } + + if ($address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { + [$host, $port] = $address; + } + } catch (\RedisException|\Relay\Exception $redisException) { } } while (++$hostIndex < \count($hosts) && !$address); if (isset($params['redis_sentinel']) && !$address) { - throw new InvalidArgumentException(sprintf('Failed to retrieve master information from sentinel "%s" and dsn "%s".', $params['redis_sentinel'], $dsn)); + throw new InvalidArgumentException(sprintf('Failed to retrieve master information from sentinel "%s".', $params['redis_sentinel']), previous: $redisException ?? null); } try { - @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []); + $extra = [ + 'stream' => $params['ssl'] ?? null, + ]; + if (isset($params['auth'])) { + $extra['auth'] = $params['auth']; + } + @$redis->{$connect}($host, $port, (float) $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') || !$isRedisExt ? [$extra] : []); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { @@ -228,39 +272,39 @@ trait RedisTrait restore_error_handler(); } if (!$isConnected) { - $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : ''; - throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$error.'.'); + $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error ?? $redis->getLastError() ?? '', $error) ? sprintf(' (%s)', $error[1]) : ''; + throw new InvalidArgumentException('Redis connection failed: '.$error.'.'); } if ((null !== $auth && !$redis->auth($auth)) || ($params['dbindex'] && !$redis->select($params['dbindex'])) ) { $e = preg_replace('/^ERR /', '', $redis->getLastError()); - throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e.'.'); + throw new InvalidArgumentException('Redis connection failed: '.$e.'.'); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } - } catch (\RedisException $e) { - throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage()); + } catch (\RedisException|\Relay\Exception $e) { + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } - return true; + return $redis; }; if ($params['lazy']) { - $redis = new RedisProxy($redis, $initializer); + $redis = $isRedisExt ? RedisProxy::createLazyProxy($initializer) : RelayProxy::createLazyProxy($initializer); } else { - $initializer($redis); + $redis = $initializer(); } } elseif (is_a($class, \RedisArray::class, true)) { foreach ($hosts as $i => $host) { - switch ($host['scheme']) { - case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break; - case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break; - default: $hosts[$i] = $host['path']; - } + $hosts[$i] = match ($host['scheme']) { + 'tcp' => $host['host'].':'.$host['port'], + 'tls' => 'tls://'.$host['host'].':'.$host['port'], + default => $host['path'], + }; } $params['lazy_connect'] = $params['lazy'] ?? true; $params['connect_timeout'] = $params['timeout']; @@ -268,41 +312,42 @@ trait RedisTrait try { $redis = new $class($hosts, $params); } catch (\RedisClusterException $e) { - throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage()); + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } } elseif (is_a($class, \RedisCluster::class, true)) { - $initializer = static function () use ($class, $params, $dsn, $hosts) { + $initializer = static function () use ($isRedisExt, $class, $params, $hosts) { foreach ($hosts as $i => $host) { - switch ($host['scheme']) { - case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break; - case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break; - default: $hosts[$i] = $host['path']; - } + $hosts[$i] = match ($host['scheme']) { + 'tcp' => $host['host'].':'.$host['port'], + 'tls' => 'tls://'.$host['host'].':'.$host['port'], + default => $host['path'], + }; } try { $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []); } catch (\RedisClusterException $e) { - throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage()); + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); - } - switch ($params['failover']) { - case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break; - case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break; - case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break; + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } + $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, match ($params['failover']) { + 'error' => \RedisCluster::FAILOVER_ERROR, + 'distribute' => \RedisCluster::FAILOVER_DISTRIBUTE, + 'slaves' => \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES, + 'none' => \RedisCluster::FAILOVER_NONE, + }); return $redis; }; - $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer(); + $redis = $params['lazy'] ? RedisClusterProxy::createLazyProxy($initializer) : $initializer(); } elseif (is_a($class, \Predis\ClientInterface::class, true)) { if ($params['redis_cluster']) { $params['cluster'] = 'redis'; @@ -321,8 +366,21 @@ trait RedisTrait $params['parameters']['database'] = $params['dbindex']; } if (null !== $auth) { - $params['parameters']['password'] = $auth; + if (\is_array($auth)) { + // ACL + $params['parameters']['username'] = $auth[0]; + $params['parameters']['password'] = $auth[1]; + } else { + $params['parameters']['password'] = $auth; + } } + + if (isset($params['ssl'])) { + foreach ($hosts as $i => $host) { + $hosts[$i]['ssl'] ??= $params['ssl']; + } + } + if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) { $hosts = $hosts[0]; } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) { @@ -331,12 +389,12 @@ trait RedisTrait } $params['exceptions'] = false; - $redis = new $class($hosts, array_diff_key($params, array_diff_key(self::$defaultConnectionOptions, ['ssl' => null]))); + $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions)); if (isset($params['redis_sentinel'])) { $redis->getConnection()->setSentinelTimeout($params['timeout']); } } elseif (class_exists($class, false)) { - throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class)); + throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster", "Relay\Relay" nor "Predis\ClientInterface".', $class)); } else { throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); } @@ -344,10 +402,7 @@ trait RedisTrait return $redis; } - /** - * {@inheritdoc} - */ - protected function doFetch(array $ids) + protected function doFetch(array $ids): iterable { if (!$ids) { return []; @@ -355,7 +410,7 @@ trait RedisTrait $result = []; - if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { $values = $this->pipeline(function () use ($ids) { foreach ($ids as $id) { yield 'get' => [$id]; @@ -380,18 +435,12 @@ trait RedisTrait return $result; } - /** - * {@inheritdoc} - */ - protected function doHave(string $id) + protected function doHave(string $id): bool { return (bool) $this->redis->exists($id); } - /** - * {@inheritdoc} - */ - protected function doClear(string $namespace) + protected function doClear(string $namespace): bool { if ($this->redis instanceof \Predis\ClientInterface) { $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; @@ -415,7 +464,10 @@ trait RedisTrait $info = $host->info('Server'); $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0']; - if (!$host instanceof \Predis\ClientInterface) { + if ($host instanceof Relay) { + $prefix = Relay::SCAN_PREFIX & $host->getOption(Relay::OPT_SCAN) ? '' : $host->getOption(Relay::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(Relay::OPT_PREFIX) ?? ''); + } elseif (!$host instanceof \Predis\ClientInterface) { $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX); $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); } @@ -452,18 +504,15 @@ trait RedisTrait return $cleared; } - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) + protected function doDelete(array $ids): bool { if (!$ids) { return true; } - if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { static $del; - $del = $del ?? (class_exists(UNLINK::class) ? 'unlink' : 'del'); + $del ??= (class_exists(UNLINK::class) ? 'unlink' : 'del'); $this->pipeline(function () use ($ids, $del) { foreach ($ids as $id) { @@ -476,7 +525,7 @@ trait RedisTrait if ($unlink) { try { $unlink = false !== $this->redis->unlink($ids); - } catch (\Throwable $e) { + } catch (\Throwable) { $unlink = false; } } @@ -489,10 +538,7 @@ trait RedisTrait return true; } - /** - * {@inheritdoc} - */ - protected function doSave(array $values, int $lifetime) + protected function doSave(array $values, int $lifetime): array|bool { if (!$values = $this->marshaller->marshall($values, $failed)) { return $failed; @@ -520,9 +566,9 @@ trait RedisTrait private function pipeline(\Closure $generator, object $redis = null): \Generator { $ids = []; - $redis = $redis ?? $this->redis; + $redis ??= $this->redis; - if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) { + if ($redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && ($redis->getConnection() instanceof RedisCluster || $redis->getConnection() instanceof Predis2RedisCluster))) { // phpredis & predis don't support pipelining with RedisCluster // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 @@ -557,7 +603,7 @@ trait RedisTrait $results[$k] = $connections[$h][$c]; } } else { - $redis->multi(\Redis::PIPELINE); + $redis->multi($redis instanceof Relay ? Relay::PIPELINE : \Redis::PIPELINE); foreach ($generator() as $command => $args) { $redis->{$command}(...$args); $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; @@ -566,8 +612,12 @@ trait RedisTrait } if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { - $e = new \RedisException($redis->getLastError()); - $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, $results); + $e = $redis instanceof Relay ? new \Relay\Exception($redis->getLastError()) : new \RedisException($redis->getLastError()); + $results = array_map(fn ($v) => false === $v ? $e : $v, (array) $results); + } + + if (\is_bool($results)) { + return; } foreach ($ids as $k => $id) { @@ -580,7 +630,7 @@ trait RedisTrait $hosts = [$this->redis]; if ($this->redis instanceof \Predis\ClientInterface) { $connection = $this->redis->getConnection(); - if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) { + if (($connection instanceof ClusterInterface || $connection instanceof Predis2ClusterInterface) && $connection instanceof \Traversable) { $hosts = []; foreach ($connection as $c) { $hosts[] = new \Predis\Client($c); @@ -591,7 +641,7 @@ trait RedisTrait foreach ($this->redis->_hosts() as $host) { $hosts[] = $this->redis->_instance($host); } - } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) { + } elseif ($this->redis instanceof \RedisCluster) { $hosts = []; foreach ($this->redis->_masters() as $host) { $hosts[] = new RedisClusterNodeProxy($host, $this->redis); diff --git a/lib/symfony/cache/Traits/RelayProxy.php b/lib/symfony/cache/Traits/RelayProxy.php new file mode 100644 index 000000000..c55206ead --- /dev/null +++ b/lib/symfony/cache/Traits/RelayProxy.php @@ -0,0 +1,1313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RelayProxy extends \Relay\Relay implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($host = null, $port = 6379, $connect_timeout = 0.0, $command_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->connect(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pconnect(...\func_get_args()); + } + + public function close(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function pclose(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pclose(...\func_get_args()); + } + + public function listen($callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->listen(...\func_get_args()); + } + + public function onFlushed($callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->onFlushed(...\func_get_args()); + } + + public function onInvalidated($callback, $pattern = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->onInvalidated(...\func_get_args()); + } + + public function dispatchEvents(): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dispatchEvents(...\func_get_args()); + } + + public function getOption($option): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getOption(...\func_get_args()); + } + + public function option($option, $value = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->option(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setOption(...\func_get_args()); + } + + public function addIgnorePatterns(...$pattern): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->addIgnorePatterns(...\func_get_args()); + } + + public function addAllowPatterns(...$pattern): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->addAllowPatterns(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTimeout(...\func_get_args()); + } + + public function timeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->timeout(...\func_get_args()); + } + + public function getReadTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getReadTimeout(...\func_get_args()); + } + + public function readTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->readTimeout(...\func_get_args()); + } + + public function getBytes(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getBytes(...\func_get_args()); + } + + public function bytes(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bytes(...\func_get_args()); + } + + public function getHost(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getHost(...\func_get_args()); + } + + public function isConnected(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->isConnected(...\func_get_args()); + } + + public function getPort(): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPort(...\func_get_args()); + } + + public function getAuth(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getAuth(...\func_get_args()); + } + + public function getDbNum(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDbNum(...\func_get_args()); + } + + public function _serialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _compress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _pack($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function _prefix($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function getLastError(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getLastError(...\func_get_args()); + } + + public function clearLastError(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearLastError(...\func_get_args()); + } + + public function endpointId(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->endpointId(...\func_get_args()); + } + + public function getPersistentID(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPersistentID(...\func_get_args()); + } + + public function socketId(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->socketId(...\func_get_args()); + } + + public function rawCommand($cmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawCommand(...\func_get_args()); + } + + public function select($db): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->select(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $auth): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->auth(...\func_get_args()); + } + + public function info(...$sections): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function flushdb($async = false): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushdb(...\func_get_args()); + } + + public function flushall($async = false): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushall(...\func_get_args()); + } + + public function fcall($name, $keys = [], $argv = [], $handler = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall(...\func_get_args()); + } + + public function fcall_ro($name, $keys = [], $argv = [], $handler = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall_ro(...\func_get_args()); + } + + public function function($op, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->function(...\func_get_args()); + } + + public function dbsize(): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbsize(...\func_get_args()); + } + + public function dump($key): \Relay\Relay|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function replicaof($host = null, $port = 0): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->replicaof(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->migrate(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->copy(...\func_get_args()); + } + + public function echo($arg): \Relay\Relay|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function ping($arg = null): \Relay\Relay|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function idleTime(): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->idleTime(...\func_get_args()); + } + + public function randomkey(): \Relay\Relay|bool|null|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomkey(...\func_get_args()); + } + + public function time(): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function bgrewriteaof(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function lastsave(): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastsave(...\func_get_args()); + } + + public function bgsave($schedule = false): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgsave(...\func_get_args()); + } + + public function save(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function role(): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function ttl($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function pttl($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function exists(...$keys): \Relay\Relay|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval_ro(...\func_get_args()); + } + + public function evalsha($sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha_ro(...\func_get_args()); + } + + public function client($operation, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): \Relay\Relay|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getset($key, $value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function getrange($key, $start, $end): \Relay\Relay|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getrange(...\func_get_args()); + } + + public function setrange($key, $start, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setrange(...\func_get_args()); + } + + public function getbit($key, $pos): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getbit(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $by_bit = false): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitfield($key, ...$args): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitfield(...\func_get_args()); + } + + public function config($operation, $key = null, $value = null): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function command(...$args): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function bitop($operation, $dstkey, $srckey, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null, $bybit = false): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function setbit($key, $pos, $val): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setbit(...\func_get_args()); + } + + public function acl($cmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function set($key, $value, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function getex($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getex(...\func_get_args()); + } + + public function getdel($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getdel(...\func_get_args()); + } + + public function setex($key, $seconds, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function pfadd($key, $elements): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function psetex($key, $milliseconds, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function publish($channel, $message): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($operation, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function spublish($channel, $message): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spublish(...\func_get_args()); + } + + public function setnx($key, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function mget($keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function move($key, $db): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->move(...\func_get_args()); + } + + public function mset($kvals): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($kvals): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function rename($key, $newkey): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renamenx(...\func_get_args()); + } + + public function del(...$keys): \Relay\Relay|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function unlink(...$keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function expire($key, $seconds, $mode = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function pexpire($key, $milliseconds): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function expireat($key, $timestamp): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireat(...\func_get_args()); + } + + public function expiretime($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expiretime(...\func_get_args()); + } + + public function pexpireat($key, $timestamp_ms): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireat(...\func_get_args()); + } + + public function pexpiretime($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpiretime(...\func_get_args()); + } + + public function persist($key): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function type($key): \Relay\Relay|bool|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function lmove($srckey, $dstkey, $srcpos, $dstpos): \Relay\Relay|false|null|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmove(...\func_get_args()); + } + + public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): \Relay\Relay|false|null|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmove(...\func_get_args()); + } + + public function lrange($key, $start, $stop): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpush(...\func_get_args()); + } + + public function rpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpush(...\func_get_args()); + } + + public function lpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpushx(...\func_get_args()); + } + + public function rpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpushx(...\func_get_args()); + } + + public function lset($key, $index, $mem): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lset(...\func_get_args()); + } + + public function lpop($key, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Relay\Relay|array|false|int|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpos(...\func_get_args()); + } + + public function rpop($key, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpop(...\func_get_args()); + } + + public function rpoplpush($source, $dest): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function brpoplpush($source, $dest, $timeout): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmpop(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpop(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmin(...\func_get_args()); + } + + public function object($op, $key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function geopos($key, ...$members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function lrem($key, $mem, $count = 0): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function linsert($key, $op, $pivot, $element): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->linsert(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function hget($hash, $member): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hget(...\func_get_args()); + } + + public function hstrlen($hash, $member): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hstrlen(...\func_get_args()); + } + + public function hgetall($hash): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetall(...\func_get_args()); + } + + public function hkeys($hash): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hkeys(...\func_get_args()); + } + + public function hvals($hash): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hvals(...\func_get_args()); + } + + public function hmget($hash, $members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmget(...\func_get_args()); + } + + public function hrandfield($hash, $options = null): \Relay\Relay|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hrandfield(...\func_get_args()); + } + + public function hmset($hash, $members): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmset(...\func_get_args()); + } + + public function hexists($hash, $member): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexists(...\func_get_args()); + } + + public function hsetnx($hash, $member, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hsetnx(...\func_get_args()); + } + + public function hset($key, $mem, $val, ...$kvals): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hset(...\func_get_args()); + } + + public function hdel($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hdel(...\func_get_args()); + } + + public function hincrby($key, $mem, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $mem, $value): \Relay\Relay|bool|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrbyfloat(...\func_get_args()); + } + + public function incr($key, $by = 1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function decr($key, $by = 1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function incrby($key, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrby(...\func_get_args()); + } + + public function decrby($key, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrbyfloat(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiff(...\func_get_args()); + } + + public function sdiffstore($key, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiffstore(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinterstore(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunion(...\func_get_args()); + } + + public function sunionstore($key, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunionstore(...\func_get_args()); + } + + public function subscribe($channels, $callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function unsubscribe($channels = []): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function psubscribe($patterns, $callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function punsubscribe($patterns = []): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function ssubscribe($channels, $callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ssubscribe(...\func_get_args()); + } + + public function sunsubscribe($channels = []): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunsubscribe(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->touch(...\func_get_args()); + } + + public function pipeline(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pipeline(...\func_get_args()); + } + + public function multi($mode = 0): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function exec(): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function wait($replicas, $timeout): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function unwatch(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function discard(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function getMode($masked = false): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMode(...\func_get_args()); + } + + public function clearBytes(): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearBytes(...\func_get_args()); + } + + public function scan(&$iterator, $match = null, $count = 0, $type = null): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function hscan($key, &$iterator, $match = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function sscan($key, &$iterator, $match = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscan($key, &$iterator, $match = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function keys($pattern): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function slowlog($operation, ...$extra_args): \Relay\Relay|array|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function smembers($set): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smembers(...\func_get_args()); + } + + public function sismember($set, $member): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function smismember($set, ...$members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smismember(...\func_get_args()); + } + + public function srem($set, $member, ...$members): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sadd($set, $member, ...$members): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sadd(...\func_get_args()); + } + + public function sort($key, $options = []): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = []): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort_ro(...\func_get_args()); + } + + public function smove($srcset, $dstset, $member): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smove(...\func_get_args()); + } + + public function spop($set, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spop(...\func_get_args()); + } + + public function srandmember($set, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srandmember(...\func_get_args()); + } + + public function scard($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function strlen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function hlen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hlen(...\func_get_args()); + } + + public function llen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->llen(...\func_get_args()); + } + + public function xack($key, $group, $ids): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Relay\Relay|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xautoclaim(...\func_get_args()); + } + + public function xlen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xdel($key, $ids): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null, $idle = 0): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Relay\Relay|array|bool|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Relay\Relay|array|bool|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zadd($key, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zadd(...\func_get_args()); + } + + public function zrandmember($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrandmember(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrange(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrange(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebyscore(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebyscore(...\func_get_args()); + } + + public function zrangestore($dst, $src, $start, $end, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangestore(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebylex(...\func_get_args()); + } + + public function zrevrangebylex($key, $max, $min, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebylex(...\func_get_args()); + } + + public function zrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrank(...\func_get_args()); + } + + public function zrevrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrank(...\func_get_args()); + } + + public function zrem($key, ...$args): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $start, $end): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyscore(...\func_get_args()); + } + + public function zcard($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcount(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiffstore(...\func_get_args()); + } + + public function zincrby($key, $score, $mem): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zincrby(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zlexcount(...\func_get_args()); + } + + public function zmscore($key, ...$mems): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmscore(...\func_get_args()); + } + + public function zscore($key, $member): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } + + public function zpopmin($key, $count = 1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmin(...\func_get_args()); + } + + public function zpopmax($key, $count = 1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmax(...\func_get_args()); + } + + public function _getKeys() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_getKeys(...\func_get_args()); + } +} diff --git a/lib/symfony/cache/Traits/ValueWrapper.php b/lib/symfony/cache/Traits/ValueWrapper.php new file mode 100644 index 000000000..718a23d39 --- /dev/null +++ b/lib/symfony/cache/Traits/ValueWrapper.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * A short namespace-less class to serialize items with metadata. + * + * @author Nicolas Grekas + * + * @internal + */ +class © +{ + private const EXPIRY_OFFSET = 1648206727; + private const INT32_MAX = 2147483647; + + public readonly mixed $value; + public readonly array $metadata; + + public function __construct(mixed $value, array $metadata) + { + $this->value = $value; + $this->metadata = $metadata; + } + + public function __serialize(): array + { + // pack 31-bits ctime into 14bits + $c = $this->metadata['ctime'] ?? 0; + $c = match (true) { + $c > self::INT32_MAX - 2 => self::INT32_MAX, + $c > 0 => 1 + $c, + default => 1, + }; + $e = 0; + while (!(0x40000000 & $c)) { + $c <<= 1; + ++$e; + } + $c = (0x7FE0 & ($c >> 16)) | $e; + + $pack = pack('Vn', (int) (0.1 + ($this->metadata['expiry'] ?: self::INT32_MAX + self::EXPIRY_OFFSET) - self::EXPIRY_OFFSET), $c); + + if (isset($this->metadata['tags'])) { + $pack[4] = $pack[4] | "\x80"; + } + + return [$pack => $this->value] + ($this->metadata['tags'] ?? []); + } + + public function __unserialize(array $data): void + { + $pack = array_key_first($data); + $this->value = $data[$pack]; + + if ($hasTags = "\x80" === ($pack[4] & "\x80")) { + unset($data[$pack]); + $pack[4] = $pack[4] & "\x7F"; + } + + $metadata = unpack('Vexpiry/nctime', $pack); + $metadata['expiry'] += self::EXPIRY_OFFSET; + + if (!$metadata['ctime'] = ((0x4000 | $metadata['ctime']) << 16 >> (0x1F & $metadata['ctime'])) - 1) { + unset($metadata['ctime']); + } + + if ($hasTags) { + $metadata['tags'] = $data; + } + + $this->metadata = $metadata; + } +} diff --git a/lib/symfony/cache/composer.json b/lib/symfony/cache/composer.json index 1ebcfc9b3..af743b463 100644 --- a/lib/symfony/cache/composer.json +++ b/lib/symfony/cache/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/cache", "type": "library", - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", "keywords": ["caching", "psr6"], "homepage": "https://symfony.com", "license": "MIT", @@ -16,42 +16,41 @@ } ], "provide": { - "psr/cache-implementation": "1.0|2.0", - "psr/simple-cache-implementation": "1.0|2.0", - "symfony/cache-implementation": "1.0|2.0" + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0", + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.3.6|^7.0" }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6|^2.0", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "conflict": { "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "/Tests/" ] diff --git a/lib/symfony/config/Builder/ClassBuilder.php b/lib/symfony/config/Builder/ClassBuilder.php index 9960d6508..8194a1526 100644 --- a/lib/symfony/config/Builder/ClassBuilder.php +++ b/lib/symfony/config/Builder/ClassBuilder.php @@ -20,21 +20,18 @@ namespace Symfony\Component\Config\Builder; */ class ClassBuilder { - /** @var string */ - private $namespace; - - /** @var string */ - private $name; + private string $namespace; + private string $name; /** @var Property[] */ - private $properties = []; + private array $properties = []; /** @var Method[] */ - private $methods = []; - private $require = []; - private $use = []; - private $implements = []; - private $allowExtraKeys = false; + private array $methods = []; + private array $require = []; + private array $use = []; + private array $implements = []; + private bool $allowExtraKeys = false; public function __construct(string $namespace, string $name) { diff --git a/lib/symfony/config/Builder/ConfigBuilderGenerator.php b/lib/symfony/config/Builder/ConfigBuilderGenerator.php index c63917bcb..d43d814eb 100644 --- a/lib/symfony/config/Builder/ConfigBuilderGenerator.php +++ b/lib/symfony/config/Builder/ConfigBuilderGenerator.php @@ -12,7 +12,9 @@ namespace Symfony\Component\Config\Builder; use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\BaseNode; use Symfony\Component\Config\Definition\BooleanNode; +use Symfony\Component\Config\Definition\Builder\ExprBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\EnumNode; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; @@ -34,8 +36,8 @@ class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface /** * @var ClassBuilder[] */ - private $classes; - private $outputDir; + private array $classes = []; + private string $outputDir; public function __construct(string $outputDir) { @@ -67,14 +69,12 @@ public function NAME(): string $this->writeClasses(); } - $loader = \Closure::fromCallable(function () use ($path, $rootClass) { + return function () use ($path, $rootClass) { require_once $path; $className = $rootClass->getFqcn(); return new $className(); - }); - - return $loader; + }; } private function getFullPath(ClassBuilder $class): string @@ -110,22 +110,13 @@ public function NAME(): string } foreach ($node->getChildren() as $child) { - switch (true) { - case $child instanceof ScalarNode: - $this->handleScalarNode($child, $class); - break; - case $child instanceof PrototypedArrayNode: - $this->handlePrototypedArrayNode($child, $class, $namespace); - break; - case $child instanceof VariableNode: - $this->handleVariableNode($child, $class); - break; - case $child instanceof ArrayNode: - $this->handleArrayNode($child, $class, $namespace); - break; - default: - throw new \RuntimeException(sprintf('Unknown node "%s".', \get_class($child))); - } + match (true) { + $child instanceof ScalarNode => $this->handleScalarNode($child, $class), + $child instanceof PrototypedArrayNode => $this->handlePrototypedArrayNode($child, $class, $namespace), + $child instanceof VariableNode => $this->handleVariableNode($child, $class), + $child instanceof ArrayNode => $this->handleArrayNode($child, $class, $namespace), + default => throw new \RuntimeException(sprintf('Unknown node "%s".', $child::class)), + }; } } @@ -137,15 +128,23 @@ public function NAME(): string $this->classes[] = $childClass; $hasNormalizationClosures = $this->hasNormalizationClosures($node); + $comment = $this->getComment($node); + if ($hasNormalizationClosures) { + $comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); + $comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); + $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); + } + if ('' !== $comment) { + $comment = "/**\n$comment*/\n"; + } + $property = $class->addProperty( $node->getName(), $this->getType($childClass->getFqcn(), $hasNormalizationClosures) ); + $nodeTypes = $this->getParameterTypes($node); $body = $hasNormalizationClosures ? ' -/** - * @return CLASS|$this - */ -public function NAME($value = []) +COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static { if (!\is_array($value)) { $this->_usedProperties[\'PROPERTY\'] = true; @@ -163,7 +162,7 @@ public function NAME($value = []) return $this->PROPERTY; }' : ' -public function NAME(array $value = []): CLASS +COMMENTpublic function NAME(array $value = []): CLASS { if (null === $this->PROPERTY) { $this->_usedProperties[\'PROPERTY\'] = true; @@ -175,7 +174,12 @@ public function NAME(array $value = []): CLASS return $this->PROPERTY; }'; $class->addUse(InvalidConfigurationException::class); - $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]); + $class->addMethod($node->getName(), $body, [ + 'COMMENT' => $comment, + 'PROPERTY' => $property->getName(), + 'CLASS' => $childClass->getFqcn(), + 'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes), + ]); $this->buildNode($node, $childClass, $this->getSubNamespace($childClass)); } @@ -188,16 +192,21 @@ public function NAME(array $value = []): CLASS $body = ' /** -COMMENT * @return $this +COMMENT * + * @return $this */ -public function NAME($valueDEFAULT): self +public function NAME(mixed $valueDEFAULT): static { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; }'; - $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '']); + $class->addMethod($node->getName(), $body, [ + 'PROPERTY' => $property->getName(), + 'COMMENT' => $comment, + 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '', + ]); } private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace): void @@ -205,19 +214,23 @@ public function NAME($valueDEFAULT): self $name = $this->getSingularName($node); $prototype = $node->getPrototype(); $methodName = $name; + $hasNormalizationClosures = $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype); - $parameterType = $this->getParameterType($prototype); - if (null !== $parameterType || $prototype instanceof ScalarNode) { + $nodeParameterTypes = $this->getParameterTypes($node); + $prototypeParameterTypes = $this->getParameterTypes($prototype); + if (!$prototype instanceof ArrayNode || ($prototype instanceof PrototypedArrayNode && $prototype->getPrototype() instanceof ScalarNode)) { $class->addUse(ParamConfigurator::class); $property = $class->addProperty($node->getName()); if (null === $key = $node->getKeyAttribute()) { // This is an array of values; don't use singular name + $nodeTypesWithoutArray = array_filter($nodeParameterTypes, static fn ($type) => 'array' !== $type); $body = ' /** - * @param ParamConfigurator|list $value + * @param ParamConfigurator|listEXTRA_TYPE $value + * * @return $this */ -public function NAME($value): self +public function NAME(PARAM_TYPE $value): static { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; @@ -225,14 +238,18 @@ public function NAME($value): self return $this; }'; - $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType]); + $class->addMethod($node->getName(), $body, [ + 'PROPERTY' => $property->getName(), + 'PROTOTYPE_TYPE' => implode('|', $prototypeParameterTypes), + 'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '', + 'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $nodeParameterTypes), + ]); } else { $body = ' /** - * @param ParamConfigurator|TYPE $value * @return $this */ -public function NAME(string $VAR, $VALUE): self +public function NAME(string $VAR, TYPE $VALUE): static { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY[$VAR] = $VALUE; @@ -240,7 +257,12 @@ public function NAME(string $VAR, $VALUE): self return $this; }'; - $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType, 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']); + $class->addMethod($methodName, $body, [ + 'PROPERTY' => $property->getName(), + 'TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $prototypeParameterTypes), + 'VAR' => '' === $key ? 'key' : $key, + 'VALUE' => 'value' === $key ? 'data' : 'value', + ]); } return; @@ -253,18 +275,24 @@ public function NAME(string $VAR, $VALUE): self $class->addRequire($childClass); $this->classes[] = $childClass; - $hasNormalizationClosures = $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype); $property = $class->addProperty( $node->getName(), $this->getType($childClass->getFqcn().'[]', $hasNormalizationClosures) ); + $comment = $this->getComment($node); + if ($hasNormalizationClosures) { + $comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); + $comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); + $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); + } + if ('' !== $comment) { + $comment = "/**\n$comment*/\n"; + } + if (null === $key = $node->getKeyAttribute()) { $body = $hasNormalizationClosures ? ' -/** - * @return CLASS|$this - */ -public function NAME($value = []) +COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static { $this->_usedProperties[\'PROPERTY\'] = true; if (!\is_array($value)) { @@ -275,19 +303,21 @@ public function NAME($value = []) return $this->PROPERTY[] = new CLASS($value); }' : ' -public function NAME(array $value = []): CLASS +COMMENTpublic function NAME(array $value = []): CLASS { $this->_usedProperties[\'PROPERTY\'] = true; return $this->PROPERTY[] = new CLASS($value); }'; - $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]); + $class->addMethod($methodName, $body, [ + 'COMMENT' => $comment, + 'PROPERTY' => $property->getName(), + 'CLASS' => $childClass->getFqcn(), + 'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : implode('|', $nodeParameterTypes), + ]); } else { $body = $hasNormalizationClosures ? ' -/** - * @return CLASS|$this - */ -public function NAME(string $VAR, $VALUE = []) +COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static { if (!\is_array($VALUE)) { $this->_usedProperties[\'PROPERTY\'] = true; @@ -305,7 +335,7 @@ public function NAME(string $VAR, $VALUE = []) return $this->PROPERTY[$VAR]; }' : ' -public function NAME(string $VAR, array $VALUE = []): CLASS +COMMENTpublic function NAME(string $VAR, array $VALUE = []): CLASS { if (!isset($this->PROPERTY[$VAR])) { $this->_usedProperties[\'PROPERTY\'] = true; @@ -317,7 +347,13 @@ public function NAME(string $VAR, array $VALUE = []): CLASS return $this->PROPERTY[$VAR]; }'; $class->addUse(InvalidConfigurationException::class); - $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']); + $class->addMethod($methodName, str_replace('$value', '$VAR', $body), [ + 'COMMENT' => $comment, 'PROPERTY' => $property->getName(), + 'CLASS' => $childClass->getFqcn(), + 'VAR' => '' === $key ? 'key' : $key, + 'VALUE' => 'value' === $key ? 'data' : 'value', + 'PARAM_TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : implode('|', $prototypeParameterTypes), + ]); } $this->buildNode($prototype, $childClass, $namespace.'\\'.$childClass->getName()); @@ -333,7 +369,7 @@ public function NAME(string $VAR, array $VALUE = []): CLASS /** COMMENT * @return $this */ -public function NAME($value): self +public function NAME($value): static { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; @@ -344,62 +380,65 @@ public function NAME($value): self $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]); } - private function getParameterType(NodeInterface $node): ?string + private function getParameterTypes(NodeInterface $node): array { + $paramTypes = []; + if ($node instanceof BaseNode) { + $types = $node->getNormalizedTypes(); + if (\in_array(ExprBuilder::TYPE_ANY, $types, true)) { + $paramTypes[] = 'mixed'; + } + if (\in_array(ExprBuilder::TYPE_STRING, $types, true)) { + $paramTypes[] = 'string'; + } + } if ($node instanceof BooleanNode) { - return 'bool'; + $paramTypes[] = 'bool'; + } elseif ($node instanceof IntegerNode) { + $paramTypes[] = 'int'; + } elseif ($node instanceof FloatNode) { + $paramTypes[] = 'float'; + } elseif ($node instanceof EnumNode) { + $paramTypes[] = 'mixed'; + } elseif ($node instanceof ArrayNode) { + $paramTypes[] = 'array'; + } elseif ($node instanceof VariableNode) { + $paramTypes[] = 'mixed'; } - if ($node instanceof IntegerNode) { - return 'int'; - } - - if ($node instanceof FloatNode) { - return 'float'; - } - - if ($node instanceof EnumNode) { - return ''; - } - - if ($node instanceof PrototypedArrayNode && $node->getPrototype() instanceof ScalarNode) { - // This is just an array of variables - return 'array'; - } - - if ($node instanceof VariableNode) { - // mixed - return ''; - } - - return null; + return array_unique($paramTypes); } - private function getComment(VariableNode $node): string + private function getComment(BaseNode $node): string { $comment = ''; if ('' !== $info = (string) $node->getInfo()) { $comment .= ' * '.$info."\n"; } - foreach ((array) ($node->getExample() ?? []) as $example) { - $comment .= ' * @example '.$example."\n"; - } - - if ('' !== $default = $node->getDefaultValue()) { - $comment .= ' * @default '.(null === $default ? 'null' : var_export($default, true))."\n"; - } - - if ($node instanceof EnumNode) { - $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_map(function ($a) { - return var_export($a, true); - }, $node->getValues())))."\n"; - } else { - $parameterType = $this->getParameterType($node); - if (null === $parameterType || '' === $parameterType) { - $parameterType = 'mixed'; + if (!$node instanceof ArrayNode) { + foreach ((array) ($node->getExample() ?? []) as $example) { + $comment .= ' * @example '.$example."\n"; + } + + if ('' !== $default = $node->getDefaultValue()) { + $comment .= ' * @default '.(null === $default ? 'null' : var_export($default, true))."\n"; + } + + if ($node instanceof EnumNode) { + $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => !$a instanceof \UnitEnum ? var_export($a, true) : '\\'.ltrim(var_export($a, true), '\\'), $node->getValues()))))."\n"; + } else { + $parameterTypes = $this->getParameterTypes($node); + $comment .= ' * @param ParamConfigurator|'.implode('|', $parameterTypes).' $value'."\n"; + } + } else { + foreach ((array) ($node->getExample() ?? []) as $example) { + $comment .= ' * @example '.json_encode($example)."\n"; + } + + if ($node->hasDefaultValue() && [] != $default = $node->getDefaultValue()) { + $comment .= ' * @default '.json_encode($default)."\n"; } - $comment .= ' * @param ParamConfigurator|'.$parameterType.' $value'."\n"; } if ($node->isDeprecated()) { @@ -415,7 +454,7 @@ public function NAME($value): self private function getSingularName(PrototypedArrayNode $node): string { $name = $node->getName(); - if ('s' !== substr($name, -1)) { + if (!str_ends_with($name, 's')) { return $name; } @@ -439,8 +478,8 @@ public function NAME($value): self if (null !== $p->getType()) { if ($p->isArray()) { $code = $p->areScalarsAllowed() - ? 'array_map(function ($v) { return $v instanceof CLASS ? $v->toArray() : $v; }, $this->PROPERTY)' - : 'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY)' + ? 'array_map(fn ($v) => $v instanceof CLASS ? $v->toArray() : $v, $this->PROPERTY)' + : 'array_map(fn ($v) => $v->toArray(), $this->PROPERTY)' ; } else { $code = $p->areScalarsAllowed() @@ -475,8 +514,8 @@ public function NAME(): array if (null !== $p->getType()) { if ($p->isArray()) { $code = $p->areScalarsAllowed() - ? 'array_map(function ($v) { return \is_array($v) ? new '.$p->getType().'($v) : $v; }, $value[\'ORG_NAME\'])' - : 'array_map(function ($v) { return new '.$p->getType().'($v); }, $value[\'ORG_NAME\'])' + ? 'array_map(fn ($v) => \is_array($v) ? new '.$p->getType().'($v) : $v, $value[\'ORG_NAME\'])' + : 'array_map(fn ($v) => new '.$p->getType().'($v), $value[\'ORG_NAME\'])' ; } else { $code = $p->areScalarsAllowed() @@ -527,9 +566,10 @@ public function __construct(array $value = []) $class->addMethod('set', ' /** * @param ParamConfigurator|mixed $value + * * @return $this */ -public function NAME(string $key, $value): self +public function NAME(string $key, mixed $value): static { $this->_extraKeys[$key] = $value; @@ -546,10 +586,9 @@ public function NAME(string $key, $value): self { try { $r = new \ReflectionProperty($node, 'normalizationClosures'); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { return false; } - $r->setAccessible(true); return [] !== $r->getValue($node); } diff --git a/lib/symfony/config/Builder/Method.php b/lib/symfony/config/Builder/Method.php index 3577e3d7a..c97c98649 100644 --- a/lib/symfony/config/Builder/Method.php +++ b/lib/symfony/config/Builder/Method.php @@ -20,7 +20,7 @@ namespace Symfony\Component\Config\Builder; */ class Method { - private $content; + private string $content; public function __construct(string $content) { diff --git a/lib/symfony/config/Builder/Property.php b/lib/symfony/config/Builder/Property.php index 5a6f7e31b..cf2f8d549 100644 --- a/lib/symfony/config/Builder/Property.php +++ b/lib/symfony/config/Builder/Property.php @@ -20,12 +20,12 @@ namespace Symfony\Component\Config\Builder; */ class Property { - private $name; - private $originalName; - private $array = false; - private $scalarsAllowed = false; - private $type = null; - private $content; + private string $name; + private string $originalName; + private bool $array = false; + private bool $scalarsAllowed = false; + private ?string $type = null; + private ?string $content = null; public function __construct(string $originalName, string $name) { @@ -48,12 +48,12 @@ class Property $this->array = false; $this->type = $type; - if ('|scalar' === substr($type, -7)) { + if (str_ends_with($type, '|scalar')) { $this->scalarsAllowed = true; $this->type = $type = substr($type, 0, -7); } - if ('[]' === substr($type, -2)) { + if (str_ends_with($type, '[]')) { $this->array = true; $this->type = substr($type, 0, -2); } diff --git a/lib/symfony/config/CHANGELOG.md b/lib/symfony/config/CHANGELOG.md index 75ef8bc44..094d5abba 100644 --- a/lib/symfony/config/CHANGELOG.md +++ b/lib/symfony/config/CHANGELOG.md @@ -1,6 +1,30 @@ CHANGELOG ========= +6.3 +--- + + * Allow enum values in `EnumNode` + +6.2 +--- + + * Deprecate calling `NodeBuilder::setParent()` without any arguments + * Add a more accurate typehint in generated PHP config + +6.1 +--- + + * Allow using environment variables in `EnumNode` + * Add Node's information in generated Config + * Add `DefinitionFileLoader` class to load a TreeBuilder definition from an external file + * Add `DefinitionConfigurator` helper + +6.0 +--- + + * Remove `BaseNode::getDeprecationMessage()` + 5.3.0 ----- diff --git a/lib/symfony/config/ConfigCache.php b/lib/symfony/config/ConfigCache.php index 3b090525e..89519417d 100644 --- a/lib/symfony/config/ConfigCache.php +++ b/lib/symfony/config/ConfigCache.php @@ -25,7 +25,7 @@ use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; */ class ConfigCache extends ResourceCheckerConfigCache { - private $debug; + private bool $debug; /** * @param string $file The absolute cache path @@ -48,10 +48,8 @@ class ConfigCache extends ResourceCheckerConfigCache * * This implementation always returns true when debug is off and the * cache file exists. - * - * @return bool */ - public function isFresh() + public function isFresh(): bool { if (!$this->debug && is_file($this->getPath())) { return true; diff --git a/lib/symfony/config/ConfigCacheFactory.php b/lib/symfony/config/ConfigCacheFactory.php index 11fd3cb3a..39adad1e1 100644 --- a/lib/symfony/config/ConfigCacheFactory.php +++ b/lib/symfony/config/ConfigCacheFactory.php @@ -22,7 +22,7 @@ namespace Symfony\Component\Config; */ class ConfigCacheFactory implements ConfigCacheFactoryInterface { - private $debug; + private bool $debug; /** * @param bool $debug The debug flag to pass to ConfigCache @@ -32,10 +32,7 @@ class ConfigCacheFactory implements ConfigCacheFactoryInterface $this->debug = $debug; } - /** - * {@inheritdoc} - */ - public function cache(string $file, callable $callback) + public function cache(string $file, callable $callback): ConfigCacheInterface { $cache = new ConfigCache($file, $this->debug); if (!$cache->isFresh()) { diff --git a/lib/symfony/config/ConfigCacheFactoryInterface.php b/lib/symfony/config/ConfigCacheFactoryInterface.php index 146ee9b56..01c90d1e3 100644 --- a/lib/symfony/config/ConfigCacheFactoryInterface.php +++ b/lib/symfony/config/ConfigCacheFactoryInterface.php @@ -25,8 +25,6 @@ interface ConfigCacheFactoryInterface * * @param string $file The absolute cache file path * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback - * - * @return ConfigCacheInterface */ - public function cache(string $file, callable $callable); + public function cache(string $file, callable $callable): ConfigCacheInterface; } diff --git a/lib/symfony/config/ConfigCacheInterface.php b/lib/symfony/config/ConfigCacheInterface.php index 3cd7a5cc0..be7f0986c 100644 --- a/lib/symfony/config/ConfigCacheInterface.php +++ b/lib/symfony/config/ConfigCacheInterface.php @@ -22,19 +22,15 @@ interface ConfigCacheInterface { /** * Gets the cache file path. - * - * @return string */ - public function getPath(); + public function getPath(): string; /** * Checks if the cache is still fresh. * * This check should take the metadata passed to the write() method into consideration. - * - * @return bool */ - public function isFresh(); + public function isFresh(): bool; /** * Writes the given content into the cache file. Metadata will be stored @@ -43,6 +39,8 @@ interface ConfigCacheInterface * @param string $content The content to write into the cache * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances * + * @return void + * * @throws \RuntimeException When the cache file cannot be written */ public function write(string $content, array $metadata = null); diff --git a/lib/symfony/config/Definition/ArrayNode.php b/lib/symfony/config/Definition/ArrayNode.php index bd0eae9fd..1448220cd 100644 --- a/lib/symfony/config/Definition/ArrayNode.php +++ b/lib/symfony/config/Definition/ArrayNode.php @@ -32,21 +32,22 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface protected $removeExtraKeys = true; protected $normalizeKeys = true; + /** + * @return void + */ public function setNormalizeKeys(bool $normalizeKeys) { $this->normalizeKeys = $normalizeKeys; } /** - * {@inheritdoc} - * * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML. * After running this method, all keys are normalized to foo_bar. * * If you have a mixed key like foo-bar_moo, it will not be altered. * The key will also not be altered if the target key already exists. */ - protected function preNormalize($value) + protected function preNormalize(mixed $value): mixed { if (!$this->normalizeKeys || !\is_array($value)) { return $value; @@ -70,7 +71,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface * * @return array */ - public function getChildren() + public function getChildren(): array { return $this->children; } @@ -79,6 +80,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface * Sets the xml remappings that should be performed. * * @param array $remappings An array of the form [[string, string]] + * + * @return void */ public function setXmlRemappings(array $remappings) { @@ -90,7 +93,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface * * @return array an array of the form [[string, string]] */ - public function getXmlRemappings() + public function getXmlRemappings(): array { return $this->xmlRemappings; } @@ -98,6 +101,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface /** * Sets whether to add default values for this array if it has not been * defined in any of the configuration files. + * + * @return void */ public function setAddIfNotSet(bool $boolean) { @@ -106,6 +111,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface /** * Sets whether false is allowed as value indicating that the array should be unset. + * + * @return void */ public function setAllowFalse(bool $allow) { @@ -114,6 +121,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface /** * Sets whether new keys can be defined in subsequent configurations. + * + * @return void */ public function setAllowNewKeys(bool $allow) { @@ -122,6 +131,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface /** * Sets if deep merging should occur. + * + * @return void */ public function setPerformDeepMerging(bool $boolean) { @@ -133,6 +144,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface * * @param bool $boolean To allow extra keys * @param bool $remove To remove extra keys + * + * @return void */ public function setIgnoreExtraKeys(bool $boolean, bool $remove = true) { @@ -149,25 +162,19 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface } /** - * {@inheritdoc} + * @return void */ public function setName(string $name) { $this->name = $name; } - /** - * {@inheritdoc} - */ - public function hasDefaultValue() + public function hasDefaultValue(): bool { return $this->addIfNotSet; } - /** - * {@inheritdoc} - */ - public function getDefaultValue() + public function getDefaultValue(): mixed { if (!$this->hasDefaultValue()) { throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath())); @@ -186,6 +193,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface /** * Adds a child node. * + * @return void + * * @throws \InvalidArgumentException when the child node has no name * @throws \InvalidArgumentException when the child node's name is not unique */ @@ -203,12 +212,10 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface } /** - * {@inheritdoc} - * * @throws UnsetKeyException * @throws InvalidConfigurationException if the node doesn't have enough children */ - protected function finalizeValue($value) + protected function finalizeValue(mixed $value): mixed { if (false === $value) { throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); @@ -243,7 +250,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface try { $value[$name] = $child->finalize($value[$name]); - } catch (UnsetKeyException $e) { + } catch (UnsetKeyException) { unset($value[$name]); } } @@ -252,9 +259,9 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface } /** - * {@inheritdoc} + * @return void */ - protected function validateType($value) + protected function validateType(mixed $value) { if (!\is_array($value) && (!$this->allowFalse || false !== $value)) { $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), get_debug_type($value))); @@ -268,11 +275,9 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface } /** - * {@inheritdoc} - * * @throws InvalidConfigurationException */ - protected function normalizeValue($value) + protected function normalizeValue(mixed $value): mixed { if (false === $value) { return $value; @@ -285,7 +290,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface if (isset($this->children[$name])) { try { $normalized[$name] = $this->children[$name]->normalize($val); - } catch (UnsetKeyException $e) { + } catch (UnsetKeyException) { } unset($value[$name]); } elseif (!$this->removeExtraKeys) { @@ -330,10 +335,8 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface /** * Remaps multiple singular values to a single plural value. - * - * @return array */ - protected function remapXml(array $value) + protected function remapXml(array $value): array { foreach ($this->xmlRemappings as [$singular, $plural]) { if (!isset($value[$singular])) { @@ -348,12 +351,10 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface } /** - * {@inheritdoc} - * * @throws InvalidConfigurationException * @throws \RuntimeException */ - protected function mergeValues($leftSide, $rightSide) + protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed { if (false === $rightSide) { // if this is still false after the last config has been merged the @@ -394,9 +395,6 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface return $leftSide; } - /** - * {@inheritdoc} - */ protected function allowPlaceholders(): bool { return false; diff --git a/lib/symfony/config/Definition/BaseNode.php b/lib/symfony/config/Definition/BaseNode.php index 673cfaf60..85f0f7eeb 100644 --- a/lib/symfony/config/Definition/BaseNode.php +++ b/lib/symfony/config/Definition/BaseNode.php @@ -26,12 +26,13 @@ abstract class BaseNode implements NodeInterface { public const DEFAULT_PATH_SEPARATOR = '.'; - private static $placeholderUniquePrefixes = []; - private static $placeholders = []; + private static array $placeholderUniquePrefixes = []; + private static array $placeholders = []; protected $name; protected $parent; protected $normalizationClosures = []; + protected $normalizedTypes = []; protected $finalValidationClosures = []; protected $allowOverwrite = true; protected $required = false; @@ -40,7 +41,7 @@ abstract class BaseNode implements NodeInterface protected $attributes = []; protected $pathSeparator; - private $handlingPlaceholder; + private mixed $handlingPlaceholder = null; /** * @throws \InvalidArgumentException if the name contains a period @@ -97,40 +98,40 @@ abstract class BaseNode implements NodeInterface self::$placeholders = []; } - public function setAttribute(string $key, $value) + /** + * @return void + */ + public function setAttribute(string $key, mixed $value) { $this->attributes[$key] = $value; } - /** - * @return mixed - */ - public function getAttribute(string $key, $default = null) + public function getAttribute(string $key, mixed $default = null): mixed { return $this->attributes[$key] ?? $default; } - /** - * @return bool - */ - public function hasAttribute(string $key) + public function hasAttribute(string $key): bool { return isset($this->attributes[$key]); } - /** - * @return array - */ - public function getAttributes() + public function getAttributes(): array { return $this->attributes; } + /** + * @return void + */ public function setAttributes(array $attributes) { $this->attributes = $attributes; } + /** + * @return void + */ public function removeAttribute(string $key) { unset($this->attributes[$key]); @@ -138,6 +139,8 @@ abstract class BaseNode implements NodeInterface /** * Sets an info message. + * + * @return void */ public function setInfo(string $info) { @@ -146,10 +149,8 @@ abstract class BaseNode implements NodeInterface /** * Returns info message. - * - * @return string|null */ - public function getInfo() + public function getInfo(): ?string { return $this->getAttribute('info'); } @@ -157,19 +158,17 @@ abstract class BaseNode implements NodeInterface /** * Sets the example configuration for this node. * - * @param string|array $example + * @return void */ - public function setExample($example) + public function setExample(string|array $example) { $this->setAttribute('example', $example); } /** * Retrieves the example configuration for this node. - * - * @return string|array|null */ - public function getExample() + public function getExample(): string|array|null { return $this->getAttribute('example'); } @@ -177,16 +176,17 @@ abstract class BaseNode implements NodeInterface /** * Adds an equivalent value. * - * @param mixed $originalValue - * @param mixed $equivalentValue + * @return void */ - public function addEquivalentValue($originalValue, $equivalentValue) + public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue) { $this->equivalentValues[] = [$originalValue, $equivalentValue]; } /** * Set this node as required. + * + * @return void */ public function setRequired(bool $boolean) { @@ -196,36 +196,17 @@ abstract class BaseNode implements NodeInterface /** * Sets this node as deprecated. * + * You can use %node% and %path% placeholders in your message to display, + * respectively, the node name and its complete path. + * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message the deprecation message to use * - * You can use %node% and %path% placeholders in your message to display, - * respectively, the node name and its complete path + * @return void */ - public function setDeprecated(?string $package/* , string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */) + public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.') { - $args = \func_get_args(); - - if (\func_num_args() < 2) { - trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); - - if (!isset($args[0])) { - trigger_deprecation('symfony/config', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); - - $this->deprecation = []; - - return; - } - - $message = (string) $args[0]; - $package = $version = ''; - } else { - $package = (string) $args[0]; - $version = (string) $args[1]; - $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.'); - } - $this->deprecation = [ 'package' => $package, 'version' => $version, @@ -235,6 +216,8 @@ abstract class BaseNode implements NodeInterface /** * Sets if this node can be overridden. + * + * @return void */ public function setAllowOverwrite(bool $allow) { @@ -245,57 +228,61 @@ abstract class BaseNode implements NodeInterface * Sets the closures used for normalization. * * @param \Closure[] $closures An array of Closures used for normalization + * + * @return void */ public function setNormalizationClosures(array $closures) { $this->normalizationClosures = $closures; } + /** + * Sets the list of types supported by normalization. + * + * see ExprBuilder::TYPE_* constants. + * + * @return void + */ + public function setNormalizedTypes(array $types) + { + $this->normalizedTypes = $types; + } + + /** + * Gets the list of types supported by normalization. + * + * see ExprBuilder::TYPE_* constants. + */ + public function getNormalizedTypes(): array + { + return $this->normalizedTypes; + } + /** * Sets the closures used for final validation. * * @param \Closure[] $closures An array of Closures used for final validation + * + * @return void */ public function setFinalValidationClosures(array $closures) { $this->finalValidationClosures = $closures; } - /** - * {@inheritdoc} - */ - public function isRequired() + public function isRequired(): bool { return $this->required; } /** * Checks if this node is deprecated. - * - * @return bool */ - public function isDeprecated() + public function isDeprecated(): bool { return (bool) $this->deprecation; } - /** - * Returns the deprecated message. - * - * @param string $node the configuration node name - * @param string $path the path of the node - * - * @return string - * - * @deprecated since Symfony 5.1, use "getDeprecation()" instead. - */ - public function getDeprecationMessage(string $node, string $path) - { - trigger_deprecation('symfony/config', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); - - return $this->getDeprecation($node, $path)['message']; - } - /** * @param string $node The configuration node name * @param string $path The path of the node @@ -303,24 +290,18 @@ abstract class BaseNode implements NodeInterface public function getDeprecation(string $node, string $path): array { return [ - 'package' => $this->deprecation['package'] ?? '', - 'version' => $this->deprecation['version'] ?? '', - 'message' => strtr($this->deprecation['message'] ?? '', ['%node%' => $node, '%path%' => $path]), + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => strtr($this->deprecation['message'], ['%node%' => $node, '%path%' => $path]), ]; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->name; } - /** - * {@inheritdoc} - */ - public function getPath() + public function getPath(): string { if (null !== $this->parent) { return $this->parent->getPath().$this->pathSeparator.$this->name; @@ -329,10 +310,7 @@ abstract class BaseNode implements NodeInterface return $this->name; } - /** - * {@inheritdoc} - */ - final public function merge($leftSide, $rightSide) + final public function merge(mixed $leftSide, mixed $rightSide): mixed { if (!$this->allowOverwrite) { throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath())); @@ -370,10 +348,7 @@ abstract class BaseNode implements NodeInterface return $this->mergeValues($leftSide, $rightSide); } - /** - * {@inheritdoc} - */ - final public function normalize($value) + final public function normalize(mixed $value): mixed { $value = $this->preNormalize($value); @@ -412,30 +387,21 @@ abstract class BaseNode implements NodeInterface /** * Normalizes the value before any other normalization is applied. - * - * @param mixed $value - * - * @return mixed */ - protected function preNormalize($value) + protected function preNormalize(mixed $value): mixed { return $value; } /** * Returns parent node for this node. - * - * @return NodeInterface|null */ - public function getParent() + public function getParent(): ?NodeInterface { return $this->parent; } - /** - * {@inheritdoc} - */ - final public function finalize($value) + final public function finalize(mixed $value): mixed { if ($value !== $placeholders = self::resolvePlaceholderValue($value)) { foreach ($placeholders as $placeholder) { @@ -476,39 +442,26 @@ abstract class BaseNode implements NodeInterface /** * Validates the type of a Node. * - * @param mixed $value The value to validate + * @return void * * @throws InvalidTypeException when the value is invalid */ - abstract protected function validateType($value); + abstract protected function validateType(mixed $value); /** * Normalizes the value. - * - * @param mixed $value The value to normalize - * - * @return mixed */ - abstract protected function normalizeValue($value); + abstract protected function normalizeValue(mixed $value): mixed; /** * Merges two values together. - * - * @param mixed $leftSide - * @param mixed $rightSide - * - * @return mixed */ - abstract protected function mergeValues($leftSide, $rightSide); + abstract protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed; /** * Finalizes a value. - * - * @param mixed $value The value to finalize - * - * @return mixed */ - abstract protected function finalizeValue($value); + abstract protected function finalizeValue(mixed $value): mixed; /** * Tests if placeholder values are allowed for this node. @@ -534,7 +487,7 @@ abstract class BaseNode implements NodeInterface return []; } - private static function resolvePlaceholderValue($value) + private static function resolvePlaceholderValue(mixed $value): mixed { if (\is_string($value)) { if (isset(self::$placeholders[$value])) { @@ -551,7 +504,7 @@ abstract class BaseNode implements NodeInterface return $value; } - private function doValidateType($value): void + private function doValidateType(mixed $value): void { if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) { $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath())); diff --git a/lib/symfony/config/Definition/BooleanNode.php b/lib/symfony/config/Definition/BooleanNode.php index c64ecb839..7ec903cd6 100644 --- a/lib/symfony/config/Definition/BooleanNode.php +++ b/lib/symfony/config/Definition/BooleanNode.php @@ -21,9 +21,9 @@ use Symfony\Component\Config\Definition\Exception\InvalidTypeException; class BooleanNode extends ScalarNode { /** - * {@inheritdoc} + * @return void */ - protected function validateType($value) + protected function validateType(mixed $value) { if (!\is_bool($value)) { $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), get_debug_type($value))); @@ -36,18 +36,12 @@ class BooleanNode extends ScalarNode } } - /** - * {@inheritdoc} - */ - protected function isValueEmpty($value) + protected function isValueEmpty(mixed $value): bool { // a boolean value cannot be empty return false; } - /** - * {@inheritdoc} - */ protected function getValidPlaceholderTypes(): array { return ['bool']; diff --git a/lib/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/lib/symfony/config/Definition/Builder/ArrayNodeDefinition.php index eb5b04021..3ada5c550 100644 --- a/lib/symfony/config/Definition/Builder/ArrayNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/ArrayNodeDefinition.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Config\Definition\Builder; use Symfony\Component\Config\Definition\ArrayNode; use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; +use Symfony\Component\Config\Definition\NodeInterface; use Symfony\Component\Config\Definition\PrototypedArrayNode; /** @@ -36,9 +37,6 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition protected $nodeBuilder; protected $normalizeKeys = true; - /** - * {@inheritdoc} - */ public function __construct(?string $name, NodeParentInterface $parent = null) { parent::__construct($name, $parent); @@ -48,83 +46,57 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition } /** - * {@inheritdoc} + * @return void */ public function setBuilder(NodeBuilder $builder) { $this->nodeBuilder = $builder; } - /** - * {@inheritdoc} - */ - public function children() + public function children(): NodeBuilder { return $this->getNodeBuilder(); } /** * Sets a prototype for child nodes. - * - * @return NodeDefinition */ - public function prototype(string $type) + public function prototype(string $type): NodeDefinition { return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this); } - /** - * @return VariableNodeDefinition - */ - public function variablePrototype() + public function variablePrototype(): VariableNodeDefinition { return $this->prototype('variable'); } - /** - * @return ScalarNodeDefinition - */ - public function scalarPrototype() + public function scalarPrototype(): ScalarNodeDefinition { return $this->prototype('scalar'); } - /** - * @return BooleanNodeDefinition - */ - public function booleanPrototype() + public function booleanPrototype(): BooleanNodeDefinition { return $this->prototype('boolean'); } - /** - * @return IntegerNodeDefinition - */ - public function integerPrototype() + public function integerPrototype(): IntegerNodeDefinition { return $this->prototype('integer'); } - /** - * @return FloatNodeDefinition - */ - public function floatPrototype() + public function floatPrototype(): FloatNodeDefinition { return $this->prototype('float'); } - /** - * @return ArrayNodeDefinition - */ - public function arrayPrototype() + public function arrayPrototype(): self { return $this->prototype('array'); } - /** - * @return EnumNodeDefinition - */ - public function enumPrototype() + public function enumPrototype(): EnumNodeDefinition { return $this->prototype('enum'); } @@ -138,7 +110,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function addDefaultsIfNotSet() + public function addDefaultsIfNotSet(): static { $this->addDefaults = true; @@ -154,7 +126,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function addDefaultChildrenIfNoneSet($children = null) + public function addDefaultChildrenIfNoneSet(int|string|array $children = null): static { $this->addDefaultChildren = $children; @@ -168,7 +140,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function requiresAtLeastOneElement() + public function requiresAtLeastOneElement(): static { $this->atLeastOne = true; @@ -182,7 +154,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function disallowNewKeysInSubsequentConfigs() + public function disallowNewKeysInSubsequentConfigs(): static { $this->allowNewKeys = false; @@ -197,7 +169,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function fixXmlConfig(string $singular, string $plural = null) + public function fixXmlConfig(string $singular, string $plural = null): static { $this->normalization()->remap($singular, $plural); @@ -232,7 +204,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function useAttributeAsKey(string $name, bool $removeKeyItem = true) + public function useAttributeAsKey(string $name, bool $removeKeyItem = true): static { $this->key = $name; $this->removeKeyItem = $removeKeyItem; @@ -245,7 +217,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function canBeUnset(bool $allow = true) + public function canBeUnset(bool $allow = true): static { $this->merge()->allowUnset($allow); @@ -267,7 +239,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function canBeEnabled() + public function canBeEnabled(): static { $this ->addDefaultsIfNotSet() @@ -277,7 +249,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition ->beforeNormalization() ->ifArray() ->then(function (array $v) { - $v['enabled'] = $v['enabled'] ?? true; + $v['enabled'] ??= true; return $v; }) @@ -297,7 +269,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function canBeDisabled() + public function canBeDisabled(): static { $this ->addDefaultsIfNotSet() @@ -317,7 +289,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function performNoDeepMerging() + public function performNoDeepMerging(): static { $this->performDeepMerging = false; @@ -337,7 +309,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function ignoreExtraKeys(bool $remove = true) + public function ignoreExtraKeys(bool $remove = true): static { $this->ignoreExtraKeys = true; $this->removeExtraKeys = $remove; @@ -350,17 +322,14 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition * * @return $this */ - public function normalizeKeys(bool $bool) + public function normalizeKeys(bool $bool): static { $this->normalizeKeys = $bool; return $this; } - /** - * {@inheritdoc} - */ - public function append(NodeDefinition $node) + public function append(NodeDefinition $node): static { $this->children[$node->name] = $node->setParent($this); @@ -369,24 +338,17 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition /** * Returns a node builder to be used to add children and prototype. - * - * @return NodeBuilder */ - protected function getNodeBuilder() + protected function getNodeBuilder(): NodeBuilder { - if (null === $this->nodeBuilder) { - $this->nodeBuilder = new NodeBuilder(); - } + $this->nodeBuilder ??= new NodeBuilder(); return $this->nodeBuilder->setParent($this); } - /** - * {@inheritdoc} - */ - protected function createNode() + protected function createNode(): NodeInterface { - if (null === $this->prototype) { + if (!isset($this->prototype)) { $node = new ArrayNode($this->name, $this->parent, $this->pathSeparator); $this->validateConcreteNode($node); @@ -420,7 +382,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition if (false !== $this->addDefaultChildren) { $node->setAddChildrenIfNoneSet($this->addDefaultChildren); - if ($this->prototype instanceof static && null === $this->prototype->prototype) { + if ($this->prototype instanceof static && !isset($this->prototype->prototype)) { $this->prototype->addDefaultsIfNotSet(); } } @@ -442,17 +404,18 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']); } - if (null !== $this->normalization) { + if (isset($this->normalization)) { $node->setNormalizationClosures($this->normalization->before); + $node->setNormalizedTypes($this->normalization->declaredTypes); $node->setXmlRemappings($this->normalization->remappings); } - if (null !== $this->merge) { + if (isset($this->merge)) { $node->setAllowOverwrite($this->merge->allowOverwrite); $node->setAllowFalse($this->merge->allowFalse); } - if (null !== $this->validation) { + if (isset($this->validation)) { $node->setFinalValidationClosures($this->validation->rules); } @@ -462,6 +425,8 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition /** * Validate the configuration of a concrete node. * + * @return void + * * @throws InvalidDefinitionException */ protected function validateConcreteNode(ArrayNode $node) @@ -492,6 +457,8 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition /** * Validate the configuration of a prototype node. * + * @return void + * * @throws InvalidDefinitionException */ protected function validatePrototypeNode(PrototypedArrayNode $node) @@ -520,7 +487,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition /** * @return NodeDefinition[] */ - public function getChildNodeDefinitions() + public function getChildNodeDefinitions(): array { return $this->children; } diff --git a/lib/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/lib/symfony/config/Definition/Builder/BooleanNodeDefinition.php index ace0b34a2..3d8fad4d5 100644 --- a/lib/symfony/config/Definition/Builder/BooleanNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/BooleanNodeDefinition.php @@ -21,9 +21,6 @@ use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; */ class BooleanNodeDefinition extends ScalarNodeDefinition { - /** - * {@inheritdoc} - */ public function __construct(?string $name, NodeParentInterface $parent = null) { parent::__construct($name, $parent); @@ -33,20 +30,16 @@ class BooleanNodeDefinition extends ScalarNodeDefinition /** * Instantiate a Node. - * - * @return BooleanNode */ - protected function instantiateNode() + protected function instantiateNode(): BooleanNode { return new BooleanNode($this->name, $this->parent, $this->pathSeparator); } /** - * {@inheritdoc} - * * @throws InvalidDefinitionException */ - public function cannotBeEmpty() + public function cannotBeEmpty(): static { throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.'); } diff --git a/lib/symfony/config/Definition/Builder/BuilderAwareInterface.php b/lib/symfony/config/Definition/Builder/BuilderAwareInterface.php index f30b8736c..bb40307e1 100644 --- a/lib/symfony/config/Definition/Builder/BuilderAwareInterface.php +++ b/lib/symfony/config/Definition/Builder/BuilderAwareInterface.php @@ -20,6 +20,8 @@ interface BuilderAwareInterface { /** * Sets a custom children builder. + * + * @return void */ public function setBuilder(NodeBuilder $builder); } diff --git a/lib/symfony/config/Definition/Builder/EnumNodeDefinition.php b/lib/symfony/config/Definition/Builder/EnumNodeDefinition.php index 52e2fd111..99f318123 100644 --- a/lib/symfony/config/Definition/Builder/EnumNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/EnumNodeDefinition.php @@ -20,16 +20,14 @@ use Symfony\Component\Config\Definition\EnumNode; */ class EnumNodeDefinition extends ScalarNodeDefinition { - private $values; + private array $values; /** * @return $this */ - public function values(array $values) + public function values(array $values): static { - $values = array_unique($values); - - if (empty($values)) { + if (!$values) { throw new \InvalidArgumentException('->values() must be called with at least one value.'); } @@ -41,13 +39,11 @@ class EnumNodeDefinition extends ScalarNodeDefinition /** * Instantiate a Node. * - * @return EnumNode - * * @throws \RuntimeException */ - protected function instantiateNode() + protected function instantiateNode(): EnumNode { - if (null === $this->values) { + if (!isset($this->values)) { throw new \RuntimeException('You must call ->values() on enum nodes.'); } diff --git a/lib/symfony/config/Definition/Builder/ExprBuilder.php b/lib/symfony/config/Definition/Builder/ExprBuilder.php index 14387b51b..9cb441481 100644 --- a/lib/symfony/config/Definition/Builder/ExprBuilder.php +++ b/lib/symfony/config/Definition/Builder/ExprBuilder.php @@ -21,7 +21,14 @@ use Symfony\Component\Config\Definition\Exception\UnsetKeyException; */ class ExprBuilder { + public const TYPE_ANY = 'any'; + public const TYPE_STRING = 'string'; + public const TYPE_NULL = 'null'; + public const TYPE_ARRAY = 'array'; + protected $node; + + public $allowedTypes; public $ifPart; public $thenPart; @@ -35,9 +42,10 @@ class ExprBuilder * * @return $this */ - public function always(\Closure $then = null) + public function always(\Closure $then = null): static { - $this->ifPart = function () { return true; }; + $this->ifPart = static fn () => true; + $this->allowedTypes = self::TYPE_ANY; if (null !== $then) { $this->thenPart = $then; @@ -53,13 +61,10 @@ class ExprBuilder * * @return $this */ - public function ifTrue(\Closure $closure = null) + public function ifTrue(\Closure $closure = null): static { - if (null === $closure) { - $closure = function ($v) { return true === $v; }; - } - - $this->ifPart = $closure; + $this->ifPart = $closure ?? static fn ($v) => true === $v; + $this->allowedTypes = self::TYPE_ANY; return $this; } @@ -69,9 +74,10 @@ class ExprBuilder * * @return $this */ - public function ifString() + public function ifString(): static { - $this->ifPart = function ($v) { return \is_string($v); }; + $this->ifPart = \is_string(...); + $this->allowedTypes = self::TYPE_STRING; return $this; } @@ -81,9 +87,10 @@ class ExprBuilder * * @return $this */ - public function ifNull() + public function ifNull(): static { - $this->ifPart = function ($v) { return null === $v; }; + $this->ifPart = \is_null(...); + $this->allowedTypes = self::TYPE_NULL; return $this; } @@ -93,9 +100,10 @@ class ExprBuilder * * @return $this */ - public function ifEmpty() + public function ifEmpty(): static { - $this->ifPart = function ($v) { return empty($v); }; + $this->ifPart = static fn ($v) => empty($v); + $this->allowedTypes = self::TYPE_ANY; return $this; } @@ -105,9 +113,10 @@ class ExprBuilder * * @return $this */ - public function ifArray() + public function ifArray(): static { - $this->ifPart = function ($v) { return \is_array($v); }; + $this->ifPart = \is_array(...); + $this->allowedTypes = self::TYPE_ARRAY; return $this; } @@ -117,9 +126,10 @@ class ExprBuilder * * @return $this */ - public function ifInArray(array $array) + public function ifInArray(array $array): static { - $this->ifPart = function ($v) use ($array) { return \in_array($v, $array, true); }; + $this->ifPart = static fn ($v) => \in_array($v, $array, true); + $this->allowedTypes = self::TYPE_ANY; return $this; } @@ -129,9 +139,10 @@ class ExprBuilder * * @return $this */ - public function ifNotInArray(array $array) + public function ifNotInArray(array $array): static { - $this->ifPart = function ($v) use ($array) { return !\in_array($v, $array, true); }; + $this->ifPart = static fn ($v) => !\in_array($v, $array, true); + $this->allowedTypes = self::TYPE_ANY; return $this; } @@ -141,10 +152,11 @@ class ExprBuilder * * @return $this */ - public function castToArray() + public function castToArray(): static { - $this->ifPart = function ($v) { return !\is_array($v); }; - $this->thenPart = function ($v) { return [$v]; }; + $this->ifPart = static fn ($v) => !\is_array($v); + $this->allowedTypes = self::TYPE_ANY; + $this->thenPart = static fn ($v) => [$v]; return $this; } @@ -154,7 +166,7 @@ class ExprBuilder * * @return $this */ - public function then(\Closure $closure) + public function then(\Closure $closure): static { $this->thenPart = $closure; @@ -166,9 +178,9 @@ class ExprBuilder * * @return $this */ - public function thenEmptyArray() + public function thenEmptyArray(): static { - $this->thenPart = function () { return []; }; + $this->thenPart = static fn () => []; return $this; } @@ -182,9 +194,9 @@ class ExprBuilder * * @throws \InvalidArgumentException */ - public function thenInvalid(string $message) + public function thenInvalid(string $message): static { - $this->thenPart = function ($v) use ($message) { throw new \InvalidArgumentException(sprintf($message, json_encode($v))); }; + $this->thenPart = static fn ($v) => throw new \InvalidArgumentException(sprintf($message, json_encode($v))); return $this; } @@ -196,9 +208,9 @@ class ExprBuilder * * @throws UnsetKeyException */ - public function thenUnset() + public function thenUnset(): static { - $this->thenPart = function () { throw new UnsetKeyException('Unsetting key.'); }; + $this->thenPart = static fn () => throw new UnsetKeyException('Unsetting key.'); return $this; } @@ -206,11 +218,9 @@ class ExprBuilder /** * Returns the related node. * - * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition - * * @throws \RuntimeException */ - public function end() + public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition { if (null === $this->ifPart) { throw new \RuntimeException('You must specify an if part.'); @@ -226,18 +236,14 @@ class ExprBuilder * Builds the expressions. * * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build - * - * @return array */ - public static function buildExpressions(array $expressions) + public static function buildExpressions(array $expressions): array { foreach ($expressions as $k => $expr) { if ($expr instanceof self) { $if = $expr->ifPart; $then = $expr->thenPart; - $expressions[$k] = function ($v) use ($if, $then) { - return $if($v) ? $then($v) : $v; - }; + $expressions[$k] = static fn ($v) => $if($v) ? $then($v) : $v; } } diff --git a/lib/symfony/config/Definition/Builder/FloatNodeDefinition.php b/lib/symfony/config/Definition/Builder/FloatNodeDefinition.php index f50f190f8..337e971bf 100644 --- a/lib/symfony/config/Definition/Builder/FloatNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/FloatNodeDefinition.php @@ -22,10 +22,8 @@ class FloatNodeDefinition extends NumericNodeDefinition { /** * Instantiates a Node. - * - * @return FloatNode */ - protected function instantiateNode() + protected function instantiateNode(): FloatNode { return new FloatNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator); } diff --git a/lib/symfony/config/Definition/Builder/IntegerNodeDefinition.php b/lib/symfony/config/Definition/Builder/IntegerNodeDefinition.php index d28e5aecb..2af81df47 100644 --- a/lib/symfony/config/Definition/Builder/IntegerNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/IntegerNodeDefinition.php @@ -22,10 +22,8 @@ class IntegerNodeDefinition extends NumericNodeDefinition { /** * Instantiates a Node. - * - * @return IntegerNode */ - protected function instantiateNode() + protected function instantiateNode(): IntegerNode { return new IntegerNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator); } diff --git a/lib/symfony/config/Definition/Builder/MergeBuilder.php b/lib/symfony/config/Definition/Builder/MergeBuilder.php index a88d49ba9..f8980a6e0 100644 --- a/lib/symfony/config/Definition/Builder/MergeBuilder.php +++ b/lib/symfony/config/Definition/Builder/MergeBuilder.php @@ -32,7 +32,7 @@ class MergeBuilder * * @return $this */ - public function allowUnset(bool $allow = true) + public function allowUnset(bool $allow = true): static { $this->allowFalse = $allow; @@ -44,7 +44,7 @@ class MergeBuilder * * @return $this */ - public function denyOverwrite(bool $deny = true) + public function denyOverwrite(bool $deny = true): static { $this->allowOverwrite = !$deny; @@ -53,10 +53,8 @@ class MergeBuilder /** * Returns the related node. - * - * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition */ - public function end() + public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition { return $this->node; } diff --git a/lib/symfony/config/Definition/Builder/NodeBuilder.php b/lib/symfony/config/Definition/Builder/NodeBuilder.php index 245e97277..7cda0bc7d 100644 --- a/lib/symfony/config/Definition/Builder/NodeBuilder.php +++ b/lib/symfony/config/Definition/Builder/NodeBuilder.php @@ -39,8 +39,11 @@ class NodeBuilder implements NodeParentInterface * * @return $this */ - public function setParent(ParentNodeDefinitionInterface $parent = null) + public function setParent(ParentNodeDefinitionInterface $parent = null): static { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/form', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } $this->parent = $parent; return $this; @@ -48,70 +51,56 @@ class NodeBuilder implements NodeParentInterface /** * Creates a child array node. - * - * @return ArrayNodeDefinition */ - public function arrayNode(string $name) + public function arrayNode(string $name): ArrayNodeDefinition { return $this->node($name, 'array'); } /** * Creates a child scalar node. - * - * @return ScalarNodeDefinition */ - public function scalarNode(string $name) + public function scalarNode(string $name): ScalarNodeDefinition { return $this->node($name, 'scalar'); } /** * Creates a child Boolean node. - * - * @return BooleanNodeDefinition */ - public function booleanNode(string $name) + public function booleanNode(string $name): BooleanNodeDefinition { return $this->node($name, 'boolean'); } /** * Creates a child integer node. - * - * @return IntegerNodeDefinition */ - public function integerNode(string $name) + public function integerNode(string $name): IntegerNodeDefinition { return $this->node($name, 'integer'); } /** * Creates a child float node. - * - * @return FloatNodeDefinition */ - public function floatNode(string $name) + public function floatNode(string $name): FloatNodeDefinition { return $this->node($name, 'float'); } /** * Creates a child EnumNode. - * - * @return EnumNodeDefinition */ - public function enumNode(string $name) + public function enumNode(string $name): EnumNodeDefinition { return $this->node($name, 'enum'); } /** * Creates a child variable node. - * - * @return VariableNodeDefinition */ - public function variableNode(string $name) + public function variableNode(string $name): VariableNodeDefinition { return $this->node($name, 'variable'); } @@ -129,12 +118,10 @@ class NodeBuilder implements NodeParentInterface /** * Creates a child node. * - * @return NodeDefinition - * * @throws \RuntimeException When the node type is not registered * @throws \RuntimeException When the node class is not found */ - public function node(?string $name, string $type) + public function node(?string $name, string $type): NodeDefinition { $class = $this->getNodeClass($type); @@ -160,7 +147,7 @@ class NodeBuilder implements NodeParentInterface * * @return $this */ - public function append(NodeDefinition $node) + public function append(NodeDefinition $node): static { if ($node instanceof BuilderAwareInterface) { $builder = clone $this; @@ -185,7 +172,7 @@ class NodeBuilder implements NodeParentInterface * * @return $this */ - public function setNodeClass(string $type, string $class) + public function setNodeClass(string $type, string $class): static { $this->nodeMapping[strtolower($type)] = $class; @@ -195,12 +182,10 @@ class NodeBuilder implements NodeParentInterface /** * Returns the class name of the node definition. * - * @return string - * * @throws \RuntimeException When the node type is not registered * @throws \RuntimeException When the node class is not found */ - protected function getNodeClass(string $type) + protected function getNodeClass(string $type): string { $type = strtolower($type); diff --git a/lib/symfony/config/Definition/Builder/NodeDefinition.php b/lib/symfony/config/Definition/Builder/NodeDefinition.php index cf153f01c..2c93a0f7d 100644 --- a/lib/symfony/config/Definition/Builder/NodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/NodeDefinition.php @@ -49,7 +49,7 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function setParent(NodeParentInterface $parent) + public function setParent(NodeParentInterface $parent): static { $this->parent = $parent; @@ -61,7 +61,7 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function info(string $info) + public function info(string $info): static { return $this->attribute('info', $info); } @@ -69,11 +69,9 @@ abstract class NodeDefinition implements NodeParentInterface /** * Sets example configuration. * - * @param string|array $example - * * @return $this */ - public function example($example) + public function example(string|array $example): static { return $this->attribute('example', $example); } @@ -81,11 +79,9 @@ abstract class NodeDefinition implements NodeParentInterface /** * Sets an attribute on the node. * - * @param mixed $value - * * @return $this */ - public function attribute(string $key, $value) + public function attribute(string $key, mixed $value): static { $this->attributes[$key] = $value; @@ -94,30 +90,32 @@ abstract class NodeDefinition implements NodeParentInterface /** * Returns the parent node. - * - * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null */ - public function end() + public function end(): NodeParentInterface|NodeBuilder|self|ArrayNodeDefinition|VariableNodeDefinition|null { return $this->parent; } /** * Creates the node. - * - * @return NodeInterface */ - public function getNode(bool $forceRootNode = false) + public function getNode(bool $forceRootNode = false): NodeInterface { if ($forceRootNode) { $this->parent = null; } - if (null !== $this->normalization) { + if (isset($this->normalization)) { + $allowedTypes = []; + foreach ($this->normalization->before as $expr) { + $allowedTypes[] = $expr->allowedTypes; + } + $allowedTypes = array_unique($allowedTypes); $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before); + $this->normalization->declaredTypes = $allowedTypes; } - if (null !== $this->validation) { + if (isset($this->validation)) { $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules); } @@ -132,11 +130,9 @@ abstract class NodeDefinition implements NodeParentInterface /** * Sets the default value. * - * @param mixed $value The default value - * * @return $this */ - public function defaultValue($value) + public function defaultValue(mixed $value): static { $this->default = true; $this->defaultValue = $value; @@ -149,7 +145,7 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function isRequired() + public function isRequired(): static { $this->required = true; @@ -168,21 +164,8 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function setDeprecated(/* string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */) + public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.'): static { - $args = \func_get_args(); - - if (\func_num_args() < 2) { - trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); - - $message = $args[0] ?? 'The child node "%node%" at path "%path%" is deprecated.'; - $package = $version = ''; - } else { - $package = (string) $args[0]; - $version = (string) $args[1]; - $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.'); - } - $this->deprecation = [ 'package' => $package, 'version' => $version, @@ -195,11 +178,9 @@ abstract class NodeDefinition implements NodeParentInterface /** * Sets the equivalent value used when the node contains null. * - * @param mixed $value - * * @return $this */ - public function treatNullLike($value) + public function treatNullLike(mixed $value): static { $this->nullEquivalent = $value; @@ -209,11 +190,9 @@ abstract class NodeDefinition implements NodeParentInterface /** * Sets the equivalent value used when the node contains true. * - * @param mixed $value - * * @return $this */ - public function treatTrueLike($value) + public function treatTrueLike(mixed $value): static { $this->trueEquivalent = $value; @@ -223,11 +202,9 @@ abstract class NodeDefinition implements NodeParentInterface /** * Sets the equivalent value used when the node contains false. * - * @param mixed $value - * * @return $this */ - public function treatFalseLike($value) + public function treatFalseLike(mixed $value): static { $this->falseEquivalent = $value; @@ -239,7 +216,7 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function defaultNull() + public function defaultNull(): static { return $this->defaultValue(null); } @@ -249,7 +226,7 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function defaultTrue() + public function defaultTrue(): static { return $this->defaultValue(true); } @@ -259,17 +236,15 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function defaultFalse() + public function defaultFalse(): static { return $this->defaultValue(false); } /** * Sets an expression to run before the normalization. - * - * @return ExprBuilder */ - public function beforeNormalization() + public function beforeNormalization(): ExprBuilder { return $this->normalization()->before(); } @@ -279,7 +254,7 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function cannotBeEmpty() + public function cannotBeEmpty(): static { $this->allowEmptyValue = false; @@ -292,10 +267,8 @@ abstract class NodeDefinition implements NodeParentInterface * The expression receives the value of the node and must return it. It can * modify it. * An exception should be thrown when the node is not valid. - * - * @return ExprBuilder */ - public function validate() + public function validate(): ExprBuilder { return $this->validation()->rule(); } @@ -305,7 +278,7 @@ abstract class NodeDefinition implements NodeParentInterface * * @return $this */ - public function cannotBeOverwritten(bool $deny = true) + public function cannotBeOverwritten(bool $deny = true): static { $this->merge()->denyOverwrite($deny); @@ -314,61 +287,41 @@ abstract class NodeDefinition implements NodeParentInterface /** * Gets the builder for validation rules. - * - * @return ValidationBuilder */ - protected function validation() + protected function validation(): ValidationBuilder { - if (null === $this->validation) { - $this->validation = new ValidationBuilder($this); - } - - return $this->validation; + return $this->validation ??= new ValidationBuilder($this); } /** * Gets the builder for merging rules. - * - * @return MergeBuilder */ - protected function merge() + protected function merge(): MergeBuilder { - if (null === $this->merge) { - $this->merge = new MergeBuilder($this); - } - - return $this->merge; + return $this->merge ??= new MergeBuilder($this); } /** * Gets the builder for normalization rules. - * - * @return NormalizationBuilder */ - protected function normalization() + protected function normalization(): NormalizationBuilder { - if (null === $this->normalization) { - $this->normalization = new NormalizationBuilder($this); - } - - return $this->normalization; + return $this->normalization ??= new NormalizationBuilder($this); } /** * Instantiate and configure the node according to this definition. * - * @return NodeInterface - * * @throws InvalidDefinitionException When the definition is invalid */ - abstract protected function createNode(); + abstract protected function createNode(): NodeInterface; /** * Set PathSeparator to use. * * @return $this */ - public function setPathSeparator(string $separator) + public function setPathSeparator(string $separator): static { if ($this instanceof ParentNodeDefinitionInterface) { foreach ($this->getChildNodeDefinitions() as $child) { diff --git a/lib/symfony/config/Definition/Builder/NormalizationBuilder.php b/lib/symfony/config/Definition/Builder/NormalizationBuilder.php index 06cbbd434..0e362d9fa 100644 --- a/lib/symfony/config/Definition/Builder/NormalizationBuilder.php +++ b/lib/symfony/config/Definition/Builder/NormalizationBuilder.php @@ -20,6 +20,7 @@ class NormalizationBuilder { protected $node; public $before = []; + public $declaredTypes = []; public $remappings = []; public function __construct(NodeDefinition $node) @@ -35,7 +36,7 @@ class NormalizationBuilder * * @return $this */ - public function remap(string $key, string $plural = null) + public function remap(string $key, string $plural = null): static { $this->remappings[] = [$key, null === $plural ? $key.'s' : $plural]; @@ -47,7 +48,7 @@ class NormalizationBuilder * * @return ExprBuilder|$this */ - public function before(\Closure $closure = null) + public function before(\Closure $closure = null): ExprBuilder|static { if (null !== $closure) { $this->before[] = $closure; diff --git a/lib/symfony/config/Definition/Builder/NumericNodeDefinition.php b/lib/symfony/config/Definition/Builder/NumericNodeDefinition.php index c4bff1756..890910c95 100644 --- a/lib/symfony/config/Definition/Builder/NumericNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/NumericNodeDefinition.php @@ -26,13 +26,11 @@ abstract class NumericNodeDefinition extends ScalarNodeDefinition /** * Ensures that the value is smaller than the given reference. * - * @param int|float $max - * * @return $this * * @throws \InvalidArgumentException when the constraint is inconsistent */ - public function max($max) + public function max(int|float $max): static { if (isset($this->min) && $this->min > $max) { throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s).', $max, $this->min)); @@ -45,13 +43,11 @@ abstract class NumericNodeDefinition extends ScalarNodeDefinition /** * Ensures that the value is bigger than the given reference. * - * @param int|float $min - * * @return $this * * @throws \InvalidArgumentException when the constraint is inconsistent */ - public function min($min) + public function min(int|float $min): static { if (isset($this->max) && $this->max < $min) { throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s).', $min, $this->max)); @@ -62,11 +58,9 @@ abstract class NumericNodeDefinition extends ScalarNodeDefinition } /** - * {@inheritdoc} - * * @throws InvalidDefinitionException */ - public function cannotBeEmpty() + public function cannotBeEmpty(): static { throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.'); } diff --git a/lib/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php b/lib/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php index 449b91afd..7b8a7eb80 100644 --- a/lib/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php +++ b/lib/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php @@ -20,10 +20,8 @@ interface ParentNodeDefinitionInterface extends BuilderAwareInterface { /** * Returns a builder to add children nodes. - * - * @return NodeBuilder */ - public function children(); + public function children(): NodeBuilder; /** * Appends a node definition. @@ -40,12 +38,12 @@ interface ParentNodeDefinitionInterface extends BuilderAwareInterface * * @return $this */ - public function append(NodeDefinition $node); + public function append(NodeDefinition $node): static; /** * Gets the child node definitions. * * @return NodeDefinition[] */ - public function getChildNodeDefinitions(); + public function getChildNodeDefinitions(): array; } diff --git a/lib/symfony/config/Definition/Builder/ScalarNodeDefinition.php b/lib/symfony/config/Definition/Builder/ScalarNodeDefinition.php index 076f74b34..37a0af02d 100644 --- a/lib/symfony/config/Definition/Builder/ScalarNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/ScalarNodeDefinition.php @@ -22,10 +22,8 @@ class ScalarNodeDefinition extends VariableNodeDefinition { /** * Instantiate a Node. - * - * @return ScalarNode */ - protected function instantiateNode() + protected function instantiateNode(): ScalarNode { return new ScalarNode($this->name, $this->parent, $this->pathSeparator); } diff --git a/lib/symfony/config/Definition/Builder/TreeBuilder.php b/lib/symfony/config/Definition/Builder/TreeBuilder.php index f3c3c2109..cdee55772 100644 --- a/lib/symfony/config/Definition/Builder/TreeBuilder.php +++ b/lib/symfony/config/Definition/Builder/TreeBuilder.php @@ -20,19 +20,26 @@ use Symfony\Component\Config\Definition\NodeInterface; */ class TreeBuilder implements NodeParentInterface { + /** + * @var NodeInterface|null + */ protected $tree; + + /** + * @var NodeDefinition + */ protected $root; public function __construct(string $name, string $type = 'array', NodeBuilder $builder = null) { - $builder = $builder ?? new NodeBuilder(); + $builder ??= new NodeBuilder(); $this->root = $builder->node($name, $type)->setParent($this); } /** * @return NodeDefinition|ArrayNodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array') */ - public function getRootNode(): NodeDefinition + public function getRootNode(): NodeDefinition|ArrayNodeDefinition { return $this->root; } @@ -40,19 +47,16 @@ class TreeBuilder implements NodeParentInterface /** * Builds the tree. * - * @return NodeInterface - * * @throws \RuntimeException */ - public function buildTree() + public function buildTree(): NodeInterface { - if (null !== $this->tree) { - return $this->tree; - } - - return $this->tree = $this->root->getNode(true); + return $this->tree ??= $this->root->getNode(true); } + /** + * @return void + */ public function setPathSeparator(string $separator) { // unset last built as changing path separator changes all nodes diff --git a/lib/symfony/config/Definition/Builder/ValidationBuilder.php b/lib/symfony/config/Definition/Builder/ValidationBuilder.php index 4efc726c0..1bee851b6 100644 --- a/lib/symfony/config/Definition/Builder/ValidationBuilder.php +++ b/lib/symfony/config/Definition/Builder/ValidationBuilder.php @@ -31,7 +31,7 @@ class ValidationBuilder * * @return ExprBuilder|$this */ - public function rule(\Closure $closure = null) + public function rule(\Closure $closure = null): ExprBuilder|static { if (null !== $closure) { $this->rules[] = $closure; diff --git a/lib/symfony/config/Definition/Builder/VariableNodeDefinition.php b/lib/symfony/config/Definition/Builder/VariableNodeDefinition.php index eea16cc32..a4cc53a55 100644 --- a/lib/symfony/config/Definition/Builder/VariableNodeDefinition.php +++ b/lib/symfony/config/Definition/Builder/VariableNodeDefinition.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Config\Definition\Builder; +use Symfony\Component\Config\Definition\NodeInterface; use Symfony\Component\Config\Definition\VariableNode; /** @@ -22,26 +23,21 @@ class VariableNodeDefinition extends NodeDefinition { /** * Instantiate a Node. - * - * @return VariableNode */ - protected function instantiateNode() + protected function instantiateNode(): VariableNode { return new VariableNode($this->name, $this->parent, $this->pathSeparator); } - /** - * {@inheritdoc} - */ - protected function createNode() + protected function createNode(): NodeInterface { $node = $this->instantiateNode(); - if (null !== $this->normalization) { + if (isset($this->normalization)) { $node->setNormalizationClosures($this->normalization->before); } - if (null !== $this->merge) { + if (isset($this->merge)) { $node->setAllowOverwrite($this->merge->allowOverwrite); } @@ -59,7 +55,7 @@ class VariableNodeDefinition extends NodeDefinition $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']); } - if (null !== $this->validation) { + if (isset($this->validation)) { $node->setFinalValidationClosures($this->validation->rules); } diff --git a/lib/symfony/config/Definition/ConfigurableInterface.php b/lib/symfony/config/Definition/ConfigurableInterface.php new file mode 100644 index 000000000..cd4646160 --- /dev/null +++ b/lib/symfony/config/Definition/ConfigurableInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + +/** + * @author Yonel Ceruto + */ +interface ConfigurableInterface +{ + /** + * Generates the configuration tree builder. + */ + public function configure(DefinitionConfigurator $definition): void; +} diff --git a/lib/symfony/config/Definition/Configuration.php b/lib/symfony/config/Definition/Configuration.php new file mode 100644 index 000000000..32954a66a --- /dev/null +++ b/lib/symfony/config/Definition/Configuration.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\Config\Definition\Loader\DefinitionFileLoader; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Yonel Ceruto + * + * @final + */ +class Configuration implements ConfigurationInterface +{ + public function __construct( + private ConfigurableInterface $subject, + private ?ContainerBuilder $container, + private string $alias, + ) { + } + + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder($this->alias); + $file = (new \ReflectionObject($this->subject))->getFileName(); + $loader = new DefinitionFileLoader($treeBuilder, new FileLocator(\dirname($file)), $this->container); + $configurator = new DefinitionConfigurator($treeBuilder, $loader, $file, $file); + + $this->subject->configure($configurator); + + return $treeBuilder; + } +} diff --git a/lib/symfony/config/Definition/Configurator/DefinitionConfigurator.php b/lib/symfony/config/Definition/Configurator/DefinitionConfigurator.php new file mode 100644 index 000000000..006a444be --- /dev/null +++ b/lib/symfony/config/Definition/Configurator/DefinitionConfigurator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Configurator; + +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Loader\DefinitionFileLoader; + +/** + * @author Yonel Ceruto + */ +class DefinitionConfigurator +{ + public function __construct( + private TreeBuilder $treeBuilder, + private DefinitionFileLoader $loader, + private string $path, + private string $file, + ) { + } + + public function import(string $resource, string $type = null, bool $ignoreErrors = false): void + { + $this->loader->setCurrentDir(\dirname($this->path)); + $this->loader->import($resource, $type, $ignoreErrors, $this->file); + } + + public function rootNode(): NodeDefinition|ArrayNodeDefinition + { + return $this->treeBuilder->getRootNode(); + } + + public function setPathSeparator(string $separator): void + { + $this->treeBuilder->setPathSeparator($separator); + } +} diff --git a/lib/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/lib/symfony/config/Definition/Dumper/XmlReferenceDumper.php index a8b18a023..34f93ce07 100644 --- a/lib/symfony/config/Definition/Dumper/XmlReferenceDumper.php +++ b/lib/symfony/config/Definition/Dumper/XmlReferenceDumper.php @@ -13,10 +13,14 @@ namespace Symfony\Component\Config\Definition\Dumper; use Symfony\Component\Config\Definition\ArrayNode; use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\BooleanNode; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\FloatNode; +use Symfony\Component\Config\Definition\IntegerNode; use Symfony\Component\Config\Definition\NodeInterface; use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Config\Definition\ScalarNode; /** * Dumps an XML reference configuration for the given configuration/node instance. @@ -25,13 +29,19 @@ use Symfony\Component\Config\Definition\PrototypedArrayNode; */ class XmlReferenceDumper { - private $reference; + private ?string $reference = null; + /** + * @return string + */ public function dump(ConfigurationInterface $configuration, string $namespace = null) { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); } + /** + * @return string + */ public function dumpNode(NodeInterface $node, string $namespace = null) { $this->reference = ''; @@ -42,16 +52,14 @@ class XmlReferenceDumper return $ref; } - private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, string $namespace = null) + private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, string $namespace = null): void { $rootName = ($root ? 'config' : $node->getName()); $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null)); // xml remapping if ($node->getParent()) { - $remapping = array_filter($node->getParent()->getXmlRemappings(), function (array $mapping) use ($rootName) { - return $rootName === $mapping[1]; - }); + $remapping = array_filter($node->getParent()->getXmlRemappings(), fn (array $mapping) => $rootName === $mapping[1]); if (\count($remapping)) { [$singular] = current($remapping); @@ -100,27 +108,14 @@ class XmlReferenceDumper if ($prototype->hasDefaultValue()) { $prototypeValue = $prototype->getDefaultValue(); } else { - switch (\get_class($prototype)) { - case 'Symfony\Component\Config\Definition\ScalarNode': - $prototypeValue = 'scalar value'; - break; - - case 'Symfony\Component\Config\Definition\FloatNode': - case 'Symfony\Component\Config\Definition\IntegerNode': - $prototypeValue = 'numeric value'; - break; - - case 'Symfony\Component\Config\Definition\BooleanNode': - $prototypeValue = 'true|false'; - break; - - case 'Symfony\Component\Config\Definition\EnumNode': - $prototypeValue = implode('|', array_map('json_encode', $prototype->getValues())); - break; - - default: - $prototypeValue = 'value'; - } + $prototypeValue = match ($prototype::class) { + ScalarNode::class => 'scalar value', + FloatNode::class, + IntegerNode::class => 'numeric value', + BooleanNode::class => 'true|false', + EnumNode::class => $prototype->getPermissibleValues('|'), + default => 'value', + }; } } } @@ -147,7 +142,7 @@ class XmlReferenceDumper } if ($child instanceof BaseNode && $example = $child->getExample()) { - $comments[] = 'Example: '.$example; + $comments[] = 'Example: '.(\is_array($example) ? implode(', ', $example) : $example); } if ($child->isRequired()) { @@ -160,7 +155,7 @@ class XmlReferenceDumper } if ($child instanceof EnumNode) { - $comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues())); + $comments[] = 'One of '.$child->getPermissibleValues('; '); } if (\count($comments)) { @@ -258,7 +253,7 @@ class XmlReferenceDumper /** * Outputs a single config reference line. */ - private function writeLine(string $text, int $indent = 0) + private function writeLine(string $text, int $indent = 0): void { $indent = \strlen($text) + $indent; $format = '%'.$indent.'s'; @@ -268,10 +263,8 @@ class XmlReferenceDumper /** * Renders the string conversion of the value. - * - * @param mixed $value */ - private function writeValue($value): string + private function writeValue(mixed $value): string { if ('%%%%not_defined%%%%' === $value) { return ''; diff --git a/lib/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/lib/symfony/config/Definition/Dumper/YamlReferenceDumper.php index 6fcfb71bd..97a391ada 100644 --- a/lib/symfony/config/Definition/Dumper/YamlReferenceDumper.php +++ b/lib/symfony/config/Definition/Dumper/YamlReferenceDumper.php @@ -28,13 +28,19 @@ use Symfony\Component\Yaml\Inline; */ class YamlReferenceDumper { - private $reference; + private ?string $reference = null; + /** + * @return string + */ public function dump(ConfigurationInterface $configuration) { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); } + /** + * @return string + */ public function dumpAtPath(ConfigurationInterface $configuration, string $path) { $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree(); @@ -61,6 +67,9 @@ class YamlReferenceDumper return $this->dumpNode($node); } + /** + * @return string + */ public function dumpNode(NodeInterface $node) { $this->reference = ''; @@ -71,7 +80,7 @@ class YamlReferenceDumper return $ref; } - private function writeNode(NodeInterface $node, NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false) + private function writeNode(NodeInterface $node, NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false): void { $comments = []; $default = ''; @@ -98,9 +107,9 @@ class YamlReferenceDumper } } } elseif ($node instanceof EnumNode) { - $comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues())); + $comments[] = 'One of '.$node->getPermissibleValues('; '); $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~'; - } elseif (VariableNode::class === \get_class($node) && \is_array($example)) { + } elseif (VariableNode::class === $node::class && \is_array($example)) { // If there is an array example, we are sure we dont need to print a default value $default = ''; } else { @@ -170,7 +179,7 @@ class YamlReferenceDumper $this->writeLine('# '.$message.':', $depth * 4 + 4); - $this->writeArray(array_map([Inline::class, 'dump'], $example), $depth + 1); + $this->writeArray(array_map(Inline::dump(...), $example), $depth + 1); } if ($children) { @@ -183,7 +192,7 @@ class YamlReferenceDumper /** * Outputs a single config reference line. */ - private function writeLine(string $text, int $indent = 0) + private function writeLine(string $text, int $indent = 0): void { $indent = \strlen($text) + $indent; $format = '%'.$indent.'s'; @@ -191,9 +200,9 @@ class YamlReferenceDumper $this->reference .= sprintf($format, $text)."\n"; } - private function writeArray(array $array, int $depth) + private function writeArray(array $array, int $depth): void { - $isIndexed = array_values($array) === $array; + $isIndexed = array_is_list($array); foreach ($array as $key => $value) { if (\is_array($value)) { diff --git a/lib/symfony/config/Definition/EnumNode.php b/lib/symfony/config/Definition/EnumNode.php index 822e6b57f..4edeae904 100644 --- a/lib/symfony/config/Definition/EnumNode.php +++ b/lib/symfony/config/Definition/EnumNode.php @@ -20,33 +20,72 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; */ class EnumNode extends ScalarNode { - private $values; + private array $values; public function __construct(?string $name, NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) { - $values = array_unique($values); - if (empty($values)) { + if (!$values) { throw new \InvalidArgumentException('$values must contain at least one element.'); } + foreach ($values as $value) { + if (null === $value || \is_scalar($value)) { + continue; + } + + if (!$value instanceof \UnitEnum) { + throw new \InvalidArgumentException(sprintf('"%s" only supports scalar, enum, or null values, "%s" given.', __CLASS__, get_debug_type($value))); + } + + if ($value::class !== ($enumClass ??= $value::class)) { + throw new \InvalidArgumentException(sprintf('"%s" only supports one type of enum, "%s" and "%s" passed.', __CLASS__, $enumClass, $value::class)); + } + } + parent::__construct($name, $parent, $pathSeparator); $this->values = $values; } + /** + * @return array + */ public function getValues() { return $this->values; } /** - * {@inheritdoc} + * @internal */ - protected function finalizeValue($value) + public function getPermissibleValues(string $separator): string + { + return implode($separator, array_unique(array_map(static function (mixed $value): string { + if (!$value instanceof \UnitEnum) { + return json_encode($value); + } + + return ltrim(var_export($value, true), '\\'); + }, $this->values))); + } + + /** + * @return void + */ + protected function validateType(mixed $value) + { + if ($value instanceof \UnitEnum) { + return; + } + + parent::validateType($value); + } + + protected function finalizeValue(mixed $value): mixed { $value = parent::finalizeValue($value); if (!\in_array($value, $this->values, true)) { - $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_map('json_encode', $this->values)))); + $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), $this->getPermissibleValues(', '))); $ex->setPath($this->getPath()); throw $ex; @@ -54,12 +93,4 @@ class EnumNode extends ScalarNode return $value; } - - /** - * {@inheritdoc} - */ - protected function allowPlaceholders(): bool - { - return false; - } } diff --git a/lib/symfony/config/Definition/Exception/InvalidConfigurationException.php b/lib/symfony/config/Definition/Exception/InvalidConfigurationException.php index ceb5e239b..794447bf8 100644 --- a/lib/symfony/config/Definition/Exception/InvalidConfigurationException.php +++ b/lib/symfony/config/Definition/Exception/InvalidConfigurationException.php @@ -19,21 +19,26 @@ namespace Symfony\Component\Config\Definition\Exception; */ class InvalidConfigurationException extends Exception { - private $path; - private $containsHints = false; + private ?string $path = null; + private bool $containsHints = false; + /** + * @return void + */ public function setPath(string $path) { $this->path = $path; } - public function getPath() + public function getPath(): ?string { return $this->path; } /** * Adds extra information that is suffixed to the original exception message. + * + * @return void */ public function addHint(string $hint) { diff --git a/lib/symfony/config/Definition/FloatNode.php b/lib/symfony/config/Definition/FloatNode.php index 527f996ef..ce4193e09 100644 --- a/lib/symfony/config/Definition/FloatNode.php +++ b/lib/symfony/config/Definition/FloatNode.php @@ -21,9 +21,9 @@ use Symfony\Component\Config\Definition\Exception\InvalidTypeException; class FloatNode extends NumericNode { /** - * {@inheritdoc} + * @return void */ - protected function validateType($value) + protected function validateType(mixed $value) { // Integers are also accepted, we just cast them if (\is_int($value)) { @@ -41,9 +41,6 @@ class FloatNode extends NumericNode } } - /** - * {@inheritdoc} - */ protected function getValidPlaceholderTypes(): array { return ['float']; diff --git a/lib/symfony/config/Definition/IntegerNode.php b/lib/symfony/config/Definition/IntegerNode.php index dfb4cc674..4a3e3253c 100644 --- a/lib/symfony/config/Definition/IntegerNode.php +++ b/lib/symfony/config/Definition/IntegerNode.php @@ -21,9 +21,9 @@ use Symfony\Component\Config\Definition\Exception\InvalidTypeException; class IntegerNode extends NumericNode { /** - * {@inheritdoc} + * @return void */ - protected function validateType($value) + protected function validateType(mixed $value) { if (!\is_int($value)) { $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), get_debug_type($value))); @@ -36,9 +36,6 @@ class IntegerNode extends NumericNode } } - /** - * {@inheritdoc} - */ protected function getValidPlaceholderTypes(): array { return ['int']; diff --git a/lib/symfony/config/Definition/Loader/DefinitionFileLoader.php b/lib/symfony/config/Definition/Loader/DefinitionFileLoader.php new file mode 100644 index 000000000..506f787ca --- /dev/null +++ b/lib/symfony/config/Definition/Loader/DefinitionFileLoader.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Loader; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * DefinitionFileLoader loads config definitions from a PHP file. + * + * The PHP file is required. + * + * @author Yonel Ceruto + */ +class DefinitionFileLoader extends FileLoader +{ + public function __construct( + private TreeBuilder $treeBuilder, + FileLocatorInterface $locator, + private ?ContainerBuilder $container = null, + ) { + parent::__construct($locator); + } + + public function load(mixed $resource, string $type = null): mixed + { + // the loader variable is exposed to the included file below + $loader = $this; + + $path = $this->locator->locate($resource); + $this->setCurrentDir(\dirname($path)); + $this->container?->fileExists($path); + + // the closure forbids access to the private scope in the included file + $load = \Closure::bind(static function ($file) use ($loader) { + return include $file; + }, null, ProtectedDefinitionFileLoader::class); + + $callback = $load($path); + + if (\is_object($callback) && \is_callable($callback)) { + $this->executeCallback($callback, new DefinitionConfigurator($this->treeBuilder, $this, $path, $resource), $path); + } + + return null; + } + + public function supports(mixed $resource, string $type = null): bool + { + if (!\is_string($resource)) { + return false; + } + + if (null === $type && 'php' === pathinfo($resource, \PATHINFO_EXTENSION)) { + return true; + } + + return 'php' === $type; + } + + private function executeCallback(callable $callback, DefinitionConfigurator $configurator, string $path): void + { + $callback = $callback(...); + + $arguments = []; + $r = new \ReflectionFunction($callback); + + foreach ($r->getParameters() as $parameter) { + $reflectionType = $parameter->getType(); + + if (!$reflectionType instanceof \ReflectionNamedType) { + throw new \InvalidArgumentException(sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s").', $parameter->getName(), $path, DefinitionConfigurator::class)); + } + + $arguments[] = match ($reflectionType->getName()) { + DefinitionConfigurator::class => $configurator, + TreeBuilder::class => $this->treeBuilder, + FileLoader::class, self::class => $this, + }; + } + + $callback(...$arguments); + } +} + +/** + * @internal + */ +final class ProtectedDefinitionFileLoader extends DefinitionFileLoader +{ +} diff --git a/lib/symfony/config/Definition/NodeInterface.php b/lib/symfony/config/Definition/NodeInterface.php index 9c279ae26..d1715870a 100644 --- a/lib/symfony/config/Definition/NodeInterface.php +++ b/lib/symfony/config/Definition/NodeInterface.php @@ -27,74 +27,51 @@ interface NodeInterface { /** * Returns the name of the node. - * - * @return string */ - public function getName(); + public function getName(): string; /** * Returns the path of the node. - * - * @return string */ - public function getPath(); + public function getPath(): string; /** * Returns true when the node is required. - * - * @return bool */ - public function isRequired(); + public function isRequired(): bool; /** * Returns true when the node has a default value. - * - * @return bool */ - public function hasDefaultValue(); + public function hasDefaultValue(): bool; /** * Returns the default value of the node. * - * @return mixed - * * @throws \RuntimeException if the node has no default value */ - public function getDefaultValue(); + public function getDefaultValue(): mixed; /** * Normalizes a value. * - * @param mixed $value The value to normalize - * - * @return mixed - * * @throws InvalidTypeException if the value type is invalid */ - public function normalize($value); + public function normalize(mixed $value): mixed; /** * Merges two values together. * - * @param mixed $leftSide - * @param mixed $rightSide - * - * @return mixed - * * @throws ForbiddenOverwriteException if the configuration path cannot be overwritten * @throws InvalidTypeException if the value type is invalid */ - public function merge($leftSide, $rightSide); + public function merge(mixed $leftSide, mixed $rightSide): mixed; /** * Finalizes a value. * - * @param mixed $value The value to finalize - * - * @return mixed - * * @throws InvalidTypeException if the value type is invalid * @throws InvalidConfigurationException if the value is invalid configuration */ - public function finalize($value); + public function finalize(mixed $value): mixed; } diff --git a/lib/symfony/config/Definition/NumericNode.php b/lib/symfony/config/Definition/NumericNode.php index 50d137c2d..da32b843a 100644 --- a/lib/symfony/config/Definition/NumericNode.php +++ b/lib/symfony/config/Definition/NumericNode.php @@ -23,21 +23,14 @@ class NumericNode extends ScalarNode protected $min; protected $max; - /** - * @param int|float|null $min - * @param int|float|null $max - */ - public function __construct(?string $name, NodeInterface $parent = null, $min = null, $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) + public function __construct(?string $name, NodeInterface $parent = null, int|float $min = null, int|float $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) { parent::__construct($name, $parent, $pathSeparator); $this->min = $min; $this->max = $max; } - /** - * {@inheritdoc} - */ - protected function finalizeValue($value) + protected function finalizeValue(mixed $value): mixed { $value = parent::finalizeValue($value); @@ -57,10 +50,7 @@ class NumericNode extends ScalarNode return $value; } - /** - * {@inheritdoc} - */ - protected function isValueEmpty($value) + protected function isValueEmpty(mixed $value): bool { // a numeric value cannot be empty return false; diff --git a/lib/symfony/config/Definition/Processor.php b/lib/symfony/config/Definition/Processor.php index c431408e1..dc3d4c69b 100644 --- a/lib/symfony/config/Definition/Processor.php +++ b/lib/symfony/config/Definition/Processor.php @@ -63,15 +63,13 @@ class Processor * * extensions: ['twig.extension.foo', 'twig.extension.bar'] * - * @param array $config A config array - * @param string $key The key to normalize - * @param string $plural The plural form of the key if it is irregular + * @param array $config A config array + * @param string $key The key to normalize + * @param string|null $plural The plural form of the key if it is irregular */ public static function normalizeConfig(array $config, string $key, string $plural = null): array { - if (null === $plural) { - $plural = $key.'s'; - } + $plural ??= $key.'s'; if (isset($config[$plural])) { return $config[$plural]; diff --git a/lib/symfony/config/Definition/PrototypeNodeInterface.php b/lib/symfony/config/Definition/PrototypeNodeInterface.php index b160aa94a..9dce7444b 100644 --- a/lib/symfony/config/Definition/PrototypeNodeInterface.php +++ b/lib/symfony/config/Definition/PrototypeNodeInterface.php @@ -20,6 +20,8 @@ interface PrototypeNodeInterface extends NodeInterface { /** * Sets the name of the node. + * + * @return void */ public function setName(string $name); } diff --git a/lib/symfony/config/Definition/PrototypedArrayNode.php b/lib/symfony/config/Definition/PrototypedArrayNode.php index e78159c9d..c105ac1f3 100644 --- a/lib/symfony/config/Definition/PrototypedArrayNode.php +++ b/lib/symfony/config/Definition/PrototypedArrayNode.php @@ -32,11 +32,13 @@ class PrototypedArrayNode extends ArrayNode /** * @var NodeInterface[] An array of the prototypes of the simplified value children */ - private $valuePrototypes = []; + private array $valuePrototypes = []; /** * Sets the minimum number of elements that a prototype based node must * contain. By default this is zero, meaning no elements. + * + * @return void */ public function setMinNumberOfElements(int $number) { @@ -66,6 +68,8 @@ class PrototypedArrayNode extends ArrayNode * * @param string $attribute The name of the attribute which value is to be used as a key * @param bool $remove Whether or not to remove the key + * + * @return void */ public function setKeyAttribute(string $attribute, bool $remove = true) { @@ -75,26 +79,23 @@ class PrototypedArrayNode extends ArrayNode /** * Retrieves the name of the attribute which value should be used as key. - * - * @return string|null */ - public function getKeyAttribute() + public function getKeyAttribute(): ?string { return $this->keyAttribute; } /** * Sets the default value of this node. + * + * @return void */ public function setDefaultValue(array $value) { $this->defaultValue = $value; } - /** - * {@inheritdoc} - */ - public function hasDefaultValue() + public function hasDefaultValue(): bool { return true; } @@ -103,8 +104,10 @@ class PrototypedArrayNode extends ArrayNode * Adds default children when none are set. * * @param int|string|array|null $children The number of children|The child name|The children names to be added + * + * @return void */ - public function setAddChildrenIfNoneSet($children = ['defaults']) + public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']) { if (null === $children) { $this->defaultChildren = ['defaults']; @@ -114,12 +117,10 @@ class PrototypedArrayNode extends ArrayNode } /** - * {@inheritdoc} - * * The default value could be either explicited or derived from the prototype * default value. */ - public function getDefaultValue() + public function getDefaultValue(): mixed { if (null !== $this->defaultChildren) { $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : []; @@ -136,6 +137,8 @@ class PrototypedArrayNode extends ArrayNode /** * Sets the node prototype. + * + * @return void */ public function setPrototype(PrototypeNodeInterface $node) { @@ -144,10 +147,8 @@ class PrototypedArrayNode extends ArrayNode /** * Retrieves the prototype. - * - * @return PrototypeNodeInterface */ - public function getPrototype() + public function getPrototype(): PrototypeNodeInterface { return $this->prototype; } @@ -155,6 +156,8 @@ class PrototypedArrayNode extends ArrayNode /** * Disable adding concrete children for prototyped nodes. * + * @return never + * * @throws Exception */ public function addChild(NodeInterface $node) @@ -162,10 +165,7 @@ class PrototypedArrayNode extends ArrayNode throw new Exception('A prototyped array node cannot have concrete children.'); } - /** - * {@inheritdoc} - */ - protected function finalizeValue($value) + protected function finalizeValue(mixed $value): mixed { if (false === $value) { throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); @@ -175,7 +175,7 @@ class PrototypedArrayNode extends ArrayNode $prototype = $this->getPrototypeForChild($k); try { $value[$k] = $prototype->finalize($v); - } catch (UnsetKeyException $e) { + } catch (UnsetKeyException) { unset($value[$k]); } } @@ -191,11 +191,9 @@ class PrototypedArrayNode extends ArrayNode } /** - * {@inheritdoc} - * * @throws DuplicateKeyException */ - protected function normalizeValue($value) + protected function normalizeValue(mixed $value): mixed { if (false === $value) { return $value; @@ -259,10 +257,7 @@ class PrototypedArrayNode extends ArrayNode return $normalized; } - /** - * {@inheritdoc} - */ - protected function mergeValues($leftSide, $rightSide) + protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed { if (false === $rightSide) { // if this is still false after the last config has been merged the @@ -337,10 +332,8 @@ class PrototypedArrayNode extends ArrayNode * * Now, the key becomes 'name001' and the child node becomes 'value001' and * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance. - * - * @return mixed */ - private function getPrototypeForChild(string $key) + private function getPrototypeForChild(string $key): mixed { $prototype = $this->valuePrototypes[$key] ?? $this->prototype; $prototype->setName($key); diff --git a/lib/symfony/config/Definition/ScalarNode.php b/lib/symfony/config/Definition/ScalarNode.php index 89ed4c3dc..e11fa1ee1 100644 --- a/lib/symfony/config/Definition/ScalarNode.php +++ b/lib/symfony/config/Definition/ScalarNode.php @@ -28,9 +28,9 @@ use Symfony\Component\Config\Definition\Exception\InvalidTypeException; class ScalarNode extends VariableNode { /** - * {@inheritdoc} + * @return void */ - protected function validateType($value) + protected function validateType(mixed $value) { if (!\is_scalar($value) && null !== $value) { $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), get_debug_type($value))); @@ -43,10 +43,7 @@ class ScalarNode extends VariableNode } } - /** - * {@inheritdoc} - */ - protected function isValueEmpty($value) + protected function isValueEmpty(mixed $value): bool { // assume environment variables are never empty (which in practice is likely to be true during runtime) // not doing so breaks many configs that are valid today @@ -57,9 +54,6 @@ class ScalarNode extends VariableNode return null === $value || '' === $value; } - /** - * {@inheritdoc} - */ protected function getValidPlaceholderTypes(): array { return ['bool', 'int', 'float', 'string']; diff --git a/lib/symfony/config/Definition/VariableNode.php b/lib/symfony/config/Definition/VariableNode.php index e868ece13..6bdc65b4e 100644 --- a/lib/symfony/config/Definition/VariableNode.php +++ b/lib/symfony/config/Definition/VariableNode.php @@ -27,24 +27,21 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface protected $defaultValue; protected $allowEmptyValue = true; - public function setDefaultValue($value) + /** + * @return void + */ + public function setDefaultValue(mixed $value) { $this->defaultValueSet = true; $this->defaultValue = $value; } - /** - * {@inheritdoc} - */ - public function hasDefaultValue() + public function hasDefaultValue(): bool { return $this->defaultValueSet; } - /** - * {@inheritdoc} - */ - public function getDefaultValue() + public function getDefaultValue(): mixed { $v = $this->defaultValue; @@ -55,6 +52,8 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface * Sets if this node is allowed to have an empty value. * * @param bool $boolean True if this entity will accept empty values + * + * @return void */ public function setAllowEmptyValue(bool $boolean) { @@ -62,7 +61,7 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface } /** - * {@inheritdoc} + * @return void */ public function setName(string $name) { @@ -70,16 +69,13 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface } /** - * {@inheritdoc} + * @return void */ - protected function validateType($value) + protected function validateType(mixed $value) { } - /** - * {@inheritdoc} - */ - protected function finalizeValue($value) + protected function finalizeValue(mixed $value): mixed { // deny environment variables only when using custom validators // this avoids ever passing an empty value to final validation closures @@ -106,18 +102,12 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface return $value; } - /** - * {@inheritdoc} - */ - protected function normalizeValue($value) + protected function normalizeValue(mixed $value): mixed { return $value; } - /** - * {@inheritdoc} - */ - protected function mergeValues($leftSide, $rightSide) + protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed { return $rightSide; } @@ -129,13 +119,9 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface * method may be overridden by subtypes to better match their understanding * of empty data. * - * @param mixed $value - * - * @return bool - * * @see finalizeValue() */ - protected function isValueEmpty($value) + protected function isValueEmpty(mixed $value): bool { return empty($value); } diff --git a/lib/symfony/config/Exception/FileLoaderImportCircularReferenceException.php b/lib/symfony/config/Exception/FileLoaderImportCircularReferenceException.php index e235ea049..da0b55ba8 100644 --- a/lib/symfony/config/Exception/FileLoaderImportCircularReferenceException.php +++ b/lib/symfony/config/Exception/FileLoaderImportCircularReferenceException.php @@ -18,14 +18,8 @@ namespace Symfony\Component\Config\Exception; */ class FileLoaderImportCircularReferenceException extends LoaderLoadException { - public function __construct(array $resources, ?int $code = 0, \Throwable $previous = null) + public function __construct(array $resources, int $code = 0, \Throwable $previous = null) { - if (null === $code) { - trigger_deprecation('symfony/config', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); - - $code = 0; - } - $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]); \Exception::__construct($message, $code, $previous); diff --git a/lib/symfony/config/Exception/FileLocatorFileNotFoundException.php b/lib/symfony/config/Exception/FileLocatorFileNotFoundException.php index 3ee4b938f..c5173ae58 100644 --- a/lib/symfony/config/Exception/FileLocatorFileNotFoundException.php +++ b/lib/symfony/config/Exception/FileLocatorFileNotFoundException.php @@ -18,7 +18,7 @@ namespace Symfony\Component\Config\Exception; */ class FileLocatorFileNotFoundException extends \InvalidArgumentException { - private $paths; + private array $paths; public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, array $paths = []) { @@ -27,6 +27,9 @@ class FileLocatorFileNotFoundException extends \InvalidArgumentException $this->paths = $paths; } + /** + * @return array + */ public function getPaths() { return $this->paths; diff --git a/lib/symfony/config/Exception/LoaderLoadException.php b/lib/symfony/config/Exception/LoaderLoadException.php index b20e74db4..57afd6a8d 100644 --- a/lib/symfony/config/Exception/LoaderLoadException.php +++ b/lib/symfony/config/Exception/LoaderLoadException.php @@ -19,18 +19,20 @@ namespace Symfony\Component\Config\Exception; class LoaderLoadException extends \Exception { /** - * @param string $resource The resource that could not be imported + * @param mixed $resource The resource that could not be imported * @param string|null $sourceResource The original resource importing the new resource - * @param int|null $code The error code + * @param int $code The error code * @param \Throwable|null $previous A previous exception * @param string|null $type The type of resource */ - public function __construct(string $resource, string $sourceResource = null, ?int $code = 0, \Throwable $previous = null, string $type = null) + public function __construct(mixed $resource, string $sourceResource = null, int $code = 0, \Throwable $previous = null, string $type = null) { - if (null === $code) { - trigger_deprecation('symfony/config', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); - - $code = 0; + if (!\is_string($resource)) { + try { + $resource = json_encode($resource, \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + $resource = sprintf('resource of type "%s"', get_debug_type($resource)); + } } $message = ''; @@ -38,7 +40,7 @@ class LoaderLoadException extends \Exception // Include the previous exception, to help the user see what might be the underlying cause // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim... - if ('.' === substr($previous->getMessage(), -1)) { + if (str_ends_with($previous->getMessage(), '.')) { $trimmedMessage = substr($previous->getMessage(), 0, -1); $message .= sprintf('%s', $trimmedMessage).' in '; } else { @@ -68,21 +70,19 @@ class LoaderLoadException extends \Exception $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle); $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource); } elseif (null !== $type) { - // maybe there is no loader for this specific type - if ('annotation' === $type) { - $message .= ' Make sure to use PHP 8+ or that annotations are installed and enabled.'; - } else { - $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type); - } + $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type); } parent::__construct($message, $code, $previous); } - protected function varToString($var) + /** + * @return string + */ + protected function varToString(mixed $var) { if (\is_object($var)) { - return sprintf('Object(%s)', \get_class($var)); + return sprintf('Object(%s)', $var::class); } if (\is_array($var)) { diff --git a/lib/symfony/config/FileLocator.php b/lib/symfony/config/FileLocator.php index da350908a..e147d9b1a 100644 --- a/lib/symfony/config/FileLocator.php +++ b/lib/symfony/config/FileLocator.php @@ -25,13 +25,13 @@ class FileLocator implements FileLocatorInterface /** * @param string|string[] $paths A path or an array of paths where to look for resources */ - public function __construct($paths = []) + public function __construct(string|array $paths = []) { $this->paths = (array) $paths; } /** - * {@inheritdoc} + * @return string|array */ public function locate(string $name, string $currentPath = null, bool $first = true) { diff --git a/lib/symfony/config/LICENSE b/lib/symfony/config/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/lib/symfony/config/LICENSE +++ b/lib/symfony/config/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/config/Loader/DelegatingLoader.php b/lib/symfony/config/Loader/DelegatingLoader.php index e5a74ee63..fac3724e9 100644 --- a/lib/symfony/config/Loader/DelegatingLoader.php +++ b/lib/symfony/config/Loader/DelegatingLoader.php @@ -28,10 +28,7 @@ class DelegatingLoader extends Loader $this->resolver = $resolver; } - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { if (false === $loader = $this->resolver->resolve($resource, $type)) { throw new LoaderLoadException($resource, null, 0, null, $type); @@ -40,10 +37,7 @@ class DelegatingLoader extends Loader return $loader->load($resource, $type); } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { return false !== $this->resolver->resolve($resource, $type); } diff --git a/lib/symfony/config/Loader/DirectoryAwareLoaderInterface.php b/lib/symfony/config/Loader/DirectoryAwareLoaderInterface.php new file mode 100644 index 000000000..87559cba9 --- /dev/null +++ b/lib/symfony/config/Loader/DirectoryAwareLoaderInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * A loader that can be scoped to a given filesystem directory. + * + * @author Alexander M. Turek + */ +interface DirectoryAwareLoaderInterface +{ + public function forDirectory(string $currentDirectory): static; +} diff --git a/lib/symfony/config/Loader/FileLoader.php b/lib/symfony/config/Loader/FileLoader.php index 4e1b46c4e..8cfaa23ba 100644 --- a/lib/symfony/config/Loader/FileLoader.php +++ b/lib/symfony/config/Loader/FileLoader.php @@ -29,7 +29,7 @@ abstract class FileLoader extends Loader protected $locator; - private $currentDir; + private ?string $currentDir = null; public function __construct(FileLocatorInterface $locator, string $env = null) { @@ -39,6 +39,8 @@ abstract class FileLoader extends Loader /** * Sets the current directory. + * + * @return void */ public function setCurrentDir(string $dir) { @@ -47,10 +49,8 @@ abstract class FileLoader extends Loader /** * Returns the file locator used by this loader. - * - * @return FileLocatorInterface */ - public function getLocator() + public function getLocator(): FileLocatorInterface { return $this->locator; } @@ -70,7 +70,7 @@ abstract class FileLoader extends Loader * @throws FileLoaderImportCircularReferenceException * @throws FileLocatorFileNotFoundException */ - public function import($resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, $exclude = null) + public function import(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, string|array $exclude = null) { if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { $excluded = []; @@ -101,7 +101,7 @@ abstract class FileLoader extends Loader /** * @internal */ - protected function glob(string $pattern, bool $recursive, &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = []) + protected function glob(string $pattern, bool $recursive, array|GlobResource &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = []): iterable { if (\strlen($pattern) === $i = strcspn($pattern, '*?{[')) { $prefix = $pattern; @@ -133,12 +133,20 @@ abstract class FileLoader extends Loader yield from $resource; } - private function doImport($resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null) + private function doImport(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null): mixed { try { $loader = $this->resolve($resource, $type); - if ($loader instanceof self && null !== $this->currentDir) { + if ($loader instanceof DirectoryAwareLoaderInterface) { + $loader = $loader->forDirectory($this->currentDir); + } + + if (!$loader instanceof self) { + return $loader->load($resource, $type); + } + + if (null !== $this->currentDir) { $resource = $loader->getLocator()->locate($resource, $this->currentDir, false); } diff --git a/lib/symfony/config/Loader/GlobFileLoader.php b/lib/symfony/config/Loader/GlobFileLoader.php index fecb1c5d0..f921ec555 100644 --- a/lib/symfony/config/Loader/GlobFileLoader.php +++ b/lib/symfony/config/Loader/GlobFileLoader.php @@ -18,18 +18,12 @@ namespace Symfony\Component\Config\Loader; */ class GlobFileLoader extends FileLoader { - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { return $this->import($resource); } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { return 'glob' === $type; } diff --git a/lib/symfony/config/Loader/Loader.php b/lib/symfony/config/Loader/Loader.php index e7d74b5a1..36e85ad34 100644 --- a/lib/symfony/config/Loader/Loader.php +++ b/lib/symfony/config/Loader/Loader.php @@ -28,16 +28,13 @@ abstract class Loader implements LoaderInterface $this->env = $env; } - /** - * {@inheritdoc} - */ - public function getResolver() + public function getResolver(): LoaderResolverInterface { return $this->resolver; } /** - * {@inheritdoc} + * @return void */ public function setResolver(LoaderResolverInterface $resolver) { @@ -47,12 +44,9 @@ abstract class Loader implements LoaderInterface /** * Imports a resource. * - * @param mixed $resource A resource - * @param string|null $type The resource type or null if unknown - * * @return mixed */ - public function import($resource, string $type = null) + public function import(mixed $resource, string $type = null) { return $this->resolve($resource, $type)->load($resource, $type); } @@ -60,14 +54,9 @@ abstract class Loader implements LoaderInterface /** * Finds a loader able to load an imported resource. * - * @param mixed $resource A resource - * @param string|null $type The resource type or null if unknown - * - * @return LoaderInterface - * * @throws LoaderLoadException If no loader is found */ - public function resolve($resource, string $type = null) + public function resolve(mixed $resource, string $type = null): LoaderInterface { if ($this->supports($resource, $type)) { return $this; diff --git a/lib/symfony/config/Loader/LoaderInterface.php b/lib/symfony/config/Loader/LoaderInterface.php index 93a160b1e..4e0746d4d 100644 --- a/lib/symfony/config/Loader/LoaderInterface.php +++ b/lib/symfony/config/Loader/LoaderInterface.php @@ -21,13 +21,11 @@ interface LoaderInterface /** * Loads a resource. * - * @param mixed $resource The resource - * * @return mixed * * @throws \Exception If something went wrong */ - public function load($resource, string $type = null); + public function load(mixed $resource, string $type = null); /** * Returns whether this class supports the given resource. @@ -36,7 +34,7 @@ interface LoaderInterface * * @return bool */ - public function supports($resource, string $type = null); + public function supports(mixed $resource, string $type = null); /** * Gets the loader resolver. @@ -47,6 +45,8 @@ interface LoaderInterface /** * Sets the loader resolver. + * + * @return void */ public function setResolver(LoaderResolverInterface $resolver); } diff --git a/lib/symfony/config/Loader/LoaderResolver.php b/lib/symfony/config/Loader/LoaderResolver.php index cce0702b7..670e32012 100644 --- a/lib/symfony/config/Loader/LoaderResolver.php +++ b/lib/symfony/config/Loader/LoaderResolver.php @@ -24,7 +24,7 @@ class LoaderResolver implements LoaderResolverInterface /** * @var LoaderInterface[] An array of LoaderInterface objects */ - private $loaders = []; + private array $loaders = []; /** * @param LoaderInterface[] $loaders An array of loaders @@ -36,10 +36,7 @@ class LoaderResolver implements LoaderResolverInterface } } - /** - * {@inheritdoc} - */ - public function resolve($resource, string $type = null) + public function resolve(mixed $resource, string $type = null): LoaderInterface|false { foreach ($this->loaders as $loader) { if ($loader->supports($resource, $type)) { @@ -50,6 +47,9 @@ class LoaderResolver implements LoaderResolverInterface return false; } + /** + * @return void + */ public function addLoader(LoaderInterface $loader) { $this->loaders[] = $loader; @@ -61,7 +61,7 @@ class LoaderResolver implements LoaderResolverInterface * * @return LoaderInterface[] */ - public function getLoaders() + public function getLoaders(): array { return $this->loaders; } diff --git a/lib/symfony/config/Loader/LoaderResolverInterface.php b/lib/symfony/config/Loader/LoaderResolverInterface.php index 8a4841947..076c5207c 100644 --- a/lib/symfony/config/Loader/LoaderResolverInterface.php +++ b/lib/symfony/config/Loader/LoaderResolverInterface.php @@ -21,10 +21,7 @@ interface LoaderResolverInterface /** * Returns a loader able to load the resource. * - * @param mixed $resource A resource - * @param string|null $type The resource type or null if unknown - * - * @return LoaderInterface|false + * @param string|null $type The resource type or null if unknown */ - public function resolve($resource, string $type = null); + public function resolve(mixed $resource, string $type = null): LoaderInterface|false; } diff --git a/lib/symfony/config/Loader/ParamConfigurator.php b/lib/symfony/config/Loader/ParamConfigurator.php index 70c3f79a6..d91de6a73 100644 --- a/lib/symfony/config/Loader/ParamConfigurator.php +++ b/lib/symfony/config/Loader/ParamConfigurator.php @@ -18,7 +18,7 @@ namespace Symfony\Component\Config\Loader; */ class ParamConfigurator { - private $name; + private string $name; public function __construct(string $name) { diff --git a/lib/symfony/config/Resource/ClassExistenceResource.php b/lib/symfony/config/Resource/ClassExistenceResource.php index 6aff151cc..cae3877ad 100644 --- a/lib/symfony/config/Resource/ClassExistenceResource.php +++ b/lib/symfony/config/Resource/ClassExistenceResource.php @@ -23,16 +23,16 @@ namespace Symfony\Component\Config\Resource; */ class ClassExistenceResource implements SelfCheckingResourceInterface { - private $resource; - private $exists; + private string $resource; + private ?array $exists = null; - private static $autoloadLevel = 0; - private static $autoloadedClass; - private static $existsCache = []; + private static int $autoloadLevel = 0; + private static ?string $autoloadedClass = null; + private static array $existsCache = []; /** * @param string $resource The fully-qualified class name - * @param bool|null $exists Boolean when the existency check has already been done + * @param bool|null $exists Boolean when the existence check has already been done */ public function __construct(string $resource, bool $exists = null) { @@ -53,8 +53,6 @@ class ClassExistenceResource implements SelfCheckingResourceInterface } /** - * {@inheritdoc} - * * @throws \ReflectionException when a parent class/interface/trait is not found */ public function isFresh(int $timestamp): bool @@ -98,9 +96,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface } } - if (null === $this->exists) { - $this->exists = $exists; - } + $this->exists ??= $exists; return $this->exists[0] xor !$exists[0]; } @@ -120,7 +116,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface /** * @internal */ - public function __wakeup() + public function __wakeup(): void { if (\is_bool($this->exists)) { $this->exists = [$this->exists, null]; @@ -143,7 +139,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface * * @internal */ - public static function throwOnRequiredClass(string $class, \Exception $previous = null) + public static function throwOnRequiredClass(string $class, \Exception $previous = null): void { // If the passed class is the resource being checked, we shouldn't throw. if (null === $previous && self::$autoloadedClass === $class) { @@ -164,7 +160,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface $message = sprintf('Class "%s" not found.', $class); - if (self::$autoloadedClass !== $class) { + if ($class !== (self::$autoloadedClass ?? $class)) { $message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0); } @@ -184,7 +180,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface 'args' => [$class], ]; - if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) { + if (isset($trace[1])) { $callerFrame = $trace[1]; $i = 2; } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) { @@ -221,7 +217,6 @@ class ClassExistenceResource implements SelfCheckingResourceInterface foreach ($props as $p => $v) { if (null !== $v) { $r = new \ReflectionProperty(\Exception::class, $p); - $r->setAccessible(true); $r->setValue($e, $v); } } diff --git a/lib/symfony/config/Resource/ComposerResource.php b/lib/symfony/config/Resource/ComposerResource.php index f552f80ac..834812253 100644 --- a/lib/symfony/config/Resource/ComposerResource.php +++ b/lib/symfony/config/Resource/ComposerResource.php @@ -20,9 +20,9 @@ namespace Symfony\Component\Config\Resource; */ class ComposerResource implements SelfCheckingResourceInterface { - private $vendors; + private array $vendors; - private static $runtimeVendors; + private static array $runtimeVendors; public function __construct() { @@ -40,9 +40,6 @@ class ComposerResource implements SelfCheckingResourceInterface return __CLASS__; } - /** - * {@inheritdoc} - */ public function isFresh(int $timestamp): bool { self::refresh(); @@ -50,7 +47,7 @@ class ComposerResource implements SelfCheckingResourceInterface return array_values(self::$runtimeVendors) === array_values($this->vendors); } - private static function refresh() + private static function refresh(): void { self::$runtimeVendors = []; diff --git a/lib/symfony/config/Resource/DirectoryResource.php b/lib/symfony/config/Resource/DirectoryResource.php index 035814a2a..7560cd3b3 100644 --- a/lib/symfony/config/Resource/DirectoryResource.php +++ b/lib/symfony/config/Resource/DirectoryResource.php @@ -20,8 +20,8 @@ namespace Symfony\Component\Config\Resource; */ class DirectoryResource implements SelfCheckingResourceInterface { - private $resource; - private $pattern; + private string $resource; + private ?string $pattern; /** * @param string $resource The file path to the resource @@ -31,17 +31,19 @@ class DirectoryResource implements SelfCheckingResourceInterface */ public function __construct(string $resource, string $pattern = null) { - $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false); + $resolvedResource = realpath($resource) ?: (file_exists($resource) ? $resource : false); $this->pattern = $pattern; - if (false === $this->resource || !is_dir($this->resource)) { + if (false === $resolvedResource || !is_dir($resolvedResource)) { throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource)); } + + $this->resource = $resolvedResource; } public function __toString(): string { - return md5(serialize([$this->resource, $this->pattern])); + return hash('xxh128', serialize([$this->resource, $this->pattern])); } public function getResource(): string @@ -54,9 +56,6 @@ class DirectoryResource implements SelfCheckingResourceInterface return $this->pattern; } - /** - * {@inheritdoc} - */ public function isFresh(int $timestamp): bool { if (!is_dir($this->resource)) { @@ -82,7 +81,7 @@ class DirectoryResource implements SelfCheckingResourceInterface // for broken links try { $fileMTime = $file->getMTime(); - } catch (\RuntimeException $e) { + } catch (\RuntimeException) { continue; } diff --git a/lib/symfony/config/Resource/FileExistenceResource.php b/lib/symfony/config/Resource/FileExistenceResource.php index 6d79d6d1b..666866ee4 100644 --- a/lib/symfony/config/Resource/FileExistenceResource.php +++ b/lib/symfony/config/Resource/FileExistenceResource.php @@ -23,9 +23,9 @@ namespace Symfony\Component\Config\Resource; */ class FileExistenceResource implements SelfCheckingResourceInterface { - private $resource; + private string $resource; - private $exists; + private bool $exists; /** * @param string $resource The file path to the resource @@ -38,7 +38,7 @@ class FileExistenceResource implements SelfCheckingResourceInterface public function __toString(): string { - return $this->resource; + return 'existence.'.$this->resource; } public function getResource(): string @@ -46,9 +46,6 @@ class FileExistenceResource implements SelfCheckingResourceInterface return $this->resource; } - /** - * {@inheritdoc} - */ public function isFresh(int $timestamp): bool { return file_exists($this->resource) === $this->exists; diff --git a/lib/symfony/config/Resource/FileResource.php b/lib/symfony/config/Resource/FileResource.php index ee6684cdc..6e8f9bdb3 100644 --- a/lib/symfony/config/Resource/FileResource.php +++ b/lib/symfony/config/Resource/FileResource.php @@ -22,10 +22,7 @@ namespace Symfony\Component\Config\Resource; */ class FileResource implements SelfCheckingResourceInterface { - /** - * @var string|false - */ - private $resource; + private string $resource; /** * @param string $resource The file path to the resource @@ -34,11 +31,13 @@ class FileResource implements SelfCheckingResourceInterface */ public function __construct(string $resource) { - $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false); + $resolvedResource = realpath($resource) ?: (file_exists($resource) ? $resource : false); - if (false === $this->resource) { + if (false === $resolvedResource) { throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource)); } + + $this->resource = $resolvedResource; } public function __toString(): string @@ -54,9 +53,6 @@ class FileResource implements SelfCheckingResourceInterface return $this->resource; } - /** - * {@inheritdoc} - */ public function isFresh(int $timestamp): bool { return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp; diff --git a/lib/symfony/config/Resource/GlobResource.php b/lib/symfony/config/Resource/GlobResource.php index 2bfbe2e4b..2aedc84b3 100644 --- a/lib/symfony/config/Resource/GlobResource.php +++ b/lib/symfony/config/Resource/GlobResource.php @@ -27,13 +27,13 @@ use Symfony\Component\Finder\Glob; */ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface { - private $prefix; - private $pattern; - private $recursive; - private $hash; - private $forExclusion; - private $excludedPrefixes; - private $globBrace; + private string $prefix; + private string $pattern; + private bool $recursive; + private string $hash; + private bool $forExclusion; + private array $excludedPrefixes; + private int $globBrace; /** * @param string $prefix A directory prefix @@ -45,16 +45,18 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface public function __construct(string $prefix, string $pattern, bool $recursive, bool $forExclusion = false, array $excludedPrefixes = []) { ksort($excludedPrefixes); - $this->prefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false); + $resolvedPrefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false); $this->pattern = $pattern; $this->recursive = $recursive; $this->forExclusion = $forExclusion; $this->excludedPrefixes = $excludedPrefixes; $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; - if (false === $this->prefix) { + if (false === $resolvedPrefix) { throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix)); } + + $this->prefix = $resolvedPrefix; } public function getPrefix(): string @@ -67,16 +69,10 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface return 'glob.'.$this->prefix.(int) $this->recursive.$this->pattern.(int) $this->forExclusion.implode("\0", $this->excludedPrefixes); } - /** - * {@inheritdoc} - */ public function isFresh(int $timestamp): bool { $hash = $this->computeHash(); - - if (null === $this->hash) { - $this->hash = $hash; - } + $this->hash ??= $hash; return $this->hash === $hash; } @@ -86,9 +82,7 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface */ public function __sleep(): array { - if (null === $this->hash) { - $this->hash = $this->computeHash(); - } + $this->hash ??= $this->computeHash(); return ['prefix', 'pattern', 'recursive', 'hash', 'forExclusion', 'excludedPrefixes']; } @@ -103,28 +97,48 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface public function getIterator(): \Traversable { - if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) { + if ((!$this->recursive && '' === $this->pattern) || !file_exists($this->prefix)) { return; } - $prefix = str_replace('\\', '/', $this->prefix); + + if (is_file($prefix = str_replace('\\', '/', $this->prefix))) { + $prefix = \dirname($prefix); + $pattern = basename($prefix).$this->pattern; + } else { + $pattern = $this->pattern; + } + + if (class_exists(Finder::class)) { + $regex = Glob::toRegex($pattern); + if ($this->recursive) { + $regex = substr_replace($regex, '(/|$)', -2, 1); + } + } else { + $regex = null; + } + + $prefixLen = \strlen($prefix); $paths = null; - if ('' === $this->pattern && is_file($prefix)) { - $paths = [$this->prefix]; - } elseif (!str_starts_with($this->prefix, 'phar://') && !str_contains($this->pattern, '/**/')) { - if ($this->globBrace || !str_contains($this->pattern, '{')) { - $paths = glob($this->prefix.$this->pattern, \GLOB_NOSORT | $this->globBrace); + if ('' === $this->pattern && is_file($this->prefix)) { + $paths = [$this->prefix => null]; + } elseif (!str_starts_with($this->prefix, 'phar://') && (null !== $regex || !str_contains($this->pattern, '/**/'))) { + if (!str_contains($this->pattern, '/**/') && ($this->globBrace || !str_contains($this->pattern, '{'))) { + $paths = array_fill_keys(glob($this->prefix.$this->pattern, \GLOB_NOSORT | $this->globBrace), null); } elseif (!str_contains($this->pattern, '\\') || !preg_match('/\\\\[,{}]/', $this->pattern)) { + $paths = []; foreach ($this->expandGlob($this->pattern) as $p) { - $paths[] = glob($this->prefix.$p, \GLOB_NOSORT); + if (false !== $i = strpos($p, '/**/')) { + $p = substr_replace($p, '/*', $i); + } + $paths += array_fill_keys(glob($this->prefix.$p, \GLOB_NOSORT), false !== $i ? $regex : null); } - $paths = array_merge(...$paths); } } if (null !== $paths) { - natsort($paths); - foreach ($paths as $path) { + uksort($paths, 'strnatcmp'); + foreach ($paths as $path => $regex) { if ($this->excludedPrefixes) { $normalizedPath = str_replace('\\', '/', $path); do { @@ -134,25 +148,25 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath)); } - if (is_file($path)) { + if ((null === $regex || preg_match($regex, substr(str_replace('\\', '/', $path), $prefixLen))) && is_file($path)) { yield $path => new \SplFileInfo($path); } if (!is_dir($path)) { continue; } - if ($this->forExclusion) { + if ($this->forExclusion && (null === $regex || preg_match($regex, substr(str_replace('\\', '/', $path), $prefixLen)))) { yield $path => new \SplFileInfo($path); continue; } - if (!$this->recursive || isset($this->excludedPrefixes[str_replace('\\', '/', $path)])) { + if (!($this->recursive || null !== $regex) || isset($this->excludedPrefixes[str_replace('\\', '/', $path)])) { continue; } $files = iterator_to_array(new \RecursiveIteratorIterator( new \RecursiveCallbackFilterIterator( new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), - function (\SplFileInfo $file, $path) { - return !isset($this->excludedPrefixes[str_replace('\\', '/', $path)]) && '.' !== $file->getBasename()[0]; - } + fn (\SplFileInfo $file, $path) => !isset($this->excludedPrefixes[$path = str_replace('\\', '/', $path)]) + && (null === $regex || preg_match($regex, substr($path, $prefixLen)) || $file->isDir()) + && '.' !== $file->getBasename()[0] ), \RecursiveIteratorIterator::LEAVES_ONLY )); @@ -169,43 +183,32 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface } if (!class_exists(Finder::class)) { - throw new \LogicException(sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern)); + throw new \LogicException('Extended glob patterns cannot be used as the Finder component is not installed. Try running "composer require symfony/finder".'); } - if (is_file($prefix = $this->prefix)) { - $prefix = \dirname($prefix); - $pattern = basename($prefix).$this->pattern; - } else { - $pattern = $this->pattern; - } - - $finder = new Finder(); - $regex = Glob::toRegex($pattern); - if ($this->recursive) { - $regex = substr_replace($regex, '(/|$)', -2, 1); - } - - $prefixLen = \strlen($prefix); - foreach ($finder->followLinks()->sortByName()->in($prefix) as $path => $info) { - $normalizedPath = str_replace('\\', '/', $path); - if (!preg_match($regex, substr($normalizedPath, $prefixLen)) || !$info->isFile()) { - continue; - } - if ($this->excludedPrefixes) { - do { - if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { - continue 2; - } - } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath)); - } - - yield $path => $info; - } + yield from (new Finder()) + ->followLinks() + ->filter(function (\SplFileInfo $info) use ($regex, $prefixLen, $prefix) { + $normalizedPath = str_replace('\\', '/', $info->getPathname()); + if (!preg_match($regex, substr($normalizedPath, $prefixLen)) || !$info->isFile()) { + return false; + } + if ($this->excludedPrefixes) { + do { + if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { + return false; + } + } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath)); + } + }) + ->sortByName() + ->in($prefix) + ; } private function computeHash(): string { - $hash = hash_init('md5'); + $hash = hash_init('xxh128'); foreach ($this->getIterator() as $path => $info) { hash_update($hash, $path."\n"); diff --git a/lib/symfony/config/Resource/ReflectionClassResource.php b/lib/symfony/config/Resource/ReflectionClassResource.php index 06f1d766e..dbd47e66d 100644 --- a/lib/symfony/config/Resource/ReflectionClassResource.php +++ b/lib/symfony/config/Resource/ReflectionClassResource.php @@ -22,11 +22,11 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; */ class ReflectionClassResource implements SelfCheckingResourceInterface { - private $files = []; - private $className; - private $classReflector; - private $excludedVendors = []; - private $hash; + private array $files = []; + private string $className; + private \ReflectionClass $classReflector; + private array $excludedVendors = []; + private string $hash; public function __construct(\ReflectionClass $classReflector, array $excludedVendors = []) { @@ -35,12 +35,9 @@ class ReflectionClassResource implements SelfCheckingResourceInterface $this->excludedVendors = $excludedVendors; } - /** - * {@inheritdoc} - */ public function isFresh(int $timestamp): bool { - if (null === $this->hash) { + if (!isset($this->hash)) { $this->hash = $this->computeHash(); $this->loadFiles($this->classReflector); } @@ -68,7 +65,7 @@ class ReflectionClassResource implements SelfCheckingResourceInterface */ public function __sleep(): array { - if (null === $this->hash) { + if (!isset($this->hash)) { $this->hash = $this->computeHash(); $this->loadFiles($this->classReflector); } @@ -76,7 +73,7 @@ class ReflectionClassResource implements SelfCheckingResourceInterface return ['files', 'className', 'hash']; } - private function loadFiles(\ReflectionClass $class) + private function loadFiles(\ReflectionClass $class): void { foreach ($class->getInterfaces() as $v) { $this->loadFiles($v); @@ -102,15 +99,13 @@ class ReflectionClassResource implements SelfCheckingResourceInterface private function computeHash(): string { - if (null === $this->classReflector) { - try { - $this->classReflector = new \ReflectionClass($this->className); - } catch (\ReflectionException $e) { - // the class does not exist anymore - return false; - } + try { + $this->classReflector ??= new \ReflectionClass($this->className); + } catch (\ReflectionException) { + // the class does not exist anymore + return false; } - $hash = hash_init('md5'); + $hash = hash_init('xxh128'); foreach ($this->generateSignature($this->classReflector) as $info) { hash_update($hash, $info); @@ -121,14 +116,12 @@ class ReflectionClassResource implements SelfCheckingResourceInterface private function generateSignature(\ReflectionClass $class): iterable { - if (\PHP_VERSION_ID >= 80000) { - $attributes = []; - foreach ($class->getAttributes() as $a) { - $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; - } - yield print_r($attributes, true); - $attributes = []; + $attributes = []; + foreach ($class->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; } + yield print_r($attributes, true); + $attributes = []; yield $class->getDocComment(); yield (int) $class->isFinal(); @@ -146,13 +139,11 @@ class ReflectionClassResource implements SelfCheckingResourceInterface $defaults = $class->getDefaultProperties(); foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) { - if (\PHP_VERSION_ID >= 80000) { - foreach ($p->getAttributes() as $a) { - $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; - } - yield print_r($attributes, true); - $attributes = []; + foreach ($p->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; } + yield print_r($attributes, true); + $attributes = []; yield $p->getDocComment(); yield $p->isDefault() ? '' : ''; @@ -163,27 +154,20 @@ class ReflectionClassResource implements SelfCheckingResourceInterface } } - $defined = \Closure::bind(static function ($c) { return \defined($c); }, null, $class->name); - foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { - if (\PHP_VERSION_ID >= 80000) { - foreach ($m->getAttributes() as $a) { - $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; + foreach ($m->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; + } + yield print_r($attributes, true); + $attributes = []; + + $defaults = []; + foreach ($m->getParameters() as $p) { + foreach ($p->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; } yield print_r($attributes, true); $attributes = []; - } - - $defaults = []; - $parametersWithUndefinedConstants = []; - foreach ($m->getParameters() as $p) { - if (\PHP_VERSION_ID >= 80000) { - foreach ($p->getAttributes() as $a) { - $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; - } - yield print_r($attributes, true); - $attributes = []; - } if (!$p->isDefaultValueAvailable()) { $defaults[$p->name] = null; @@ -191,55 +175,10 @@ class ReflectionClassResource implements SelfCheckingResourceInterface continue; } - if (\PHP_VERSION_ID >= 80100) { - $defaults[$p->name] = (string) $p; - - continue; - } - - if (!$p->isDefaultValueConstant() || $defined($p->getDefaultValueConstantName())) { - $defaults[$p->name] = $p->getDefaultValue(); - - continue; - } - - $defaults[$p->name] = $p->getDefaultValueConstantName(); - $parametersWithUndefinedConstants[$p->name] = true; - } - - if (!$parametersWithUndefinedConstants) { - yield preg_replace('/^ @@.*/m', '', $m); - } else { - $t = $m->getReturnType(); - $stack = [ - $m->getDocComment(), - $m->getName(), - $m->isAbstract(), - $m->isFinal(), - $m->isStatic(), - $m->isPublic(), - $m->isPrivate(), - $m->isProtected(), - $m->returnsReference(), - $t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t, - ]; - - foreach ($m->getParameters() as $p) { - if (!isset($parametersWithUndefinedConstants[$p->name])) { - $stack[] = (string) $p; - } else { - $t = $p->getType(); - $stack[] = $p->isOptional(); - $stack[] = $t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t; - $stack[] = $p->isPassedByReference(); - $stack[] = $p->isVariadic(); - $stack[] = $p->getName(); - } - } - - yield implode(',', $stack); + $defaults[$p->name] = (string) $p; } + yield preg_replace('/^ @@.*/m', '', $m); yield print_r($defaults, true); } diff --git a/lib/symfony/config/Resource/ResourceInterface.php b/lib/symfony/config/Resource/ResourceInterface.php index 9a0cd9a47..a97671d14 100644 --- a/lib/symfony/config/Resource/ResourceInterface.php +++ b/lib/symfony/config/Resource/ResourceInterface.php @@ -16,7 +16,7 @@ namespace Symfony\Component\Config\Resource; * * @author Fabien Potencier */ -interface ResourceInterface +interface ResourceInterface extends \Stringable { /** * Returns a string representation of the Resource. @@ -27,5 +27,5 @@ interface ResourceInterface * resource; and it should be unlikely to collide with that of other, unrelated * resource instances. */ - public function __toString(); + public function __toString(): string; } diff --git a/lib/symfony/config/Resource/SelfCheckingResourceChecker.php b/lib/symfony/config/Resource/SelfCheckingResourceChecker.php index e1727b963..5abdbadd4 100644 --- a/lib/symfony/config/Resource/SelfCheckingResourceChecker.php +++ b/lib/symfony/config/Resource/SelfCheckingResourceChecker.php @@ -27,9 +27,9 @@ class SelfCheckingResourceChecker implements ResourceCheckerInterface // situations. For example, when using the full stack framework, the router // and the container have their own cache. But they may check the very same // resources - private static $cache = []; + private static array $cache = []; - public function supports(ResourceInterface $metadata) + public function supports(ResourceInterface $metadata): bool { return $metadata instanceof SelfCheckingResourceInterface; } @@ -37,10 +37,10 @@ class SelfCheckingResourceChecker implements ResourceCheckerInterface /** * @param SelfCheckingResourceInterface $resource */ - public function isFresh(ResourceInterface $resource, int $timestamp) + public function isFresh(ResourceInterface $resource, int $timestamp): bool { $key = "$resource:$timestamp"; - return self::$cache[$key] ?? self::$cache[$key] = $resource->isFresh($timestamp); + return self::$cache[$key] ??= $resource->isFresh($timestamp); } } diff --git a/lib/symfony/config/Resource/SelfCheckingResourceInterface.php b/lib/symfony/config/Resource/SelfCheckingResourceInterface.php index 2c1a37846..197ff1f9b 100644 --- a/lib/symfony/config/Resource/SelfCheckingResourceInterface.php +++ b/lib/symfony/config/Resource/SelfCheckingResourceInterface.php @@ -23,8 +23,6 @@ interface SelfCheckingResourceInterface extends ResourceInterface * Returns true if the resource has not been updated since the given timestamp. * * @param int $timestamp The last time the resource was loaded - * - * @return bool */ - public function isFresh(int $timestamp); + public function isFresh(int $timestamp): bool; } diff --git a/lib/symfony/config/ResourceCheckerConfigCache.php b/lib/symfony/config/ResourceCheckerConfigCache.php index ba0e180c5..a8478a8cc 100644 --- a/lib/symfony/config/ResourceCheckerConfigCache.php +++ b/lib/symfony/config/ResourceCheckerConfigCache.php @@ -23,15 +23,12 @@ use Symfony\Component\Filesystem\Filesystem; */ class ResourceCheckerConfigCache implements ConfigCacheInterface { - /** - * @var string - */ - private $file; + private string $file; /** * @var iterable */ - private $resourceCheckers; + private iterable $resourceCheckers; /** * @param string $file The absolute cache path @@ -43,10 +40,7 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface $this->resourceCheckers = $resourceCheckers; } - /** - * {@inheritdoc} - */ - public function getPath() + public function getPath(): string { return $this->file; } @@ -59,10 +53,8 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface * * The first ResourceChecker that supports a given resource is considered authoritative. * Resources with no matching ResourceChecker will silently be ignored and considered fresh. - * - * @return bool */ - public function isFresh() + public function isFresh(): bool { if (!is_file($this->file)) { return false; @@ -113,6 +105,8 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface * @param string $content The content to write in the cache * @param ResourceInterface[] $metadata An array of metadata * + * @return void + * * @throws \RuntimeException When cache file can't be written */ public function write(string $content, array $metadata = null) @@ -123,7 +117,7 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface $filesystem->dumpFile($this->file, $content); try { $filesystem->chmod($this->file, $mode, $umask); - } catch (IOException $e) { + } catch (IOException) { // discard chmod failure (some filesystem may not support it) } @@ -131,12 +125,12 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface $filesystem->dumpFile($this->getMetaFile(), serialize($metadata)); try { $filesystem->chmod($this->getMetaFile(), $mode, $umask); - } catch (IOException $e) { + } catch (IOException) { // discard chmod failure (some filesystem may not support it) } } - if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { + if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL)) { @opcache_invalidate($this->file, true); } } @@ -149,7 +143,7 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface return $this->file.'.meta'; } - private function safelyUnserialize(string $file) + private function safelyUnserialize(string $file): mixed { $meta = false; $content = file_get_contents($file); @@ -180,7 +174,7 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface /** * @internal */ - public static function handleUnserializeCallback(string $class) + public static function handleUnserializeCallback(string $class): void { trigger_error('Class not found: '.$class); } diff --git a/lib/symfony/config/ResourceCheckerConfigCacheFactory.php b/lib/symfony/config/ResourceCheckerConfigCacheFactory.php index 21b7433e8..97d52006f 100644 --- a/lib/symfony/config/ResourceCheckerConfigCacheFactory.php +++ b/lib/symfony/config/ResourceCheckerConfigCacheFactory.php @@ -19,7 +19,7 @@ namespace Symfony\Component\Config; */ class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface { - private $resourceCheckers = []; + private iterable $resourceCheckers = []; /** * @param iterable $resourceCheckers @@ -29,10 +29,7 @@ class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface $this->resourceCheckers = $resourceCheckers; } - /** - * {@inheritdoc} - */ - public function cache(string $file, callable $callable) + public function cache(string $file, callable $callable): ConfigCacheInterface { $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers); if (!$cache->isFresh()) { diff --git a/lib/symfony/config/Util/XmlUtils.php b/lib/symfony/config/Util/XmlUtils.php index 8258a0627..cc024da46 100644 --- a/lib/symfony/config/Util/XmlUtils.php +++ b/lib/symfony/config/Util/XmlUtils.php @@ -38,40 +38,28 @@ class XmlUtils * @param string $content An XML string * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation * - * @return \DOMDocument - * * @throws XmlParsingException When parsing of XML file returns error * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself * @throws \RuntimeException When DOM extension is missing */ - public static function parse(string $content, $schemaOrCallable = null) + public static function parse(string $content, string|callable $schemaOrCallable = null): \DOMDocument { if (!\extension_loaded('dom')) { throw new \LogicException('Extension DOM is required.'); } $internalErrors = libxml_use_internal_errors(true); - if (\LIBXML_VERSION < 20900) { - $disableEntities = libxml_disable_entity_loader(true); - } libxml_clear_errors(); $dom = new \DOMDocument(); $dom->validateOnParse = true; - if (!$dom->loadXML($content, \LIBXML_NONET | (\defined('LIBXML_COMPACT') ? \LIBXML_COMPACT : 0))) { - if (\LIBXML_VERSION < 20900) { - libxml_disable_entity_loader($disableEntities); - } - + if (!$dom->loadXML($content, \LIBXML_NONET | \LIBXML_COMPACT)) { throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors))); } $dom->normalizeDocument(); libxml_use_internal_errors($internalErrors); - if (\LIBXML_VERSION < 20900) { - libxml_disable_entity_loader($disableEntities); - } foreach ($dom->childNodes as $child) { if (\XML_DOCUMENT_TYPE_NODE === $child->nodeType) { @@ -90,18 +78,18 @@ class XmlUtils } catch (\Exception $e) { $valid = false; } - } elseif (!\is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) { + } elseif (is_file($schemaOrCallable)) { $schemaSource = file_get_contents((string) $schemaOrCallable); $valid = @$dom->schemaValidateSource($schemaSource); } else { libxml_use_internal_errors($internalErrors); - throw new XmlParsingException('The schemaOrCallable argument has to be a valid path to XSD file or callable.'); + throw new XmlParsingException(sprintf('Invalid XSD file: "%s".', $schemaOrCallable)); } if (!$valid) { $messages = static::getXmlErrors($internalErrors); - if (empty($messages)) { + if (!$messages) { throw new InvalidXmlException('The XML is not valid.', 0, $e); } throw new XmlParsingException(implode("\n", $messages), 0, $e); @@ -120,13 +108,11 @@ class XmlUtils * @param string $file An XML file path * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation * - * @return \DOMDocument - * * @throws \InvalidArgumentException When loading of XML file returns error * @throws XmlParsingException When XML parsing returns any errors * @throws \RuntimeException When DOM extension is missing */ - public static function loadFile(string $file, $schemaOrCallable = null) + public static function loadFile(string $file, string|callable $schemaOrCallable = null): \DOMDocument { if (!is_file($file)) { throw new \InvalidArgumentException(sprintf('Resource "%s" is not a file.', $file)); @@ -166,10 +152,8 @@ class XmlUtils * * @param \DOMElement $element A \DOMElement instance * @param bool $checkPrefix Check prefix in an element or an attribute name - * - * @return mixed */ - public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = true) + public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = true): mixed { $prefix = (string) $element->prefix; $empty = true; @@ -222,12 +206,8 @@ class XmlUtils /** * Converts an xml value to a PHP type. - * - * @param mixed $value - * - * @return mixed */ - public static function phpize($value) + public static function phpize(string|\Stringable $value): mixed { $value = (string) $value; $lowercaseValue = strtolower($value); @@ -258,6 +238,9 @@ class XmlUtils } } + /** + * @return array + */ protected static function getXmlErrors(bool $internalErrors) { $errors = []; diff --git a/lib/symfony/config/composer.json b/lib/symfony/config/composer.json index b357ed317..dbd34f13b 100644 --- a/lib/symfony/config/composer.json +++ b/lib/symfony/config/composer.json @@ -16,25 +16,21 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "conflict": { - "symfony/finder": "<4.4" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, "autoload": { "psr-4": { "Symfony\\Component\\Config\\": "" }, diff --git a/lib/symfony/console/Application.php b/lib/symfony/console/Application.php index 29951e9c1..01b6a373d 100644 --- a/lib/symfony/console/Application.php +++ b/lib/symfony/console/Application.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\Command\SignalableCommandInterface; use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleSignalEvent; @@ -32,6 +33,7 @@ use Symfony\Component\Console\Exception\NamespaceNotFoundException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\HelperSet; @@ -70,23 +72,24 @@ use Symfony\Contracts\Service\ResetInterface; */ class Application implements ResetInterface { - private $commands = []; - private $wantHelps = false; - private $runningCommand; - private $name; - private $version; - private $commandLoader; - private $catchExceptions = true; - private $autoExit = true; - private $definition; - private $helperSet; - private $dispatcher; - private $terminal; - private $defaultCommand; - private $singleCommand = false; - private $initialized; - private $signalRegistry; - private $signalsToDispatchEvent = []; + private array $commands = []; + private bool $wantHelps = false; + private ?Command $runningCommand = null; + private string $name; + private string $version; + private ?CommandLoaderInterface $commandLoader = null; + private bool $catchExceptions = true; + private bool $catchErrors = false; + private bool $autoExit = true; + private InputDefinition $definition; + private HelperSet $helperSet; + private ?EventDispatcherInterface $dispatcher = null; + private Terminal $terminal; + private string $defaultCommand; + private bool $singleCommand = false; + private bool $initialized = false; + private ?SignalRegistry $signalRegistry = null; + private array $signalsToDispatchEvent = []; public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') { @@ -103,11 +106,14 @@ class Application implements ResetInterface /** * @final */ - public function setDispatcher(EventDispatcherInterface $dispatcher) + public function setDispatcher(EventDispatcherInterface $dispatcher): void { $this->dispatcher = $dispatcher; } + /** + * @return void + */ public function setCommandLoader(CommandLoaderInterface $commandLoader) { $this->commandLoader = $commandLoader; @@ -116,12 +122,15 @@ class Application implements ResetInterface public function getSignalRegistry(): 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 void + */ public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent) { $this->signalsToDispatchEvent = $signalsToDispatchEvent; @@ -134,20 +143,15 @@ class Application implements ResetInterface * * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. */ - public function run(InputInterface $input = null, OutputInterface $output = null) + public function run(InputInterface $input = null, OutputInterface $output = null): int { if (\function_exists('putenv')) { @putenv('LINES='.$this->terminal->getHeight()); @putenv('COLUMNS='.$this->terminal->getWidth()); } - if (null === $input) { - $input = new ArgvInput(); - } - - if (null === $output) { - $output = new ConsoleOutput(); - } + $input ??= new ArgvInput(); + $output ??= new ConsoleOutput(); $renderException = function (\Throwable $e) use ($output) { if ($output instanceof ConsoleOutputInterface) { @@ -169,8 +173,11 @@ class Application implements ResetInterface try { $exitCode = $this->doRun($input, $output); - } catch (\Exception $e) { - if (!$this->catchExceptions) { + } catch (\Throwable $e) { + if ($e instanceof \Exception && !$this->catchExceptions) { + throw $e; + } + if (!$e instanceof \Exception && !$this->catchErrors) { throw $e; } @@ -228,7 +235,7 @@ class Application implements ResetInterface try { // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. $input->bind($this->getDefinition()); - } catch (ExceptionInterface $e) { + } catch (ExceptionInterface) { // Errors must be ignored, full binding/validation happens later when the command is known. } @@ -258,7 +265,26 @@ class Application implements ResetInterface // the command name MUST be the first element of the input $command = $this->find($name); } catch (\Throwable $e) { - if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) { + if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) { + $alternative = $alternatives[0]; + + $style = new SymfonyStyle($input, $output); + $output->writeln(''); + $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); + $output->writeln($formattedBlock); + if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + return $event->getExitCode(); + } + + return 1; + } + + $command = $this->find($alternative); + } else { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); @@ -270,27 +296,24 @@ class Application implements ResetInterface $e = $event->getError(); } - throw $e; - } + try { + if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) { + $helper = new DescriptorHelper(); + $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [ + 'format' => 'txt', + 'raw_text' => false, + 'namespace' => $namespace, + 'short' => false, + ]); - $alternative = $alternatives[0]; + return isset($event) ? $event->getExitCode() : 1; + } - $style = new SymfonyStyle($input, $output); - $output->writeln(''); - $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); - $output->writeln($formattedBlock); - if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { - if (null !== $this->dispatcher) { - $event = new ConsoleErrorEvent($input, $output, $e); - $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); - - return $event->getExitCode(); + throw $e; + } catch (NamespaceNotFoundException) { + throw $e; } - - return 1; } - - $command = $this->find($alternative); } if ($command instanceof LazyCommand) { @@ -305,12 +328,15 @@ class Application implements ResetInterface } /** - * {@inheritdoc} + * @return void */ public function reset() { } + /** + * @return void + */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; @@ -318,18 +344,15 @@ class Application implements ResetInterface /** * Get the helper set associated with the command. - * - * @return HelperSet */ - public function getHelperSet() + public function getHelperSet(): HelperSet { - if (!$this->helperSet) { - $this->helperSet = $this->getDefaultHelperSet(); - } - - return $this->helperSet; + return $this->helperSet ??= $this->getDefaultHelperSet(); } + /** + * @return void + */ public function setDefinition(InputDefinition $definition) { $this->definition = $definition; @@ -337,14 +360,10 @@ class Application implements ResetInterface /** * Gets the InputDefinition related to this Application. - * - * @return InputDefinition */ - public function getDefinition() + public function getDefinition(): InputDefinition { - if (!$this->definition) { - $this->definition = $this->getDefaultInputDefinition(); - } + $this->definition ??= $this->getDefaultInputDefinition(); if ($this->singleCommand) { $inputDefinition = $this->definition; @@ -365,18 +384,16 @@ class Application implements ResetInterface CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && 'command' === $input->getCompletionName() ) { - $commandNames = []; foreach ($this->all() as $name => $command) { // skip hidden commands and aliased commands as they already get added below if ($command->isHidden() || $command->getName() !== $name) { continue; } - $commandNames[] = $command->getName(); + $suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription())); foreach ($command->getAliases() as $name) { - $commandNames[] = $name; + $suggestions->suggestValue(new Suggestion($name, $command->getDescription())); } } - $suggestions->suggestValues(array_filter($commandNames)); return; } @@ -390,26 +407,24 @@ class Application implements ResetInterface /** * Gets the help message. - * - * @return string */ - public function getHelp() + public function getHelp(): string { return $this->getLongVersion(); } /** * Gets whether to catch exceptions or not during commands execution. - * - * @return bool */ - public function areExceptionsCaught() + public function areExceptionsCaught(): bool { return $this->catchExceptions; } /** * Sets whether to catch exceptions or not during commands execution. + * + * @return void */ public function setCatchExceptions(bool $boolean) { @@ -417,17 +432,25 @@ class Application implements ResetInterface } /** - * Gets whether to automatically exit after a command execution or not. - * - * @return bool + * Sets whether to catch errors or not during commands execution. */ - public function isAutoExitEnabled() + public function setCatchErrors(bool $catchErrors = true): void + { + $this->catchErrors = $catchErrors; + } + + /** + * Gets whether to automatically exit after a command execution or not. + */ + public function isAutoExitEnabled(): bool { return $this->autoExit; } /** * Sets whether to automatically exit after a command execution or not. + * + * @return void */ public function setAutoExit(bool $boolean) { @@ -436,17 +459,17 @@ class Application implements ResetInterface /** * Gets the name of the application. - * - * @return string */ - public function getName() + public function getName(): string { return $this->name; } /** * Sets the application name. - **/ + * + * @return void + */ public function setName(string $name) { $this->name = $name; @@ -454,16 +477,16 @@ class Application implements ResetInterface /** * Gets the application version. - * - * @return string */ - public function getVersion() + public function getVersion(): string { return $this->version; } /** * Sets the application version. + * + * @return void */ public function setVersion(string $version) { @@ -490,10 +513,8 @@ class Application implements ResetInterface /** * Registers a new command. - * - * @return Command */ - public function register(string $name) + public function register(string $name): Command { return $this->add(new Command($name)); } @@ -504,6 +525,8 @@ class Application implements ResetInterface * If a Command is not enabled it will not be added. * * @param Command[] $commands An array of commands + * + * @return void */ public function addCommands(array $commands) { @@ -586,14 +609,12 @@ class Application implements ResetInterface /** * Returns true if the command exists, false otherwise. - * - * @return bool */ - public function has(string $name) + public function has(string $name): bool { $this->init(); - return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); + return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name))); } /** @@ -603,7 +624,7 @@ class Application implements ResetInterface * * @return string[] */ - public function getNamespaces() + public function getNamespaces(): array { $namespaces = []; foreach ($this->all() as $command) { @@ -624,11 +645,9 @@ class Application implements ResetInterface /** * Finds a registered namespace by a name or an abbreviation. * - * @return string - * * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous */ - public function findNamespace(string $namespace) + public function findNamespace(string $namespace): string { $allNamespaces = $this->getNamespaces(); $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*'; @@ -705,9 +724,7 @@ class Application implements ResetInterface if ($alternatives = $this->findAlternatives($name, $allCommands)) { // remove hidden commands - $alternatives = array_filter($alternatives, function ($name) { - return !$this->get($name)->isHidden(); - }); + $alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden()); if (1 == \count($alternatives)) { $message .= "\n\nDid you mean this?\n "; @@ -820,7 +837,7 @@ class Application implements ResetInterface * * @return string[][] */ - public static function getAbbreviations(array $names) + public static function getAbbreviations(array $names): array { $abbrevs = []; foreach ($names as $name) { @@ -858,9 +875,7 @@ class Application implements ResetInterface } 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) { - return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; - }, $message); + $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); } $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; @@ -921,6 +936,8 @@ class Application implements ResetInterface /** * Configures the input and output instances based on the user arguments and options. + * + * @return void */ protected function configureIO(InputInterface $input, OutputInterface $output) { @@ -995,44 +1012,65 @@ 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 (!$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 (Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); - if (Terminal::hasSttyAvailable()) { - $sttyMode = shell_exec('stty -g'); - - foreach ([\SIGINT, \SIGTERM] as $signal) { - $this->signalRegistry->register($signal, static function () use ($sttyMode) { - shell_exec('stty '.$sttyMode); - }); - } + foreach ([\SIGINT, \SIGTERM] as $signal) { + $this->signalRegistry->register($signal, static fn () => 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) { $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); + $exitCode = $event->getExitCode(); - // No more handlers, we try to simulate PHP default behavior - if (!$hasNext) { - if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) { - exit(0); + // If the command is signalable, we call the handleSignal() method + if (\in_array($signal, $commandSignals, true)) { + $exitCode = $command->handleSignal($signal, $exitCode); + // 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) { + $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + exit($event->getExitCode()); + } }); } + + // then we register command signals, but not if already handled after the dispatcher + $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent); } 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); + } + }); } } @@ -1044,7 +1082,7 @@ class Application implements ResetInterface try { $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); - } catch (ExceptionInterface $e) { + } catch (ExceptionInterface) { // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition } @@ -1081,20 +1119,16 @@ class Application implements ResetInterface /** * Gets the name of the command based on input. - * - * @return string|null */ - protected function getCommandName(InputInterface $input) + protected function getCommandName(InputInterface $input): ?string { return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); } /** * Gets the default input definition. - * - * @return InputDefinition */ - protected function getDefaultInputDefinition() + protected function getDefaultInputDefinition(): InputDefinition { return new InputDefinition([ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), @@ -1112,17 +1146,15 @@ class Application implements ResetInterface * * @return Command[] */ - protected function getDefaultCommands() + protected function getDefaultCommands(): array { return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()]; } /** * Gets the default helper set with the helpers that should always be available. - * - * @return HelperSet */ - protected function getDefaultHelperSet() + protected function getDefaultHelperSet(): HelperSet { return new HelperSet([ new FormatterHelper(), @@ -1144,10 +1176,8 @@ class Application implements ResetInterface * Returns the namespace part of the command name. * * This method is not part of public API and should not be used directly. - * - * @return string */ - public function extractNamespace(string $name, int $limit = null) + public function extractNamespace(string $name, int $limit = null): string { $parts = explode(':', $name, -1); @@ -1196,7 +1226,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); return array_keys($alternatives); @@ -1207,7 +1237,7 @@ class Application implements ResetInterface * * @return $this */ - public function setDefaultCommand(string $commandName, bool $isSingleCommand = false) + public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static { $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0]; @@ -1287,7 +1317,7 @@ class Application implements ResetInterface return $namespaces; } - private function init() + private function init(): void { if ($this->initialized) { return; diff --git a/lib/symfony/console/CHANGELOG.md b/lib/symfony/console/CHANGELOG.md index 6662dd1eb..9ccb41d94 100644 --- a/lib/symfony/console/CHANGELOG.md +++ b/lib/symfony/console/CHANGELOG.md @@ -1,6 +1,50 @@ CHANGELOG ========= +6.4 +--- + + * Add `SignalMap` to map signal value to its name + * Multi-line text in vertical tables is aligned properly + * The application can also catch errors with `Application::setCatchErrors(true)` + * Add `RunCommandMessage` and `RunCommandMessageHandler` + * Dispatch `ConsoleTerminateEvent` after an exit on signal handling and add `ConsoleTerminateEvent::getInterruptingSignal()` + +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 +--- + + * Improve truecolor terminal detection in some cases + * Add support for 256 color terminals (conversion from Ansi24 to Ansi8 if terminal is capable of it) + * Deprecate calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()`, `Question::setAutocompleterCallback/setValidator()`without any arguments + * Change the signature of `OutputFormatterStyleInterface::setForeground/setBackground()` to `setForeground/setBackground(?string)` + * Change the signature of `HelperInterface::setHelperSet()` to `setHelperSet(?HelperSet)` + +6.1 +--- + + * Add support to display table vertically when calling setVertical() + * Add method `__toString()` to `InputInterface` + * Added `OutputWrapper` to prevent truncated URL in `SymfonyStyle::createBlock`. + * Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead + * Add suggested values for arguments and options in input definition, for input completion + * Add `$resumeAt` parameter to `ProgressBar#start()`, so that one can easily 'resume' progress on longer tasks, and still get accurate `getEstimate()` and `getRemaining()` results. + +6.0 +--- + + * `Command::setHidden()` has a default value (`true`) for `$hidden` parameter and is final + * Remove `Helper::strlen()`, use `Helper::width()` instead + * Remove `Helper::strlenWithoutDecoration()`, use `Helper::removeDecoration()` instead + * `AddConsoleCommandPass` can not be configured anymore + * Remove `HelperSet::setCommand()` and `getCommand()` without replacement + 5.4 --- diff --git a/lib/symfony/console/CI/GithubActionReporter.php b/lib/symfony/console/CI/GithubActionReporter.php index a15c1ff18..7e5565469 100644 --- a/lib/symfony/console/CI/GithubActionReporter.php +++ b/lib/symfony/console/CI/GithubActionReporter.php @@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface; */ class GithubActionReporter { - private $output; + private OutputInterface $output; /** * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 diff --git a/lib/symfony/console/Color.php b/lib/symfony/console/Color.php index 22a4ce9ff..60ed046a6 100644 --- a/lib/symfony/console/Color.php +++ b/lib/symfony/console/Color.php @@ -49,9 +49,9 @@ final class Color 'conceal' => ['set' => 8, 'unset' => 28], ]; - private $foreground; - private $background; - private $options = []; + private string $foreground; + private string $background; + private array $options = []; public function __construct(string $foreground = '', string $background = '', array $options = []) { @@ -117,17 +117,7 @@ final class Color } if ('#' === $color[0]) { - $color = substr($color, 1); - - if (3 === \strlen($color)) { - $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2]; - } - - if (6 !== \strlen($color)) { - throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color)); - } - - return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color)); + return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color); } if (isset(self::COLORS[$color])) { @@ -140,41 +130,4 @@ final class Color throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS))))); } - - private function convertHexColorToAnsi(int $color): string - { - $r = ($color >> 16) & 255; - $g = ($color >> 8) & 255; - $b = $color & 255; - - // see https://github.com/termstandard/colors/ for more information about true color support - if ('truecolor' !== getenv('COLORTERM')) { - return (string) $this->degradeHexColorToAnsi($r, $g, $b); - } - - return sprintf('8;2;%d;%d;%d', $r, $g, $b); - } - - private function degradeHexColorToAnsi(int $r, int $g, int $b): int - { - if (0 === round($this->getSaturation($r, $g, $b) / 50)) { - return 0; - } - - return (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) $diff * 100 / $v; - } } diff --git a/lib/symfony/console/Command/Command.php b/lib/symfony/console/Command/Command.php index e0593e17a..704b112d1 100644 --- a/lib/symfony/console/Command/Command.php +++ b/lib/symfony/console/Command/Command.php @@ -15,9 +15,11 @@ use Symfony\Component\Console\Application; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\HelperInterface; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; @@ -39,56 +41,69 @@ class Command /** * @var string|null The default command name + * + * @deprecated since Symfony 6.1, use the AsCommand attribute instead */ protected static $defaultName; /** * @var string|null The default command description + * + * @deprecated since Symfony 6.1, use the AsCommand attribute instead */ protected static $defaultDescription; - private $application; - private $name; - private $processTitle; - private $aliases = []; - private $definition; - private $hidden = false; - private $help = ''; - private $description = ''; - private $fullDefinition; - private $ignoreValidationErrors = false; - private $code; - private $synopsis = []; - private $usages = []; - private $helperSet; + private ?Application $application = null; + private ?string $name = null; + private ?string $processTitle = null; + private array $aliases = []; + private InputDefinition $definition; + private bool $hidden = false; + private string $help = ''; + private string $description = ''; + private ?InputDefinition $fullDefinition = null; + private bool $ignoreValidationErrors = false; + private ?\Closure $code = null; + private array $synopsis = []; + private array $usages = []; + private ?HelperSet $helperSet = null; - /** - * @return string|null - */ - public static function getDefaultName() + public static function getDefaultName(): ?string { $class = static::class; - if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { + if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->name; } $r = new \ReflectionProperty($class, 'defaultName'); - return $class === $r->class ? static::$defaultName : null; + if ($class !== $r->class || null === static::$defaultName) { + return null; + } + + trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultName" for setting a command name is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class); + + return static::$defaultName; } public static function getDefaultDescription(): ?string { $class = static::class; - if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { + if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->description; } $r = new \ReflectionProperty($class, 'defaultDescription'); - return $class === $r->class ? static::$defaultDescription : null; + if ($class !== $r->class || null === static::$defaultDescription) { + return null; + } + + trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultDescription" for setting a command description is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class); + + return static::$defaultDescription; } /** @@ -126,14 +141,22 @@ class Command * Ignores validation errors. * * This is mainly useful for the help command. + * + * @return void */ public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } + /** + * @return void + */ public function setApplication(Application $application = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); @@ -144,6 +167,9 @@ class Command $this->fullDefinition = null; } + /** + * @return void + */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; @@ -151,20 +177,16 @@ class Command /** * Gets the helper set. - * - * @return HelperSet|null */ - public function getHelperSet() + public function getHelperSet(): ?HelperSet { return $this->helperSet; } /** * Gets the application instance for this command. - * - * @return Application|null */ - public function getApplication() + public function getApplication(): ?Application { return $this->application; } @@ -184,6 +206,8 @@ class Command /** * Configures the current command. + * + * @return void */ protected function configure() { @@ -214,6 +238,8 @@ class Command * This method is executed before the InputDefinition is validated. * This means that this is the only place where the command can * interactively ask for values of missing required arguments. + * + * @return void */ protected function interact(InputInterface $input, OutputInterface $output) { @@ -228,6 +254,8 @@ class Command * * @see InputInterface::bind() * @see InputInterface::validate() + * + * @return void */ protected function initialize(InputInterface $input, OutputInterface $output) { @@ -247,7 +275,7 @@ class Command * @see setCode() * @see execute() */ - public function run(InputInterface $input, OutputInterface $output) + public function run(InputInterface $input, OutputInterface $output): int { // add the application arguments and options $this->mergeApplicationDefinition(); @@ -310,6 +338,12 @@ class Command */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { + $definition = $this->getDefinition(); + if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) { + $definition->getOption($input->getCompletionName())->complete($input, $suggestions); + } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) { + $definition->getArgument($input->getCompletionName())->complete($input, $suggestions); + } } /** @@ -326,7 +360,7 @@ class Command * * @see execute() */ - public function setCode(callable $code) + public function setCode(callable $code): static { if ($code instanceof \Closure) { $r = new \ReflectionFunction($code); @@ -340,6 +374,8 @@ class Command restore_error_handler(); } } + } else { + $code = $code(...); } $this->code = $code; @@ -356,7 +392,7 @@ class Command * * @internal */ - public function mergeApplicationDefinition(bool $mergeArgs = true) + public function mergeApplicationDefinition(bool $mergeArgs = true): void { if (null === $this->application) { return; @@ -377,11 +413,9 @@ class Command /** * Sets an array of argument and option instances. * - * @param array|InputDefinition $definition An array of argument and option instances or a definition instance - * * @return $this */ - public function setDefinition($definition) + public function setDefinition(array|InputDefinition $definition): static { if ($definition instanceof InputDefinition) { $this->definition = $definition; @@ -396,10 +430,8 @@ class Command /** * Gets the InputDefinition attached to this Command. - * - * @return InputDefinition */ - public function getDefinition() + public function getDefinition(): InputDefinition { return $this->fullDefinition ?? $this->getNativeDefinition(); } @@ -411,34 +443,31 @@ class Command * be changed by merging with the application InputDefinition. * * This method is not part of public API and should not be used directly. - * - * @return InputDefinition */ - public function getNativeDefinition() + public function getNativeDefinition(): InputDefinition { - if (null === $this->definition) { - throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); - } - - return $this->definition; + return $this->definition ?? throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); } /** * Adds an argument. * - * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL - * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) - * - * @throws InvalidArgumentException When argument mode is not valid + * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param $default The default value (for InputArgument::OPTIONAL mode only) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this + * + * @throws InvalidArgumentException When argument mode is not valid */ - public function addArgument(string $name, int $mode = null, string $description = '', $default = null) + public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static { - $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); - if (null !== $this->fullDefinition) { - $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default)); + $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : []; + if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) { + throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues))); } + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); + $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); return $this; } @@ -446,20 +475,23 @@ class Command /** * Adds an option. * - * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants - * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) - * - * @throws InvalidArgumentException If option mode is invalid or incompatible + * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param $mode The option mode: One of the InputOption::VALUE_* constants + * @param $default The default value (must be null for InputOption::VALUE_NONE) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this + * + * @throws InvalidArgumentException If option mode is invalid or incompatible */ - public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) + public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static { - $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); - if (null !== $this->fullDefinition) { - $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : []; + if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) { + throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues))); } + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); + $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); return $this; } @@ -476,7 +508,7 @@ class Command * * @throws InvalidArgumentException When the name is invalid */ - public function setName(string $name) + public function setName(string $name): static { $this->validateName($name); @@ -493,7 +525,7 @@ class Command * * @return $this */ - public function setProcessTitle(string $title) + public function setProcessTitle(string $title): static { $this->processTitle = $title; @@ -502,23 +534,18 @@ class Command /** * Returns the command name. - * - * @return string|null */ - public function getName() + public function getName(): ?string { return $this->name; } /** * @param bool $hidden Whether or not the command should be hidden from the list of commands - * The default value will be true in Symfony 6.0 * * @return $this - * - * @final since Symfony 5.1 */ - public function setHidden(bool $hidden /* = true */) + public function setHidden(bool $hidden = true): static { $this->hidden = $hidden; @@ -528,7 +555,7 @@ class Command /** * @return bool whether the command should be publicly shown or not */ - public function isHidden() + public function isHidden(): bool { return $this->hidden; } @@ -538,7 +565,7 @@ class Command * * @return $this */ - public function setDescription(string $description) + public function setDescription(string $description): static { $this->description = $description; @@ -547,10 +574,8 @@ class Command /** * Returns the description for the command. - * - * @return string */ - public function getDescription() + public function getDescription(): string { return $this->description; } @@ -560,7 +585,7 @@ class Command * * @return $this */ - public function setHelp(string $help) + public function setHelp(string $help): static { $this->help = $help; @@ -569,10 +594,8 @@ class Command /** * Returns the help for the command. - * - * @return string */ - public function getHelp() + public function getHelp(): string { return $this->help; } @@ -580,13 +603,11 @@ class Command /** * Returns the processed help for the command replacing the %command.name% and * %command.full_name% patterns with the real values dynamically. - * - * @return string */ - public function getProcessedHelp() + public function getProcessedHelp(): string { $name = $this->name; - $isSingleCommand = $this->application && $this->application->isSingleCommand(); + $isSingleCommand = $this->application?->isSingleCommand(); $placeholders = [ '%command.name%', @@ -609,7 +630,7 @@ class Command * * @throws InvalidArgumentException When an alias is invalid */ - public function setAliases(iterable $aliases) + public function setAliases(iterable $aliases): static { $list = []; @@ -625,10 +646,8 @@ class Command /** * Returns the aliases for the command. - * - * @return array */ - public function getAliases() + public function getAliases(): array { return $this->aliases; } @@ -637,10 +656,8 @@ class Command * Returns the synopsis for the command. * * @param bool $short Whether to show the short version of the synopsis (with options folded) or not - * - * @return string */ - public function getSynopsis(bool $short = false) + public function getSynopsis(bool $short = false): string { $key = $short ? 'short' : 'long'; @@ -656,7 +673,7 @@ class Command * * @return $this */ - public function addUsage(string $usage) + public function addUsage(string $usage): static { if (!str_starts_with($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); @@ -669,10 +686,8 @@ class Command /** * Returns alternative usages of the command. - * - * @return array */ - public function getUsages() + public function getUsages(): array { return $this->usages; } @@ -680,12 +695,12 @@ class Command /** * Gets a helper instance by name. * - * @return mixed + * @return HelperInterface * * @throws LogicException if no HelperSet is defined * @throws InvalidArgumentException if the helper is not defined */ - public function getHelper(string $name) + public function getHelper(string $name): mixed { if (null === $this->helperSet) { throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); @@ -701,7 +716,7 @@ class Command * * @throws InvalidArgumentException When the name is invalid */ - private function validateName(string $name) + private function validateName(string $name): void { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); diff --git a/lib/symfony/console/Command/CompleteCommand.php b/lib/symfony/console/Command/CompleteCommand.php index 11ada4e44..23be5577b 100644 --- a/lib/symfony/console/Command/CompleteCommand.php +++ b/lib/symfony/console/Command/CompleteCommand.php @@ -11,10 +11,13 @@ namespace Symfony\Component\Console\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Completion\Output\BashCompletionOutput; use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; +use Symfony\Component\Console\Completion\Output\FishCompletionOutput; +use Symfony\Component\Console\Completion\Output\ZshCompletionOutput; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Input\InputInterface; @@ -26,14 +29,24 @@ use Symfony\Component\Console\Output\OutputInterface; * * @author Wouter de Jong */ +#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')] final class CompleteCommand extends Command { + public const COMPLETION_API_VERSION = '1'; + + /** + * @deprecated since Symfony 6.1 + */ protected static $defaultName = '|_complete'; + + /** + * @deprecated since Symfony 6.1 + */ protected static $defaultDescription = 'Internal command to provide shell completion suggestions'; - private $completionOutputs; + private array $completionOutputs; - private $isDebug = false; + private bool $isDebug = false; /** * @param array> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value @@ -41,7 +54,11 @@ final class CompleteCommand extends Command public function __construct(array $completionOutputs = []) { // must be set before the parent constructor, as the property value is used in configure() - $this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class]; + $this->completionOutputs = $completionOutputs + [ + 'bash' => BashCompletionOutput::class, + 'fish' => FishCompletionOutput::class, + 'zsh' => ZshCompletionOutput::class, + ]; parent::__construct(); } @@ -52,28 +69,29 @@ final class CompleteCommand extends Command ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")') ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)') ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)') - ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script') + ->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script') + ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated') ; } - 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_BOOLEAN); + $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL); } protected function execute(InputInterface $input, OutputInterface $output): int { try { - // uncomment when a bugfix or BC break has been introduced in the shell completion scripts - // $version = $input->getOption('symfony'); - // if ($version && version_compare($version, 'x.y', '>=')) { - // $message = sprintf('Completion script version is not supported ("%s" given, ">=x.y" required).', $version); - // $this->log($message); + // "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1 + $version = $input->getOption('symfony') ? '1' : $input->getOption('api-version'); + if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) { + $message = sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION); + $this->log($message); - // $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.'); + $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.'); - // return 126; - // } + return 126; + } $shell = $input->getOption('shell'); if (!$shell) { @@ -116,12 +134,12 @@ final class CompleteCommand extends Command $completionInput->bind($command->getDefinition()); if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { - $this->log(' Completing option names for the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' command.'); + $this->log(' Completing option names for the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' command.'); $suggestions->suggestOptions($command->getDefinition()->getOptions()); } else { $this->log([ - ' Completing using the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' class.', + ' Completing using the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' class.', ' Completing '.$completionInput->getCompletionType().' for '.$completionInput->getCompletionName().'', ]); if (null !== $compval = $completionInput->getCompletionValue()) { @@ -137,7 +155,7 @@ final class CompleteCommand extends Command $this->log('Suggestions:'); 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()) { $this->log(' '.implode(' ', $values)); } else { @@ -155,10 +173,10 @@ final class CompleteCommand extends Command throw $e; } - return self::FAILURE; + return 2; } - return self::SUCCESS; + return 0; } private function createCompletionInput(InputInterface $input): CompletionInput @@ -172,7 +190,7 @@ final class CompleteCommand extends Command try { $completionInput->bind($this->getApplication()->getDefinition()); - } catch (ExceptionInterface $e) { + } catch (ExceptionInterface) { } return $completionInput; @@ -187,7 +205,7 @@ final class CompleteCommand extends Command } return $this->getApplication()->find($inputName); - } catch (CommandNotFoundException $e) { + } catch (CommandNotFoundException) { } return null; diff --git a/lib/symfony/console/Command/DumpCompletionCommand.php b/lib/symfony/console/Command/DumpCompletionCommand.php index 518d606a0..51b613a14 100644 --- a/lib/symfony/console/Command/DumpCompletionCommand.php +++ b/lib/symfony/console/Command/DumpCompletionCommand.php @@ -11,8 +11,7 @@ namespace Symfony\Component\Console\Command; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -25,55 +24,67 @@ use Symfony\Component\Process\Process; * * @author Wouter de Jong */ +#[AsCommand(name: 'completion', description: 'Dump the shell completion script')] final class DumpCompletionCommand extends Command { + /** + * @deprecated since Symfony 6.1 + */ protected static $defaultName = 'completion'; + + /** + * @deprecated since Symfony 6.1 + */ protected static $defaultDescription = 'Dump the shell completion script'; - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($input->mustSuggestArgumentValuesFor('shell')) { - $suggestions->suggestValues($this->getSupportedShells()); - } - } + private array $supportedShells; - protected function configure() + protected function configure(): void { $fullCommand = $_SERVER['PHP_SELF']; $commandName = basename($fullCommand); $fullCommand = @realpath($fullCommand) ?: $fullCommand; + $shell = $this->guessShell(); + [$rcFile, $completionFile] = match ($shell) { + 'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"], + 'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName], + default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"], + }; + + $supportedShells = implode(', ', $this->getSupportedShells()); + $this ->setHelp(<<%command.name% command dumps the shell completion script required -to use shell autocompletion (currently only bash completion is supported). +to use shell autocompletion (currently, {$supportedShells} completion are supported). Static installation ------------------- Dump the script to a global completion file and restart your shell: - %command.full_name% bash | sudo tee /etc/bash_completion.d/{$commandName} + %command.full_name% {$shell} | sudo tee {$completionFile} Or dump the script to a local file and source it: - %command.full_name% bash > completion.sh + %command.full_name% {$shell} > completion.sh # source the file whenever you use the project source completion.sh - # or add this line at the end of your "~/.bashrc" file: + # or add this line at the end of your "{$rcFile}" file: source /path/to/completion.sh Dynamic installation -------------------- -Add this to the end of your shell configuration file (e.g. "~/.bashrc"): +Add this to the end of your shell configuration file (e.g. "{$rcFile}"): - eval "$({$fullCommand} completion bash)" + eval "$({$fullCommand} completion {$shell})" EOH ) - ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given') + ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...)) ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log') ; } @@ -85,7 +96,7 @@ EOH if ($input->getOption('debug')) { $this->tailDebugLog($commandName, $output); - return self::SUCCESS; + return 0; } $shell = $input->getArgument('shell') ?? self::guessShell(); @@ -102,12 +113,12 @@ EOH $output->writeln(sprintf('Shell not detected, Symfony shell completion only supports "%s").', implode('", "', $supportedShells))); } - return self::INVALID; + return 2; } - $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile))); + $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile))); - return self::SUCCESS; + return 0; } private static function guessShell(): string @@ -132,8 +143,19 @@ EOH */ private function getSupportedShells(): array { - return array_map(function ($f) { - return pathinfo($f, \PATHINFO_EXTENSION); - }, glob(__DIR__.'/../Resources/completion.*')); + if (isset($this->supportedShells)) { + return $this->supportedShells; + } + + $shells = []; + + foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) { + if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) { + $shells[] = $file->getExtension(); + } + } + sort($shells); + + return $this->supportedShells = $shells; } } diff --git a/lib/symfony/console/Command/HelpCommand.php b/lib/symfony/console/Command/HelpCommand.php index c66ef463e..e6447b050 100644 --- a/lib/symfony/console/Command/HelpCommand.php +++ b/lib/symfony/console/Command/HelpCommand.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Console\Command; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Descriptor\ApplicationDescription; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; @@ -27,10 +25,10 @@ use Symfony\Component\Console\Output\OutputInterface; */ class HelpCommand extends Command { - private $command; + private Command $command; /** - * {@inheritdoc} + * @return void */ protected function configure() { @@ -39,8 +37,8 @@ class HelpCommand extends Command $this ->setName('help') ->setDefinition([ - new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => 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('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), ]) ->setDescription('Display help for a command') @@ -59,19 +57,17 @@ EOF ; } + /** + * @return void + */ public function setCommand(Command $command) { $this->command = $command; } - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - if (null === $this->command) { - $this->command = $this->getApplication()->find($input->getArgument('command_name')); - } + $this->command ??= $this->getApplication()->find($input->getArgument('command_name')); $helper = new DescriptorHelper(); $helper->describe($output, $this->command, [ @@ -79,23 +75,8 @@ EOF 'raw_text' => $input->getOption('raw'), ]); - $this->command = null; + unset($this->command); return 0; } - - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($input->mustSuggestArgumentValuesFor('command_name')) { - $descriptor = new ApplicationDescription($this->getApplication()); - $suggestions->suggestValues(array_keys($descriptor->getCommands())); - - return; - } - - if ($input->mustSuggestOptionValuesFor('format')) { - $helper = new DescriptorHelper(); - $suggestions->suggestValues($helper->getFormats()); - } - } } diff --git a/lib/symfony/console/Command/LazyCommand.php b/lib/symfony/console/Command/LazyCommand.php index e576ad03f..d56058221 100644 --- a/lib/symfony/console/Command/LazyCommand.php +++ b/lib/symfony/console/Command/LazyCommand.php @@ -14,6 +14,8 @@ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Application; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Helper\HelperInterface; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; @@ -24,8 +26,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ final class LazyCommand extends Command { - private $command; - private $isEnabled; + private \Closure|Command $command; + private ?bool $isEnabled; public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true) { @@ -45,6 +47,9 @@ final class LazyCommand extends Command public function setApplication(Application $application = null): void { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if ($this->command instanceof parent) { $this->command->setApplication($application); } @@ -76,10 +81,7 @@ final class LazyCommand extends Command $this->getCommand()->complete($input, $suggestions); } - /** - * @return $this - */ - public function setCode(callable $code): self + public function setCode(callable $code): static { $this->getCommand()->setCode($code); @@ -94,10 +96,7 @@ final class LazyCommand extends Command $this->getCommand()->mergeApplicationDefinition($mergeArgs); } - /** - * @return $this - */ - public function setDefinition($definition): self + public function setDefinition(array|InputDefinition $definition): static { $this->getCommand()->setDefinition($definition); @@ -115,39 +114,35 @@ final class LazyCommand extends Command } /** - * @return $this + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion */ - public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self + public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static { - $this->getCommand()->addArgument($name, $mode, $description, $default); + $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : []; + $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues); return $this; } /** - * @return $this + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion */ - public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null): self + public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static { - $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default); + $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : []; + $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); return $this; } - /** - * @return $this - */ - public function setProcessTitle(string $title): self + public function setProcessTitle(string $title): static { $this->getCommand()->setProcessTitle($title); return $this; } - /** - * @return $this - */ - public function setHelp(string $help): self + public function setHelp(string $help): static { $this->getCommand()->setHelp($help); @@ -169,10 +164,7 @@ final class LazyCommand extends Command return $this->getCommand()->getSynopsis($short); } - /** - * @return $this - */ - public function addUsage(string $usage): self + public function addUsage(string $usage): static { $this->getCommand()->addUsage($usage); @@ -184,10 +176,7 @@ final class LazyCommand extends Command return $this->getCommand()->getUsages(); } - /** - * @return mixed - */ - public function getHelper(string $name) + public function getHelper(string $name): HelperInterface { return $this->getCommand()->getHelper($name); } diff --git a/lib/symfony/console/Command/ListCommand.php b/lib/symfony/console/Command/ListCommand.php index f04a4ef67..5850c3d7b 100644 --- a/lib/symfony/console/Command/ListCommand.php +++ b/lib/symfony/console/Command/ListCommand.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Console\Command; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Descriptor\ApplicationDescription; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; @@ -28,16 +26,16 @@ use Symfony\Component\Console\Output\OutputInterface; class ListCommand extends Command { /** - * {@inheritdoc} + * @return void */ protected function configure() { $this ->setName('list') ->setDefinition([ - new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())), 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'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()), new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), ]) ->setDescription('List commands') @@ -62,10 +60,7 @@ EOF ; } - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), [ @@ -77,19 +72,4 @@ EOF return 0; } - - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($input->mustSuggestArgumentValuesFor('namespace')) { - $descriptor = new ApplicationDescription($this->getApplication()); - $suggestions->suggestValues(array_keys($descriptor->getNamespaces())); - - return; - } - - if ($input->mustSuggestOptionValuesFor('format')) { - $helper = new DescriptorHelper(); - $suggestions->suggestValues($helper->getFormats()); - } - } } diff --git a/lib/symfony/console/Command/LockableTrait.php b/lib/symfony/console/Command/LockableTrait.php index b1856dca7..c1006a65c 100644 --- a/lib/symfony/console/Command/LockableTrait.php +++ b/lib/symfony/console/Command/LockableTrait.php @@ -24,8 +24,7 @@ use Symfony\Component\Lock\Store\SemaphoreStore; */ trait LockableTrait { - /** @var LockInterface|null */ - private $lock; + private ?LockInterface $lock = null; /** * Locks a command. @@ -33,7 +32,7 @@ trait LockableTrait private function lock(string $name = null, bool $blocking = false): bool { 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) { @@ -59,7 +58,7 @@ trait LockableTrait /** * Releases the command lock if there is one. */ - private function release() + private function release(): void { if ($this->lock) { $this->lock->release(); diff --git a/lib/symfony/console/Command/SignalableCommandInterface.php b/lib/symfony/console/Command/SignalableCommandInterface.php index d439728b6..f8eb8e522 100644 --- a/lib/symfony/console/Command/SignalableCommandInterface.php +++ b/lib/symfony/console/Command/SignalableCommandInterface.php @@ -25,6 +25,10 @@ interface SignalableCommandInterface /** * 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 */); } diff --git a/lib/symfony/console/Command/TraceableCommand.php b/lib/symfony/console/Command/TraceableCommand.php new file mode 100644 index 000000000..d8c46b7fa --- /dev/null +++ b/lib/symfony/console/Command/TraceableCommand.php @@ -0,0 +1,356 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @internal + * + * @author Jules Pietri + */ +final class TraceableCommand extends Command implements SignalableCommandInterface +{ + public readonly Command $command; + public int $exitCode; + public ?int $interruptedBySignal = null; + public bool $ignoreValidation; + public bool $isInteractive = false; + public string $duration = 'n/a'; + public string $maxMemoryUsage = 'n/a'; + public InputInterface $input; + public OutputInterface $output; + /** @var array */ + public array $arguments; + /** @var array */ + public array $options; + /** @var array */ + public array $interactiveInputs = []; + public array $handledSignals = []; + + public function __construct( + Command $command, + private readonly Stopwatch $stopwatch, + ) { + if ($command instanceof LazyCommand) { + $command = $command->getCommand(); + } + + $this->command = $command; + + // prevent call to self::getDefaultDescription() + $this->setDescription($command->getDescription()); + + parent::__construct($command->getName()); + + // init below enables calling {@see parent::run()} + [$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(function () { + return [$this->code, $this->processTitle, $this->ignoreValidationErrors]; + }, $command, Command::class)(); + + if (\is_callable($code)) { + $this->setCode($code); + } + + if ($processTitle) { + parent::setProcessTitle($processTitle); + } + + if ($ignoreValidationErrors) { + parent::ignoreValidationErrors(); + } + + $this->ignoreValidation = $ignoreValidationErrors; + } + + public function __call(string $name, array $arguments): mixed + { + return $this->command->{$name}(...$arguments); + } + + public function getSubscribedSignals(): array + { + return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : []; + } + + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false + { + if (!$this->command instanceof SignalableCommandInterface) { + return false; + } + + $event = $this->stopwatch->start($this->getName().'.handle_signal'); + + $exit = $this->command->handleSignal($signal, $previousExitCode); + + $event->stop(); + + if (!isset($this->handledSignals[$signal])) { + $this->handledSignals[$signal] = [ + 'handled' => 0, + 'duration' => 0, + 'memory' => 0, + ]; + } + + ++$this->handledSignals[$signal]['handled']; + $this->handledSignals[$signal]['duration'] += $event->getDuration(); + $this->handledSignals[$signal]['memory'] = max( + $this->handledSignals[$signal]['memory'], + $event->getMemory() >> 20 + ); + + return $exit; + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function ignoreValidationErrors(): void + { + $this->ignoreValidation = true; + $this->command->ignoreValidationErrors(); + + parent::ignoreValidationErrors(); + } + + public function setApplication(Application $application = null): void + { + $this->command->setApplication($application); + } + + public function getApplication(): ?Application + { + return $this->command->getApplication(); + } + + public function setHelperSet(HelperSet $helperSet): void + { + $this->command->setHelperSet($helperSet); + } + + public function getHelperSet(): ?HelperSet + { + return $this->command->getHelperSet(); + } + + public function isEnabled(): bool + { + return $this->command->isEnabled(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $this->command->complete($input, $suggestions); + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function setCode(callable $code): static + { + $this->command->setCode($code); + + return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int { + $event = $this->stopwatch->start($this->getName().'.code'); + + $this->exitCode = $code($input, $output); + + $event->stop(); + + return $this->exitCode; + }); + } + + /** + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + $this->command->mergeApplicationDefinition($mergeArgs); + } + + public function setDefinition(array|InputDefinition $definition): static + { + $this->command->setDefinition($definition); + + return $this; + } + + public function getDefinition(): InputDefinition + { + return $this->command->getDefinition(); + } + + public function getNativeDefinition(): InputDefinition + { + return $this->command->getNativeDefinition(); + } + + public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->command->addArgument($name, $mode, $description, $default, $suggestedValues); + + return $this; + } + + public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); + + return $this; + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function setProcessTitle(string $title): static + { + $this->command->setProcessTitle($title); + + return parent::setProcessTitle($title); + } + + public function setHelp(string $help): static + { + $this->command->setHelp($help); + + return $this; + } + + public function getHelp(): string + { + return $this->command->getHelp(); + } + + public function getProcessedHelp(): string + { + return $this->command->getProcessedHelp(); + } + + public function getSynopsis(bool $short = false): string + { + return $this->command->getSynopsis($short); + } + + public function addUsage(string $usage): static + { + $this->command->addUsage($usage); + + return $this; + } + + public function getUsages(): array + { + return $this->command->getUsages(); + } + + public function getHelper(string $name): HelperInterface + { + return $this->command->getHelper($name); + } + + public function run(InputInterface $input, OutputInterface $output): int + { + $this->input = $input; + $this->output = $output; + $this->arguments = $input->getArguments(); + $this->options = $input->getOptions(); + $event = $this->stopwatch->start($this->getName(), 'command'); + + try { + $this->exitCode = parent::run($input, $output); + } finally { + $event->stop(); + + if ($output instanceof ConsoleOutputInterface && $output->isDebug()) { + $output->getErrorOutput()->writeln((string) $event); + } + + $this->duration = $event->getDuration().' ms'; + $this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB'; + + if ($this->isInteractive) { + $this->extractInteractiveInputs($input->getArguments(), $input->getOptions()); + } + } + + return $this->exitCode; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $event = $this->stopwatch->start($this->getName().'.init', 'command'); + + $this->command->initialize($input, $output); + + $event->stop(); + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) { + return; + } + + $event = $this->stopwatch->start($this->getName().'.interact', 'command'); + + $this->command->interact($input, $output); + + $event->stop(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $event = $this->stopwatch->start($this->getName().'.execute', 'command'); + + $exitCode = $this->command->execute($input, $output); + + $event->stop(); + + return $exitCode; + } + + private function extractInteractiveInputs(array $arguments, array $options): void + { + foreach ($arguments as $argName => $argValue) { + if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) { + continue; + } + + $this->interactiveInputs[$argName] = $argValue; + } + + foreach ($options as $optName => $optValue) { + if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) { + continue; + } + + $this->interactiveInputs['--'.$optName] = $optValue; + } + } +} diff --git a/lib/symfony/console/CommandLoader/CommandLoaderInterface.php b/lib/symfony/console/CommandLoader/CommandLoaderInterface.php index 0adaf886f..b6b637ce6 100644 --- a/lib/symfony/console/CommandLoader/CommandLoaderInterface.php +++ b/lib/symfony/console/CommandLoader/CommandLoaderInterface.php @@ -22,21 +22,17 @@ interface CommandLoaderInterface /** * Loads a command. * - * @return Command - * * @throws CommandNotFoundException */ - public function get(string $name); + public function get(string $name): Command; /** * Checks if a command exists. - * - * @return bool */ - public function has(string $name); + public function has(string $name): bool; /** * @return string[] */ - public function getNames(); + public function getNames(): array; } diff --git a/lib/symfony/console/CommandLoader/ContainerCommandLoader.php b/lib/symfony/console/CommandLoader/ContainerCommandLoader.php index ddccb3d45..bfa0ac467 100644 --- a/lib/symfony/console/CommandLoader/ContainerCommandLoader.php +++ b/lib/symfony/console/CommandLoader/ContainerCommandLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\CommandLoader; use Psr\Container\ContainerInterface; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\CommandNotFoundException; /** @@ -21,8 +22,8 @@ use Symfony\Component\Console\Exception\CommandNotFoundException; */ class ContainerCommandLoader implements CommandLoaderInterface { - private $container; - private $commandMap; + private ContainerInterface $container; + private array $commandMap; /** * @param array $commandMap An array with command names as keys and service ids as values @@ -33,10 +34,7 @@ class ContainerCommandLoader implements CommandLoaderInterface $this->commandMap = $commandMap; } - /** - * {@inheritdoc} - */ - public function get(string $name) + public function get(string $name): Command { if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); @@ -45,18 +43,12 @@ class ContainerCommandLoader implements CommandLoaderInterface return $this->container->get($this->commandMap[$name]); } - /** - * {@inheritdoc} - */ - public function has(string $name) + public function has(string $name): bool { return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); } - /** - * {@inheritdoc} - */ - public function getNames() + public function getNames(): array { return array_keys($this->commandMap); } diff --git a/lib/symfony/console/CommandLoader/FactoryCommandLoader.php b/lib/symfony/console/CommandLoader/FactoryCommandLoader.php index 7e2db3464..9ced75aeb 100644 --- a/lib/symfony/console/CommandLoader/FactoryCommandLoader.php +++ b/lib/symfony/console/CommandLoader/FactoryCommandLoader.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\CommandLoader; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\CommandNotFoundException; /** @@ -20,7 +21,7 @@ use Symfony\Component\Console\Exception\CommandNotFoundException; */ class FactoryCommandLoader implements CommandLoaderInterface { - private $factories; + private array $factories; /** * @param callable[] $factories Indexed by command names @@ -30,18 +31,12 @@ class FactoryCommandLoader implements CommandLoaderInterface $this->factories = $factories; } - /** - * {@inheritdoc} - */ - public function has(string $name) + public function has(string $name): bool { return isset($this->factories[$name]); } - /** - * {@inheritdoc} - */ - public function get(string $name) + public function get(string $name): Command { if (!isset($this->factories[$name])) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); @@ -52,10 +47,7 @@ class FactoryCommandLoader implements CommandLoaderInterface return $factory(); } - /** - * {@inheritdoc} - */ - public function getNames() + public function getNames(): array { return array_keys($this->factories); } diff --git a/lib/symfony/console/Completion/CompletionInput.php b/lib/symfony/console/Completion/CompletionInput.php index 368b94507..7ba41c083 100644 --- a/lib/symfony/console/Completion/CompletionInput.php +++ b/lib/symfony/console/Completion/CompletionInput.php @@ -31,11 +31,11 @@ final class CompletionInput extends ArgvInput public const TYPE_OPTION_NAME = 'option_name'; public const TYPE_NONE = 'none'; - private $tokens; - private $currentIndex; - private $completionType; - private $completionName = null; - private $completionValue = ''; + private array $tokens; + private int $currentIndex; + private string $completionType; + private ?string $completionName = null; + private string $completionValue = ''; /** * Converts a terminal string into tokens. @@ -64,9 +64,6 @@ final class CompletionInput extends ArgvInput return $input; } - /** - * {@inheritdoc} - */ public function bind(InputDefinition $definition): void { parent::bind($definition); @@ -84,7 +81,7 @@ final class CompletionInput extends ArgvInput return; } - if (null !== $option && $option->acceptValue()) { + if ($option?->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $option->getName(); $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); @@ -97,7 +94,7 @@ final class CompletionInput extends ArgvInput if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { // check if previous option accepted a value $previousOption = $this->getOptionFromToken($previousToken); - if (null !== $previousOption && $previousOption->acceptValue()) { + if ($previousOption?->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $previousOption->getName(); $this->completionValue = $relevantToken; @@ -144,7 +141,9 @@ final class CompletionInput extends ArgvInput * TYPE_OPTION_NAME when completing the name of an input option * TYPE_NONE when nothing should be completed * - * @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component + * TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component. + * + * @return self::TYPE_* */ public function getCompletionType(): string { @@ -183,7 +182,7 @@ final class CompletionInput extends ArgvInput { try { return parent::parseToken($token, $parseOptions); - } catch (RuntimeException $e) { + } catch (RuntimeException) { // suppress errors, completed input is almost never valid } diff --git a/lib/symfony/console/Completion/CompletionSuggestions.php b/lib/symfony/console/Completion/CompletionSuggestions.php index d8905e5ee..549bbafbd 100644 --- a/lib/symfony/console/Completion/CompletionSuggestions.php +++ b/lib/symfony/console/Completion/CompletionSuggestions.php @@ -20,17 +20,15 @@ use Symfony\Component\Console\Input\InputOption; */ final class CompletionSuggestions { - private $valueSuggestions = []; - private $optionSuggestions = []; + private array $valueSuggestions = []; + private array $optionSuggestions = []; /** * Add a suggested value for an input option or argument. * - * @param string|Suggestion $value - * * @return $this */ - public function suggestValue($value): self + public function suggestValue(string|Suggestion $value): static { $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value; @@ -44,7 +42,7 @@ final class CompletionSuggestions * * @return $this */ - public function suggestValues(array $values): self + public function suggestValues(array $values): static { foreach ($values as $value) { $this->suggestValue($value); @@ -58,7 +56,7 @@ final class CompletionSuggestions * * @return $this */ - public function suggestOption(InputOption $option): self + public function suggestOption(InputOption $option): static { $this->optionSuggestions[] = $option; @@ -72,7 +70,7 @@ final class CompletionSuggestions * * @return $this */ - public function suggestOptions(array $options): self + public function suggestOptions(array $options): static { foreach ($options as $option) { $this->suggestOption($option); diff --git a/lib/symfony/console/Completion/Output/FishCompletionOutput.php b/lib/symfony/console/Completion/Output/FishCompletionOutput.php new file mode 100644 index 000000000..d2c414e48 --- /dev/null +++ b/lib/symfony/console/Completion/Output/FishCompletionOutput.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Guillaume Aveline + */ +class FishCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = $suggestions->getValueSuggestions(); + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName(); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName(); + } + } + $output->write(implode("\n", $values)); + } +} diff --git a/lib/symfony/console/Completion/Output/ZshCompletionOutput.php b/lib/symfony/console/Completion/Output/ZshCompletionOutput.php new file mode 100644 index 000000000..bb4ce70b5 --- /dev/null +++ b/lib/symfony/console/Completion/Output/ZshCompletionOutput.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jitendra A + */ +class ZshCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = []; + foreach ($suggestions->getValueSuggestions() as $value) { + $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : ''); + } + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + } + } + $output->write(implode("\n", $values)."\n"); + } +} diff --git a/lib/symfony/console/Completion/Suggestion.php b/lib/symfony/console/Completion/Suggestion.php index 6c7bc4dc4..7392965a2 100644 --- a/lib/symfony/console/Completion/Suggestion.php +++ b/lib/symfony/console/Completion/Suggestion.php @@ -16,13 +16,12 @@ namespace Symfony\Component\Console\Completion; * * @author Wouter de Jong */ -class Suggestion +class Suggestion implements \Stringable { - private $value; - - public function __construct(string $value) - { - $this->value = $value; + public function __construct( + private readonly string $value, + private readonly string $description = '' + ) { } public function getValue(): string @@ -30,6 +29,11 @@ class Suggestion return $this->value; } + public function getDescription(): string + { + return $this->description; + } + public function __toString(): string { return $this->getValue(); diff --git a/lib/symfony/console/Cursor.php b/lib/symfony/console/Cursor.php index 0c4dafb6c..69fd3821c 100644 --- a/lib/symfony/console/Cursor.php +++ b/lib/symfony/console/Cursor.php @@ -18,7 +18,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ final class Cursor { - private $output; + private OutputInterface $output; + /** @var resource */ private $input; /** @@ -33,7 +34,7 @@ final class Cursor /** * @return $this */ - public function moveUp(int $lines = 1): self + public function moveUp(int $lines = 1): static { $this->output->write(sprintf("\x1b[%dA", $lines)); @@ -43,7 +44,7 @@ final class Cursor /** * @return $this */ - public function moveDown(int $lines = 1): self + public function moveDown(int $lines = 1): static { $this->output->write(sprintf("\x1b[%dB", $lines)); @@ -53,7 +54,7 @@ final class Cursor /** * @return $this */ - public function moveRight(int $columns = 1): self + public function moveRight(int $columns = 1): static { $this->output->write(sprintf("\x1b[%dC", $columns)); @@ -63,7 +64,7 @@ final class Cursor /** * @return $this */ - public function moveLeft(int $columns = 1): self + public function moveLeft(int $columns = 1): static { $this->output->write(sprintf("\x1b[%dD", $columns)); @@ -73,7 +74,7 @@ final class Cursor /** * @return $this */ - public function moveToColumn(int $column): self + public function moveToColumn(int $column): static { $this->output->write(sprintf("\x1b[%dG", $column)); @@ -83,7 +84,7 @@ final class Cursor /** * @return $this */ - public function moveToPosition(int $column, int $row): self + public function moveToPosition(int $column, int $row): static { $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column)); @@ -93,7 +94,7 @@ final class Cursor /** * @return $this */ - public function savePosition(): self + public function savePosition(): static { $this->output->write("\x1b7"); @@ -103,7 +104,7 @@ final class Cursor /** * @return $this */ - public function restorePosition(): self + public function restorePosition(): static { $this->output->write("\x1b8"); @@ -113,7 +114,7 @@ final class Cursor /** * @return $this */ - public function hide(): self + public function hide(): static { $this->output->write("\x1b[?25l"); @@ -123,7 +124,7 @@ final class Cursor /** * @return $this */ - public function show(): self + public function show(): static { $this->output->write("\x1b[?25h\x1b[?0c"); @@ -135,7 +136,7 @@ final class Cursor * * @return $this */ - public function clearLine(): self + public function clearLine(): static { $this->output->write("\x1b[2K"); @@ -157,7 +158,7 @@ final class Cursor * * @return $this */ - public function clearOutput(): self + public function clearOutput(): static { $this->output->write("\x1b[0J"); @@ -169,7 +170,7 @@ final class Cursor * * @return $this */ - public function clearScreen(): self + public function clearScreen(): static { $this->output->write("\x1b[2J"); @@ -183,11 +184,7 @@ final class Cursor { static $isTtySupported; - if (null === $isTtySupported && \function_exists('proc_open')) { - $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); - } - - if (!$isTtySupported) { + if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) { return [1, 1]; } diff --git a/lib/symfony/console/DataCollector/CommandDataCollector.php b/lib/symfony/console/DataCollector/CommandDataCollector.php new file mode 100644 index 000000000..16a0eadf4 --- /dev/null +++ b/lib/symfony/console/DataCollector/CommandDataCollector.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\DataCollector; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\SignalRegistry\SignalMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @internal + * + * @author Jules Pietri + */ +final class CommandDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, \Throwable $exception = null): void + { + if (!$request instanceof CliRequest) { + return; + } + + $command = $request->command; + $application = $command->getApplication(); + + $this->data = [ + 'command' => $this->cloneVar($command->command), + 'exit_code' => $command->exitCode, + 'interrupted_by_signal' => $command->interruptedBySignal, + 'duration' => $command->duration, + 'max_memory_usage' => $command->maxMemoryUsage, + 'verbosity_level' => match ($command->output->getVerbosity()) { + OutputInterface::VERBOSITY_QUIET => 'quiet', + OutputInterface::VERBOSITY_NORMAL => 'normal', + OutputInterface::VERBOSITY_VERBOSE => 'verbose', + OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose', + OutputInterface::VERBOSITY_DEBUG => 'debug', + }, + 'interactive' => $command->isInteractive, + 'validate_input' => !$command->ignoreValidation, + 'enabled' => $command->isEnabled(), + 'visible' => !$command->isHidden(), + 'input' => $this->cloneVar($command->input), + 'output' => $this->cloneVar($command->output), + 'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs), + 'signalable' => $command->getSubscribedSignals(), + 'handled_signals' => $command->handledSignals, + 'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())), + ]; + + $baseDefinition = $application->getDefinition(); + + foreach ($command->arguments as $argName => $argValue) { + if ($baseDefinition->hasArgument($argName)) { + $this->data['application_inputs'][$argName] = $this->cloneVar($argValue); + } else { + $this->data['arguments'][$argName] = $this->cloneVar($argValue); + } + } + + foreach ($command->options as $optName => $optValue) { + if ($baseDefinition->hasOption($optName)) { + $this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue); + } else { + $this->data['options'][$optName] = $this->cloneVar($optValue); + } + } + } + + public function getName(): string + { + return 'command'; + } + + /** + * @return array{ + * class?: class-string, + * executor?: string, + * file: string, + * line: int, + * } + */ + public function getCommand(): array + { + $class = $this->data['command']->getType(); + $r = new \ReflectionMethod($class, 'execute'); + + if (Command::class !== $r->getDeclaringClass()) { + return [ + 'executor' => $class.'::'.$r->name, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + + $r = new \ReflectionClass($class); + + return [ + 'class' => $class, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + + public function getInterruptedBySignal(): ?string + { + if (isset($this->data['interrupted_by_signal'])) { + return sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']); + } + + return null; + } + + public function getDuration(): string + { + return $this->data['duration']; + } + + public function getMaxMemoryUsage(): string + { + return $this->data['max_memory_usage']; + } + + public function getVerbosityLevel(): string + { + return $this->data['verbosity_level']; + } + + public function getInteractive(): bool + { + return $this->data['interactive']; + } + + public function getValidateInput(): bool + { + return $this->data['validate_input']; + } + + public function getEnabled(): bool + { + return $this->data['enabled']; + } + + public function getVisible(): bool + { + return $this->data['visible']; + } + + public function getInput(): Data + { + return $this->data['input']; + } + + public function getOutput(): Data + { + return $this->data['output']; + } + + /** + * @return Data[] + */ + public function getArguments(): array + { + return $this->data['arguments'] ?? []; + } + + /** + * @return Data[] + */ + public function getOptions(): array + { + return $this->data['options'] ?? []; + } + + /** + * @return Data[] + */ + public function getApplicationInputs(): array + { + return $this->data['application_inputs'] ?? []; + } + + /** + * @return Data[] + */ + public function getInteractiveInputs(): array + { + return $this->data['interactive_inputs'] ?? []; + } + + public function getSignalable(): array + { + return array_map( + static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal), + $this->data['signalable'] + ); + } + + public function getHandledSignals(): array + { + $keys = array_map( + static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal), + array_keys($this->data['handled_signals']) + ); + + return array_combine($keys, array_values($this->data['handled_signals'])); + } + + /** + * @return Data[] + */ + public function getHelperSet(): array + { + return $this->data['helper_set'] ?? []; + } + + public function reset(): void + { + $this->data = []; + } +} diff --git a/lib/symfony/console/Debug/CliRequest.php b/lib/symfony/console/Debug/CliRequest.php new file mode 100644 index 000000000..b023db07a --- /dev/null +++ b/lib/symfony/console/Debug/CliRequest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Debug; + +use Symfony\Component\Console\Command\TraceableCommand; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @internal + */ +final class CliRequest extends Request +{ + public function __construct( + public readonly TraceableCommand $command, + ) { + parent::__construct( + attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'], + server: $_SERVER, + ); + } + + // Methods below allow to populate a profile, thus enable search and filtering + public function getUri(): string + { + if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) { + $binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console'; + } else { + $binary = $this->server->get('argv')[0]; + } + + return $binary.' '.$this->command->input; + } + + public function getMethod(): string + { + return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH'; + } + + public function getResponse(): Response + { + return new class($this->command->exitCode) extends Response { + public function __construct(private readonly int $exitCode) + { + parent::__construct(); + } + + public function getStatusCode(): int + { + return $this->exitCode; + } + }; + } + + public function getClientIp(): string + { + $application = $this->command->getApplication(); + + return $application->getName().' '.$application->getVersion(); + } +} diff --git a/lib/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/lib/symfony/console/DependencyInjection/AddConsoleCommandPass.php index 1fbb212e7..27705ddb6 100644 --- a/lib/symfony/console/DependencyInjection/AddConsoleCommandPass.php +++ b/lib/symfony/console/DependencyInjection/AddConsoleCommandPass.php @@ -29,33 +29,19 @@ use Symfony\Component\DependencyInjection\TypedReference; */ class AddConsoleCommandPass implements CompilerPassInterface { - private $commandLoaderServiceId; - private $commandTag; - private $noPreloadTag; - private $privateTagName; - - public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload', string $privateTagName = 'container.private') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/console', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->commandLoaderServiceId = $commandLoaderServiceId; - $this->commandTag = $commandTag; - $this->noPreloadTag = $noPreloadTag; - $this->privateTagName = $privateTagName; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { - $commandServices = $container->findTaggedServiceIds($this->commandTag, true); + $commandServices = $container->findTaggedServiceIds('console.command', true); $lazyCommandMap = []; $lazyCommandRefs = []; $serviceIds = []; foreach ($commandServices as $id => $tags) { $definition = $container->getDefinition($id); - $definition->addTag($this->noPreloadTag); + $definition->addTag('container.no_preload'); $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (isset($tags[0]['command'])) { @@ -65,7 +51,7 @@ class AddConsoleCommandPass implements CompilerPassInterface throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { - throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); } $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); } @@ -78,7 +64,7 @@ class AddConsoleCommandPass implements CompilerPassInterface } if (null === $commandName) { - if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) { + if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) { $commandId = 'console.command.public_alias.'.$id; $container->setAlias($commandId, $id)->setPublic(true); $id = $commandId; @@ -104,7 +90,7 @@ class AddConsoleCommandPass implements CompilerPassInterface $lazyCommandMap[$tag['command']] = $id; } - $description = $description ?? $tag['description'] ?? null; + $description ??= $tag['description'] ?? null; } $definition->addMethodCall('setName', [$commandName]); @@ -122,7 +108,7 @@ class AddConsoleCommandPass implements CompilerPassInterface throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { - throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); } $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); } @@ -138,9 +124,9 @@ class AddConsoleCommandPass implements CompilerPassInterface } $container - ->register($this->commandLoaderServiceId, ContainerCommandLoader::class) + ->register('console.command_loader', ContainerCommandLoader::class) ->setPublic(true) - ->addTag($this->noPreloadTag) + ->addTag('container.no_preload') ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); $container->setParameter('console.command.ids', $serviceIds); diff --git a/lib/symfony/console/Descriptor/ApplicationDescription.php b/lib/symfony/console/Descriptor/ApplicationDescription.php index 2a3acc99b..f8ed18045 100644 --- a/lib/symfony/console/Descriptor/ApplicationDescription.php +++ b/lib/symfony/console/Descriptor/ApplicationDescription.php @@ -24,24 +24,20 @@ class ApplicationDescription { public const GLOBAL_NAMESPACE = '_global'; - private $application; - private $namespace; - private $showHidden; - - /** - * @var array - */ - private $namespaces; + private Application $application; + private ?string $namespace; + private bool $showHidden; + private array $namespaces; /** * @var array */ - private $commands; + private array $commands; /** * @var array */ - private $aliases; + private array $aliases = []; public function __construct(Application $application, string $namespace = null, bool $showHidden = false) { @@ -52,7 +48,7 @@ class ApplicationDescription public function getNamespaces(): array { - if (null === $this->namespaces) { + if (!isset($this->namespaces)) { $this->inspectApplication(); } @@ -64,7 +60,7 @@ class ApplicationDescription */ public function getCommands(): array { - if (null === $this->commands) { + if (!isset($this->commands)) { $this->inspectApplication(); } @@ -83,7 +79,7 @@ class ApplicationDescription return $this->commands[$name] ?? $this->aliases[$name]; } - private function inspectApplication() + private function inspectApplication(): void { $this->commands = []; $this->namespaces = []; diff --git a/lib/symfony/console/Descriptor/Descriptor.php b/lib/symfony/console/Descriptor/Descriptor.php index a3648301f..7b2509c60 100644 --- a/lib/symfony/console/Descriptor/Descriptor.php +++ b/lib/symfony/console/Descriptor/Descriptor.php @@ -26,43 +26,23 @@ use Symfony\Component\Console\Output\OutputInterface; */ abstract class Descriptor implements DescriptorInterface { - /** - * @var OutputInterface - */ - protected $output; + protected OutputInterface $output; - /** - * {@inheritdoc} - */ - public function describe(OutputInterface $output, object $object, array $options = []) + public function describe(OutputInterface $output, object $object, array $options = []): void { $this->output = $output; - switch (true) { - case $object instanceof InputArgument: - $this->describeInputArgument($object, $options); - break; - case $object instanceof InputOption: - $this->describeInputOption($object, $options); - break; - case $object instanceof InputDefinition: - $this->describeInputDefinition($object, $options); - break; - case $object instanceof Command: - $this->describeCommand($object, $options); - break; - case $object instanceof Application: - $this->describeApplication($object, $options); - break; - default: - throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); - } + match (true) { + $object instanceof InputArgument => $this->describeInputArgument($object, $options), + $object instanceof InputOption => $this->describeInputOption($object, $options), + $object instanceof InputDefinition => $this->describeInputDefinition($object, $options), + $object instanceof Command => $this->describeCommand($object, $options), + $object instanceof Application => $this->describeApplication($object, $options), + default => throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))), + }; } - /** - * Writes content to output. - */ - protected function write(string $content, bool $decorated = false) + protected function write(string $content, bool $decorated = false): void { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } @@ -70,25 +50,25 @@ abstract class Descriptor implements DescriptorInterface /** * 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. */ - abstract protected function describeInputOption(InputOption $option, array $options = []); + abstract protected function describeInputOption(InputOption $option, array $options = []): void; /** * 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. */ - abstract protected function describeCommand(Command $command, array $options = []); + abstract protected function describeCommand(Command $command, array $options = []): void; /** * Describes an Application instance. */ - abstract protected function describeApplication(Application $application, array $options = []); + abstract protected function describeApplication(Application $application, array $options = []): void; } diff --git a/lib/symfony/console/Descriptor/DescriptorInterface.php b/lib/symfony/console/Descriptor/DescriptorInterface.php index ebea30367..ab468a256 100644 --- a/lib/symfony/console/Descriptor/DescriptorInterface.php +++ b/lib/symfony/console/Descriptor/DescriptorInterface.php @@ -20,5 +20,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ interface DescriptorInterface { + /** + * @return void + */ public function describe(OutputInterface $output, object $object, array $options = []); } diff --git a/lib/symfony/console/Descriptor/JsonDescriptor.php b/lib/symfony/console/Descriptor/JsonDescriptor.php index 1d2865941..956303709 100644 --- a/lib/symfony/console/Descriptor/JsonDescriptor.php +++ b/lib/symfony/console/Descriptor/JsonDescriptor.php @@ -26,18 +26,12 @@ use Symfony\Component\Console\Input\InputOption; */ class JsonDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ - protected function describeInputArgument(InputArgument $argument, array $options = []) + protected function describeInputArgument(InputArgument $argument, array $options = []): void { $this->writeData($this->getInputArgumentData($argument), $options); } - /** - * {@inheritdoc} - */ - protected function describeInputOption(InputOption $option, array $options = []) + protected function describeInputOption(InputOption $option, array $options = []): void { $this->writeData($this->getInputOptionData($option), $options); if ($option->isNegatable()) { @@ -45,26 +39,17 @@ class JsonDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = []) + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void { $this->writeData($this->getInputDefinitionData($definition), $options); } - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = []) + protected function describeCommand(Command $command, array $options = []): void { $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options); } - /** - * {@inheritdoc} - */ - protected function describeApplication(Application $application, array $options = []) + protected function describeApplication(Application $application, array $options = []): void { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace, true); @@ -96,7 +81,7 @@ class JsonDescriptor extends Descriptor /** * Writes data as json. */ - private function writeData(array $data, array $options) + private function writeData(array $data, array $options): void { $flags = $options['json_encoding'] ?? 0; diff --git a/lib/symfony/console/Descriptor/MarkdownDescriptor.php b/lib/symfony/console/Descriptor/MarkdownDescriptor.php index 21ceca6c2..b3f16ee90 100644 --- a/lib/symfony/console/Descriptor/MarkdownDescriptor.php +++ b/lib/symfony/console/Descriptor/MarkdownDescriptor.php @@ -28,10 +28,7 @@ use Symfony\Component\Console\Output\OutputInterface; */ class MarkdownDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ - public function describe(OutputInterface $output, object $object, array $options = []) + public function describe(OutputInterface $output, object $object, array $options = []): void { $decorated = $output->isDecorated(); $output->setDecorated(false); @@ -41,18 +38,12 @@ class MarkdownDescriptor extends Descriptor $output->setDecorated($decorated); } - /** - * {@inheritdoc} - */ - protected function write(string $content, bool $decorated = true) + protected function write(string $content, bool $decorated = true): void { parent::write($content, $decorated); } - /** - * {@inheritdoc} - */ - protected function describeInputArgument(InputArgument $argument, array $options = []) + protected function describeInputArgument(InputArgument $argument, array $options = []): void { $this->write( '#### `'.($argument->getName() ?: '')."`\n\n" @@ -63,10 +54,7 @@ class MarkdownDescriptor extends Descriptor ); } - /** - * {@inheritdoc} - */ - protected function describeInputOption(InputOption $option, array $options = []) + protected function describeInputOption(InputOption $option, array $options = []): void { $name = '--'.$option->getName(); if ($option->isNegatable()) { @@ -87,18 +75,13 @@ class MarkdownDescriptor extends Descriptor ); } - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = []) + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void { if ($showArguments = \count($definition->getArguments()) > 0) { $this->write('### Arguments'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); - if (null !== $describeInputArgument = $this->describeInputArgument($argument)) { - $this->write($describeInputArgument); - } + $this->describeInputArgument($argument); } } @@ -110,17 +93,12 @@ class MarkdownDescriptor extends Descriptor $this->write('### Options'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); - if (null !== $describeInputOption = $this->describeInputOption($option)) { - $this->write($describeInputOption); - } + $this->describeInputOption($option); } } } - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = []) + protected function describeCommand(Command $command, array $options = []): void { if ($options['short'] ?? false) { $this->write( @@ -128,9 +106,7 @@ class MarkdownDescriptor extends Descriptor .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" - .array_reduce($command->getAliases(), function ($carry, $usage) { - return $carry.'* `'.$usage.'`'."\n"; - }) + .array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") ); return; @@ -143,9 +119,7 @@ class MarkdownDescriptor extends Descriptor .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" - .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { - return $carry.'* `'.$usage.'`'."\n"; - }) + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") ); if ($help = $command->getProcessedHelp()) { @@ -160,10 +134,7 @@ class MarkdownDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ - protected function describeApplication(Application $application, array $options = []) + protected function describeApplication(Application $application, array $options = []): void { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); @@ -178,16 +149,12 @@ class MarkdownDescriptor extends Descriptor } $this->write("\n\n"); - $this->write(implode("\n", array_map(function ($commandName) use ($description) { - return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); - }, $namespace['commands']))); + $this->write(implode("\n", array_map(fn ($commandName) => sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); - if (null !== $describeCommand = $this->describeCommand($command, $options)) { - $this->write($describeCommand); - } + $this->describeCommand($command, $options); } } diff --git a/lib/symfony/console/Descriptor/ReStructuredTextDescriptor.php b/lib/symfony/console/Descriptor/ReStructuredTextDescriptor.php new file mode 100644 index 000000000..d4423fd34 --- /dev/null +++ b/lib/symfony/console/Descriptor/ReStructuredTextDescriptor.php @@ -0,0 +1,272 @@ + + * + * 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 +{ + //

+ private string $partChar = '='; + //

+ private string $chapterChar = '-'; + //

+ private string $sectionChar = '~'; + //

+ private string $subsectionChar = '.'; + //

+ private string $subsubsectionChar = '^'; + //
+ 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() ?: ''."\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; + } +} diff --git a/lib/symfony/console/Descriptor/TextDescriptor.php b/lib/symfony/console/Descriptor/TextDescriptor.php index fbb140ae7..d04d10238 100644 --- a/lib/symfony/console/Descriptor/TextDescriptor.php +++ b/lib/symfony/console/Descriptor/TextDescriptor.php @@ -28,10 +28,7 @@ use Symfony\Component\Console\Input\InputOption; */ class TextDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ - 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()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); @@ -51,10 +48,7 @@ class TextDescriptor extends Descriptor ), $options); } - /** - * {@inheritdoc} - */ - 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()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); @@ -89,10 +83,7 @@ class TextDescriptor extends Descriptor ), $options); } - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = []) + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void { $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); foreach ($definition->getArguments() as $argument) { @@ -131,10 +122,7 @@ class TextDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = []) + protected function describeCommand(Command $command, array $options = []): void { $command->mergeApplicationDefinition(false); @@ -169,10 +157,7 @@ class TextDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ - protected function describeApplication(Application $application, array $options = []) + protected function describeApplication(Application $application, array $options = []): void { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); @@ -208,9 +193,7 @@ class TextDescriptor extends Descriptor } // calculate max. width based on available commands per namespace - $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) { - return array_intersect($namespace['commands'], array_keys($commands)); - }, array_values($namespaces))))); + $width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces))))); if ($describedNamespace) { $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); @@ -219,9 +202,7 @@ class TextDescriptor extends Descriptor } foreach ($namespaces as $namespace) { - $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { - return isset($commands[$name]); - }); + $namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name])); if (!$namespace['commands']) { continue; @@ -245,10 +226,7 @@ class TextDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ - private function writeText(string $content, array $options = []) + private function writeText(string $content, array $options = []): void { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, @@ -273,10 +251,8 @@ class TextDescriptor extends Descriptor /** * Formats input option/argument default value. - * - * @param mixed $default */ - private function formatDefaultValue($default): string + private function formatDefaultValue(mixed $default): string { if (\INF === $default) { return 'INF'; diff --git a/lib/symfony/console/Descriptor/XmlDescriptor.php b/lib/symfony/console/Descriptor/XmlDescriptor.php index 4f7cd8b3e..72580fd98 100644 --- a/lib/symfony/console/Descriptor/XmlDescriptor.php +++ b/lib/symfony/console/Descriptor/XmlDescriptor.php @@ -120,42 +120,27 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * {@inheritdoc} - */ - protected function describeInputArgument(InputArgument $argument, array $options = []) + protected function describeInputArgument(InputArgument $argument, array $options = []): void { $this->writeDocument($this->getInputArgumentDocument($argument)); } - /** - * {@inheritdoc} - */ - protected function describeInputOption(InputOption $option, array $options = []) + protected function describeInputOption(InputOption $option, array $options = []): void { $this->writeDocument($this->getInputOptionDocument($option)); } - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = []) + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void { $this->writeDocument($this->getInputDefinitionDocument($definition)); } - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = []) + protected function describeCommand(Command $command, array $options = []): void { $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false)); } - /** - * {@inheritdoc} - */ - 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)); } @@ -163,7 +148,7 @@ class XmlDescriptor extends Descriptor /** * 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) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); @@ -173,7 +158,7 @@ class XmlDescriptor extends Descriptor /** * Writes DOM document. */ - private function writeDocument(\DOMDocument $dom) + private function writeDocument(\DOMDocument $dom): void { $dom->formatOutput = true; $this->write($dom->saveXML()); diff --git a/lib/symfony/console/Event/ConsoleCommandEvent.php b/lib/symfony/console/Event/ConsoleCommandEvent.php index 08bd18fd1..0757a23f6 100644 --- a/lib/symfony/console/Event/ConsoleCommandEvent.php +++ b/lib/symfony/console/Event/ConsoleCommandEvent.php @@ -12,7 +12,10 @@ namespace Symfony\Component\Console\Event; /** - * Allows to do things before the command is executed, like skipping the command or changing the input. + * Allows to do things before the command is executed, like skipping the command or executing code before the command is + * going to be executed. + * + * Changing the input arguments will have no effect. * * @author Fabien Potencier */ @@ -26,7 +29,7 @@ final class ConsoleCommandEvent extends ConsoleEvent /** * Indicates if the command should be run or skipped. */ - private $commandShouldRun = true; + private bool $commandShouldRun = true; /** * Disables the command, so it won't be run. diff --git a/lib/symfony/console/Event/ConsoleErrorEvent.php b/lib/symfony/console/Event/ConsoleErrorEvent.php index 57d9b38ba..d4a691216 100644 --- a/lib/symfony/console/Event/ConsoleErrorEvent.php +++ b/lib/symfony/console/Event/ConsoleErrorEvent.php @@ -22,8 +22,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ final class ConsoleErrorEvent extends ConsoleEvent { - private $error; - private $exitCode; + private \Throwable $error; + private int $exitCode; public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null) { @@ -47,7 +47,6 @@ final class ConsoleErrorEvent extends ConsoleEvent $this->exitCode = $exitCode; $r = new \ReflectionProperty($this->error, 'code'); - $r->setAccessible(true); $r->setValue($this->error, $this->exitCode); } diff --git a/lib/symfony/console/Event/ConsoleEvent.php b/lib/symfony/console/Event/ConsoleEvent.php index be7937d51..6ba1615fe 100644 --- a/lib/symfony/console/Event/ConsoleEvent.php +++ b/lib/symfony/console/Event/ConsoleEvent.php @@ -25,8 +25,8 @@ class ConsoleEvent extends Event { protected $command; - private $input; - private $output; + private InputInterface $input; + private OutputInterface $output; public function __construct(?Command $command, InputInterface $input, OutputInterface $output) { @@ -37,30 +37,24 @@ class ConsoleEvent extends Event /** * Gets the command that is executed. - * - * @return Command|null */ - public function getCommand() + public function getCommand(): ?Command { return $this->command; } /** * Gets the input instance. - * - * @return InputInterface */ - public function getInput() + public function getInput(): InputInterface { return $this->input; } /** * Gets the output instance. - * - * @return OutputInterface */ - public function getOutput() + public function getOutput(): OutputInterface { return $this->output; } diff --git a/lib/symfony/console/Event/ConsoleSignalEvent.php b/lib/symfony/console/Event/ConsoleSignalEvent.php index ef13ed2f5..95af1f915 100644 --- a/lib/symfony/console/Event/ConsoleSignalEvent.php +++ b/lib/symfony/console/Event/ConsoleSignalEvent.php @@ -20,16 +20,37 @@ use Symfony\Component\Console\Output\OutputInterface; */ final class ConsoleSignalEvent extends ConsoleEvent { - private $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); $this->handlingSignal = $handlingSignal; + $this->exitCode = $exitCode; } public function getHandlingSignal(): int { 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; + } } diff --git a/lib/symfony/console/Event/ConsoleTerminateEvent.php b/lib/symfony/console/Event/ConsoleTerminateEvent.php index 190038d1a..38f7253a5 100644 --- a/lib/symfony/console/Event/ConsoleTerminateEvent.php +++ b/lib/symfony/console/Event/ConsoleTerminateEvent.php @@ -19,16 +19,18 @@ use Symfony\Component\Console\Output\OutputInterface; * Allows to manipulate the exit code of a command after its execution. * * @author Francesco Levorato + * @author Jules Pietri */ final class ConsoleTerminateEvent extends ConsoleEvent { - private $exitCode; - - public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode) - { + public function __construct( + Command $command, + InputInterface $input, + OutputInterface $output, + private int $exitCode, + private readonly ?int $interruptingSignal = null, + ) { parent::__construct($command, $input, $output); - - $this->setExitCode($exitCode); } public function setExitCode(int $exitCode): void @@ -40,4 +42,9 @@ final class ConsoleTerminateEvent extends ConsoleEvent { return $this->exitCode; } + + public function getInterruptingSignal(): ?int + { + return $this->interruptingSignal; + } } diff --git a/lib/symfony/console/EventListener/ErrorListener.php b/lib/symfony/console/EventListener/ErrorListener.php index 897d9853f..9925a5f74 100644 --- a/lib/symfony/console/EventListener/ErrorListener.php +++ b/lib/symfony/console/EventListener/ErrorListener.php @@ -24,13 +24,16 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; */ class ErrorListener implements EventSubscriberInterface { - private $logger; + private ?LoggerInterface $logger; public function __construct(LoggerInterface $logger = null) { $this->logger = $logger; } + /** + * @return void + */ public function onConsoleError(ConsoleErrorEvent $event) { 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()]); } + /** + * @return void + */ public function onConsoleTerminate(ConsoleTerminateEvent $event) { if (null === $this->logger) { @@ -69,7 +75,7 @@ class ErrorListener implements EventSubscriberInterface $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ ConsoleEvents::ERROR => ['onConsoleError', -128], @@ -79,10 +85,10 @@ class ErrorListener implements EventSubscriberInterface private static function getInputString(ConsoleEvent $event): ?string { - $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; + $commandName = $event->getCommand()?->getName(); $input = $event->getInput(); - if (method_exists($input, '__toString')) { + if ($input instanceof \Stringable) { if ($commandName) { return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); } diff --git a/lib/symfony/console/Exception/CommandNotFoundException.php b/lib/symfony/console/Exception/CommandNotFoundException.php index 910ae1928..1e9f1c793 100644 --- a/lib/symfony/console/Exception/CommandNotFoundException.php +++ b/lib/symfony/console/Exception/CommandNotFoundException.php @@ -18,7 +18,7 @@ namespace Symfony\Component\Console\Exception; */ class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface { - private $alternatives; + private array $alternatives; /** * @param string $message Exception message to throw @@ -36,7 +36,7 @@ class CommandNotFoundException extends \InvalidArgumentException implements Exce /** * @return string[] */ - public function getAlternatives() + public function getAlternatives(): array { return $this->alternatives; } diff --git a/lib/symfony/console/Exception/RunCommandFailedException.php b/lib/symfony/console/Exception/RunCommandFailedException.php new file mode 100644 index 000000000..5d87ec949 --- /dev/null +++ b/lib/symfony/console/Exception/RunCommandFailedException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +use Symfony\Component\Console\Messenger\RunCommandContext; + +/** + * @author Kevin Bond + */ +final class RunCommandFailedException extends RuntimeException +{ + public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context) + { + parent::__construct( + $exception instanceof \Throwable ? $exception->getMessage() : $exception, + $exception instanceof \Throwable ? $exception->getCode() : 0, + $exception instanceof \Throwable ? $exception : null, + ); + } +} diff --git a/lib/symfony/console/Formatter/NullOutputFormatter.php b/lib/symfony/console/Formatter/NullOutputFormatter.php index d770e1465..5c11c7644 100644 --- a/lib/symfony/console/Formatter/NullOutputFormatter.php +++ b/lib/symfony/console/Formatter/NullOutputFormatter.php @@ -16,52 +16,34 @@ namespace Symfony\Component\Console\Formatter; */ final class NullOutputFormatter implements OutputFormatterInterface { - private $style; + private NullOutputFormatterStyle $style; - /** - * {@inheritdoc} - */ public function format(?string $message): ?string { return null; } - /** - * {@inheritdoc} - */ public function getStyle(string $name): OutputFormatterStyleInterface { // to comply with the interface we must return a OutputFormatterStyleInterface - return $this->style ?? $this->style = new NullOutputFormatterStyle(); + return $this->style ??= new NullOutputFormatterStyle(); } - /** - * {@inheritdoc} - */ public function hasStyle(string $name): bool { return false; } - /** - * {@inheritdoc} - */ public function isDecorated(): bool { return false; } - /** - * {@inheritdoc} - */ public function setDecorated(bool $decorated): void { // do nothing } - /** - * {@inheritdoc} - */ public function setStyle(string $name, OutputFormatterStyleInterface $style): void { // do nothing diff --git a/lib/symfony/console/Formatter/NullOutputFormatterStyle.php b/lib/symfony/console/Formatter/NullOutputFormatterStyle.php index 9232510f4..c2ce7d14c 100644 --- a/lib/symfony/console/Formatter/NullOutputFormatterStyle.php +++ b/lib/symfony/console/Formatter/NullOutputFormatterStyle.php @@ -16,49 +16,37 @@ namespace Symfony\Component\Console\Formatter; */ final class NullOutputFormatterStyle implements OutputFormatterStyleInterface { - /** - * {@inheritdoc} - */ public function apply(string $text): string { return $text; } - /** - * {@inheritdoc} - */ public function setBackground(string $color = null): void { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } // do nothing } - /** - * {@inheritdoc} - */ public function setForeground(string $color = null): void { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } // do nothing } - /** - * {@inheritdoc} - */ public function setOption(string $option): void { // do nothing } - /** - * {@inheritdoc} - */ public function setOptions(array $options): void { // do nothing } - /** - * {@inheritdoc} - */ public function unsetOption(string $option): void { // do nothing diff --git a/lib/symfony/console/Formatter/OutputFormatter.php b/lib/symfony/console/Formatter/OutputFormatter.php index 603e5dca0..3e4897c33 100644 --- a/lib/symfony/console/Formatter/OutputFormatter.php +++ b/lib/symfony/console/Formatter/OutputFormatter.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Console\Formatter; use Symfony\Component\Console\Exception\InvalidArgumentException; +use function Symfony\Component\String\b; + /** * Formatter class for console output. * @@ -21,9 +23,9 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; */ class OutputFormatter implements WrappableOutputFormatterInterface { - private $decorated; - private $styles = []; - private $styleStack; + private bool $decorated; + private array $styles = []; + private OutputFormatterStyleStack $styleStack; public function __clone() { @@ -35,10 +37,8 @@ class OutputFormatter implements WrappableOutputFormatterInterface /** * Escapes "<" and ">" special chars in given text. - * - * @return string */ - public static function escape(string $text) + public static function escape(string $text): string { $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); @@ -84,41 +84,32 @@ class OutputFormatter implements WrappableOutputFormatterInterface } /** - * {@inheritdoc} + * @return void */ public function setDecorated(bool $decorated) { $this->decorated = $decorated; } - /** - * {@inheritdoc} - */ - public function isDecorated() + public function isDecorated(): bool { return $this->decorated; } /** - * {@inheritdoc} + * @return void */ public function setStyle(string $name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } - /** - * {@inheritdoc} - */ - public function hasStyle(string $name) + public function hasStyle(string $name): bool { return isset($this->styles[strtolower($name)]); } - /** - * {@inheritdoc} - */ - public function getStyle(string $name) + public function getStyle(string $name): OutputFormatterStyleInterface { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); @@ -127,16 +118,13 @@ class OutputFormatter implements WrappableOutputFormatterInterface return $this->styles[strtolower($name)]; } - /** - * {@inheritdoc} - */ - public function format(?string $message) + public function format(?string $message): ?string { return $this->formatAndWrap($message, 0); } /** - * {@inheritdoc} + * @return string */ public function formatAndWrap(?string $message, int $width) { @@ -163,7 +151,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface $offset = $pos + \strlen($text); // opening tag? - if ($open = '/' != $text[1]) { + if ($open = '/' !== $text[1]) { $tag = $matches[1][$i][0]; } else { $tag = $matches[3][$i][0] ?? ''; @@ -186,10 +174,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); } - /** - * @return OutputFormatterStyleStack - */ - public function getStyleStack() + public function getStyleStack(): OutputFormatterStyleStack { return $this->styleStack; } @@ -258,10 +243,10 @@ class OutputFormatter implements WrappableOutputFormatterInterface } preg_match('~(\\n)$~', $text, $matches); - $text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text); + $text = $prefix.$this->addLineBreaks($text, $width); $text = rtrim($text, "\n").($matches[1] ?? ''); - if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) { + if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) { $text = "\n".$text; } @@ -282,4 +267,11 @@ class OutputFormatter implements WrappableOutputFormatterInterface return implode("\n", $lines); } + + private function addLineBreaks(string $text, int $width): string + { + $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8'; + + return b($text)->toCodePointString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding); + } } diff --git a/lib/symfony/console/Formatter/OutputFormatterInterface.php b/lib/symfony/console/Formatter/OutputFormatterInterface.php index 0b5f839a2..433cd4197 100644 --- a/lib/symfony/console/Formatter/OutputFormatterInterface.php +++ b/lib/symfony/console/Formatter/OutputFormatterInterface.php @@ -20,41 +20,37 @@ interface OutputFormatterInterface { /** * Sets the decorated flag. + * + * @return void */ public function setDecorated(bool $decorated); /** * Whether the output will decorate messages. - * - * @return bool */ - public function isDecorated(); + public function isDecorated(): bool; /** * Sets a new style. + * + * @return void */ public function setStyle(string $name, OutputFormatterStyleInterface $style); /** * Checks if output formatter has style with specified name. - * - * @return bool */ - public function hasStyle(string $name); + public function hasStyle(string $name): bool; /** * Gets style options from style with specified name. * - * @return OutputFormatterStyleInterface - * * @throws \InvalidArgumentException When style isn't defined */ - public function getStyle(string $name); + public function getStyle(string $name): OutputFormatterStyleInterface; /** * Formats a message according to the given styles. - * - * @return string|null */ - public function format(?string $message); + public function format(?string $message): ?string; } diff --git a/lib/symfony/console/Formatter/OutputFormatterStyle.php b/lib/symfony/console/Formatter/OutputFormatterStyle.php index 0fb36ac63..346a474c6 100644 --- a/lib/symfony/console/Formatter/OutputFormatterStyle.php +++ b/lib/symfony/console/Formatter/OutputFormatterStyle.php @@ -20,12 +20,12 @@ use Symfony\Component\Console\Color; */ class OutputFormatterStyle implements OutputFormatterStyleInterface { - private $color; - private $foreground; - private $background; - private $options; - private $href; - private $handlesHrefGracefully; + private Color $color; + private string $foreground; + private string $background; + private array $options; + private ?string $href = null; + private bool $handlesHrefGracefully; /** * Initializes output formatter style. @@ -39,18 +39,24 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface } /** - * {@inheritdoc} + * @return void */ public function setForeground(string $color = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); } /** - * {@inheritdoc} + * @return void */ public function setBackground(string $color = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); } @@ -60,7 +66,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface } /** - * {@inheritdoc} + * @return void */ public function setOption(string $option) { @@ -69,7 +75,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface } /** - * {@inheritdoc} + * @return void */ public function unsetOption(string $option) { @@ -82,22 +88,18 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface } /** - * {@inheritdoc} + * @return void */ public function setOptions(array $options) { $this->color = new Color($this->foreground, $this->background, $this->options = $options); } - /** - * {@inheritdoc} - */ - public function apply(string $text) + public function apply(string $text): string { - if (null === $this->handlesHrefGracefully) { - $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') - && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); - } + $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) + && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); if (null !== $this->href && $this->handlesHrefGracefully) { $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; diff --git a/lib/symfony/console/Formatter/OutputFormatterStyleInterface.php b/lib/symfony/console/Formatter/OutputFormatterStyleInterface.php index b30560d22..3b15098cb 100644 --- a/lib/symfony/console/Formatter/OutputFormatterStyleInterface.php +++ b/lib/symfony/console/Formatter/OutputFormatterStyleInterface.php @@ -20,33 +20,41 @@ interface OutputFormatterStyleInterface { /** * Sets style foreground color. + * + * @return void */ - public function setForeground(string $color = null); + public function setForeground(?string $color); /** * Sets style background color. + * + * @return void */ - public function setBackground(string $color = null); + public function setBackground(?string $color); /** * Sets some specific style option. + * + * @return void */ public function setOption(string $option); /** * Unsets some specific style option. + * + * @return void */ public function unsetOption(string $option); /** * Sets multiple style options at once. + * + * @return void */ public function setOptions(array $options); /** * Applies the style to a given text. - * - * @return string */ - public function apply(string $text); + public function apply(string $text): string; } diff --git a/lib/symfony/console/Formatter/OutputFormatterStyleStack.php b/lib/symfony/console/Formatter/OutputFormatterStyleStack.php index fc48dc0e1..f98c2eff7 100644 --- a/lib/symfony/console/Formatter/OutputFormatterStyleStack.php +++ b/lib/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -22,9 +22,9 @@ class OutputFormatterStyleStack implements ResetInterface /** * @var OutputFormatterStyleInterface[] */ - private $styles; + private array $styles = []; - private $emptyStyle; + private OutputFormatterStyleInterface $emptyStyle; public function __construct(OutputFormatterStyleInterface $emptyStyle = null) { @@ -34,6 +34,8 @@ class OutputFormatterStyleStack implements ResetInterface /** * Resets stack (ie. empty internal arrays). + * + * @return void */ public function reset() { @@ -42,6 +44,8 @@ class OutputFormatterStyleStack implements ResetInterface /** * Pushes a style in the stack. + * + * @return void */ public function push(OutputFormatterStyleInterface $style) { @@ -51,13 +55,11 @@ class OutputFormatterStyleStack implements ResetInterface /** * Pops a style from the stack. * - * @return OutputFormatterStyleInterface - * * @throws InvalidArgumentException When style tags incorrectly nested */ - public function pop(OutputFormatterStyleInterface $style = null) + public function pop(OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface { - if (empty($this->styles)) { + if (!$this->styles) { return $this->emptyStyle; } @@ -78,12 +80,10 @@ class OutputFormatterStyleStack implements ResetInterface /** * Computes current style with stacks top codes. - * - * @return OutputFormatterStyle */ - public function getCurrent() + public function getCurrent(): OutputFormatterStyleInterface { - if (empty($this->styles)) { + if (!$this->styles) { return $this->emptyStyle; } @@ -93,17 +93,14 @@ class OutputFormatterStyleStack implements ResetInterface /** * @return $this */ - public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static { $this->emptyStyle = $emptyStyle; return $this; } - /** - * @return OutputFormatterStyleInterface - */ - public function getEmptyStyle() + public function getEmptyStyle(): OutputFormatterStyleInterface { return $this->emptyStyle; } diff --git a/lib/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/lib/symfony/console/Formatter/WrappableOutputFormatterInterface.php index 42319ee55..746cd27e7 100644 --- a/lib/symfony/console/Formatter/WrappableOutputFormatterInterface.php +++ b/lib/symfony/console/Formatter/WrappableOutputFormatterInterface.php @@ -20,6 +20,8 @@ interface WrappableOutputFormatterInterface extends OutputFormatterInterface { /** * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). + * + * @return string */ public function formatAndWrap(?string $message, int $width); } diff --git a/lib/symfony/console/Helper/DebugFormatterHelper.php b/lib/symfony/console/Helper/DebugFormatterHelper.php index e258ba050..9ea7fb914 100644 --- a/lib/symfony/console/Helper/DebugFormatterHelper.php +++ b/lib/symfony/console/Helper/DebugFormatterHelper.php @@ -21,15 +21,13 @@ namespace Symfony\Component\Console\Helper; class DebugFormatterHelper extends Helper { private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default']; - private $started = []; - private $count = -1; + private array $started = []; + private int $count = -1; /** * Starts a debug formatting session. - * - * @return string */ - public function start(string $id, string $message, string $prefix = 'RUN') + public function start(string $id, string $message, string $prefix = 'RUN'): string { $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; @@ -38,10 +36,8 @@ class DebugFormatterHelper extends Helper /** * Adds progress to a formatting session. - * - * @return string */ - public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR') + public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string { $message = ''; @@ -74,10 +70,8 @@ class DebugFormatterHelper extends Helper /** * Stops a formatting session. - * - * @return string */ - public function stop(string $id, string $message, bool $successful, string $prefix = 'RES') + public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; @@ -97,10 +91,7 @@ class DebugFormatterHelper extends Helper return sprintf(' ', self::COLORS[$this->started[$id]['border']]); } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'debug_formatter'; } diff --git a/lib/symfony/console/Helper/DescriptorHelper.php b/lib/symfony/console/Helper/DescriptorHelper.php index af85e9c0a..eb32bce8f 100644 --- a/lib/symfony/console/Helper/DescriptorHelper.php +++ b/lib/symfony/console/Helper/DescriptorHelper.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Descriptor\JsonDescriptor; use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -29,7 +30,7 @@ class DescriptorHelper extends Helper /** * @var DescriptorInterface[] */ - private $descriptors = []; + private array $descriptors = []; public function __construct() { @@ -38,6 +39,7 @@ class DescriptorHelper extends Helper ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) + ->register('rst', new ReStructuredTextDescriptor()) ; } @@ -48,6 +50,8 @@ class DescriptorHelper extends Helper * * format: string, the output format name * * raw_text: boolean, sets output type as raw * + * @return void + * * @throws InvalidArgumentException when the given format is not supported */ public function describe(OutputInterface $output, ?object $object, array $options = []) @@ -70,17 +74,14 @@ class DescriptorHelper extends Helper * * @return $this */ - public function register(string $format, DescriptorInterface $descriptor) + public function register(string $format, DescriptorInterface $descriptor): static { $this->descriptors[$format] = $descriptor; return $this; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'descriptor'; } diff --git a/lib/symfony/console/Helper/Dumper.php b/lib/symfony/console/Helper/Dumper.php index b013b6c52..8c6a94d51 100644 --- a/lib/symfony/console/Helper/Dumper.php +++ b/lib/symfony/console/Helper/Dumper.php @@ -21,10 +21,10 @@ use Symfony\Component\VarDumper\Dumper\CliDumper; */ final class Dumper { - private $output; - private $dumper; - private $cloner; - private $handler; + private OutputInterface $output; + private ?CliDumper $dumper; + private ?ClonerInterface $cloner; + private \Closure $handler; public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null) { @@ -34,30 +34,23 @@ final class Dumper if (class_exists(CliDumper::class)) { $this->handler = function ($var): string { - $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); $dumper->setColors($this->output->isDecorated()); - return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true)); }; } else { - $this->handler = function ($var): string { - switch (true) { - case null === $var: - return 'null'; - case true === $var: - return 'true'; - case false === $var: - return 'false'; - case \is_string($var): - return '"'.$var.'"'; - default: - return rtrim(print_r($var, true)); - } + $this->handler = fn ($var): string => match (true) { + null === $var => 'null', + true === $var => 'true', + false === $var => 'false', + \is_string($var) => '"'.$var.'"', + default => rtrim(print_r($var, true)), }; } } - public function __invoke($var): string + public function __invoke(mixed $var): string { return ($this->handler)($var); } diff --git a/lib/symfony/console/Helper/FormatterHelper.php b/lib/symfony/console/Helper/FormatterHelper.php index 92d8dc724..279e4c799 100644 --- a/lib/symfony/console/Helper/FormatterHelper.php +++ b/lib/symfony/console/Helper/FormatterHelper.php @@ -22,22 +22,16 @@ class FormatterHelper extends Helper { /** * Formats a message within a section. - * - * @return string */ - public function formatSection(string $section, string $message, string $style = 'info') + public function formatSection(string $section, string $message, string $style = 'info'): string { return sprintf('<%s>[%s] %s', $style, $section, $style, $message); } /** * Formats a message as a block of text. - * - * @param string|array $messages The message to write in the block - * - * @return string */ - public function formatBlock($messages, string $style, bool $large = false) + public function formatBlock(string|array $messages, string $style, bool $large = false): string { if (!\is_array($messages)) { $messages = [$messages]; @@ -68,10 +62,8 @@ class FormatterHelper extends Helper /** * Truncates a message to the given length. - * - * @return string */ - public function truncate(string $message, int $length, string $suffix = '...') + public function truncate(string $message, int $length, string $suffix = '...'): string { $computedLength = $length - self::width($suffix); @@ -82,10 +74,7 @@ class FormatterHelper extends Helper return self::substr($message, 0, $length).$suffix; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'formatter'; } diff --git a/lib/symfony/console/Helper/Helper.php b/lib/symfony/console/Helper/Helper.php index c7d3e25d0..c4b3df72e 100644 --- a/lib/symfony/console/Helper/Helper.php +++ b/lib/symfony/console/Helper/Helper.php @@ -21,45 +21,31 @@ use Symfony\Component\String\UnicodeString; */ abstract class Helper implements HelperInterface { - protected $helperSet = null; + protected $helperSet; /** - * {@inheritdoc} + * @return void */ public function setHelperSet(HelperSet $helperSet = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } $this->helperSet = $helperSet; } - /** - * {@inheritdoc} - */ - public function getHelperSet() + public function getHelperSet(): ?HelperSet { return $this->helperSet; } - /** - * Returns the length of a string, using mb_strwidth if it is available. - * - * @deprecated since Symfony 5.3 - * - * @return int - */ - public static function strlen(?string $string) - { - trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::width() or Helper::length() instead.', __METHOD__); - - return self::width($string); - } - /** * Returns the width of a string, using mb_strwidth if it is available. * The width is how many characters positions the string will use. */ public static function width(?string $string): int { - $string ?? $string = ''; + $string ??= ''; if (preg_match('//u', $string)) { return (new UnicodeString($string))->width(false); @@ -78,7 +64,7 @@ abstract class Helper implements HelperInterface */ public static function length(?string $string): int { - $string ?? $string = ''; + $string ??= ''; if (preg_match('//u', $string)) { return (new UnicodeString($string))->length(); @@ -93,12 +79,10 @@ abstract class Helper implements HelperInterface /** * Returns the subset of a string, using mb_substr if it is available. - * - * @return string */ - public static function substr(?string $string, int $from, int $length = null) + public static function substr(?string $string, int $from, int $length = null): string { - $string ?? $string = ''; + $string ??= ''; if (false === $encoding = mb_detect_encoding($string, null, true)) { return substr($string, $from, $length); @@ -107,35 +91,52 @@ abstract class Helper implements HelperInterface return mb_substr($string, $from, $length, $encoding); } - public static function formatTime($secs) + /** + * @return string + */ + public static function formatTime(int|float $secs, int $precision = 1) { + $secs = (int) floor($secs); + + if (0 === $secs) { + return '< 1 sec'; + } + static $timeFormats = [ - [0, '< 1 sec'], - [1, '1 sec'], - [2, 'secs', 1], - [60, '1 min'], - [120, 'mins', 60], - [3600, '1 hr'], - [7200, 'hrs', 3600], - [86400, '1 day'], - [172800, 'days', 86400], + [1, '1 sec', 'secs'], + [60, '1 min', 'mins'], + [3600, '1 hr', 'hrs'], + [86400, '1 day', 'days'], ]; + $times = []; foreach ($timeFormats as $index => $format) { - if ($secs >= $format[0]) { - if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) - || $index == \count($timeFormats) - 1 - ) { - if (2 == \count($format)) { - return $format[1]; - } + $seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs; - return floor($secs / $format[2]).' '.$format[1]; - } + if (isset($times[$index - $precision])) { + unset($times[$index - $precision]); } + + if (0 === $seconds) { + continue; + } + + $unitCount = ($seconds / $format[0]); + $times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2]; + + if ($secs === $seconds) { + break; + } + + $secs -= $seconds; } + + return implode(', ', array_reverse($times)); } + /** + * @return string + */ public static function formatMemory(int $memory) { if ($memory >= 1024 * 1024 * 1024) { @@ -154,15 +155,8 @@ abstract class Helper implements HelperInterface } /** - * @deprecated since Symfony 5.3 + * @return string */ - public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string) - { - trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::removeDecoration() instead.', __METHOD__); - - return self::width(self::removeDecoration($formatter, $string)); - } - public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string) { $isDecorated = $formatter->isDecorated(); diff --git a/lib/symfony/console/Helper/HelperInterface.php b/lib/symfony/console/Helper/HelperInterface.php index fc952b486..ab626c938 100644 --- a/lib/symfony/console/Helper/HelperInterface.php +++ b/lib/symfony/console/Helper/HelperInterface.php @@ -20,15 +20,15 @@ interface HelperInterface { /** * Sets the helper set associated with this helper. + * + * @return void */ - public function setHelperSet(HelperSet $helperSet = null); + public function setHelperSet(?HelperSet $helperSet); /** * Gets the helper set associated with this helper. - * - * @return HelperSet|null */ - public function getHelperSet(); + public function getHelperSet(): ?HelperSet; /** * Returns the canonical name of this helper. diff --git a/lib/symfony/console/Helper/HelperSet.php b/lib/symfony/console/Helper/HelperSet.php index 719762d24..dc5d499ca 100644 --- a/lib/symfony/console/Helper/HelperSet.php +++ b/lib/symfony/console/Helper/HelperSet.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Console\Helper; -use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; /** @@ -19,16 +18,15 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; * * @author Fabien Potencier * - * @implements \IteratorAggregate + * @implements \IteratorAggregate */ class HelperSet implements \IteratorAggregate { - /** @var array */ - private $helpers = []; - private $command; + /** @var array */ + private array $helpers = []; /** - * @param Helper[] $helpers An array of helper + * @param HelperInterface[] $helpers */ public function __construct(array $helpers = []) { @@ -37,6 +35,9 @@ class HelperSet implements \IteratorAggregate } } + /** + * @return void + */ public function set(HelperInterface $helper, string $alias = null) { $this->helpers[$helper->getName()] = $helper; @@ -49,10 +50,8 @@ class HelperSet implements \IteratorAggregate /** * Returns true if the helper if defined. - * - * @return bool */ - public function has(string $name) + public function has(string $name): bool { return isset($this->helpers[$name]); } @@ -60,11 +59,9 @@ class HelperSet implements \IteratorAggregate /** * Gets a helper value. * - * @return HelperInterface - * * @throws InvalidArgumentException if the helper is not defined */ - public function get(string $name) + public function get(string $name): HelperInterface { if (!$this->has($name)) { throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); @@ -73,35 +70,7 @@ class HelperSet implements \IteratorAggregate return $this->helpers[$name]; } - /** - * @deprecated since Symfony 5.4 - */ - public function setCommand(Command $command = null) - { - trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); - - $this->command = $command; - } - - /** - * Gets the command associated with this helper set. - * - * @return Command - * - * @deprecated since Symfony 5.4 - */ - public function getCommand() - { - trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); - - return $this->command; - } - - /** - * @return \Traversable - */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->helpers); } diff --git a/lib/symfony/console/Helper/InputAwareHelper.php b/lib/symfony/console/Helper/InputAwareHelper.php index 0d0dba23e..6f8225973 100644 --- a/lib/symfony/console/Helper/InputAwareHelper.php +++ b/lib/symfony/console/Helper/InputAwareHelper.php @@ -24,7 +24,7 @@ abstract class InputAwareHelper extends Helper implements InputAwareInterface protected $input; /** - * {@inheritdoc} + * @return void */ public function setInput(InputInterface $input) { diff --git a/lib/symfony/console/Helper/OutputWrapper.php b/lib/symfony/console/Helper/OutputWrapper.php new file mode 100644 index 000000000..2ec819c74 --- /dev/null +++ b/lib/symfony/console/Helper/OutputWrapper.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow + * answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN). + * + * (?: + * # -- Words/Characters + * ( # (1 start) + * (?> # Atomic Group - Match words with valid breaks + * .{1,16} # 1-N characters + * # Followed by one of 4 prioritized, non-linebreak whitespace + * (?: # break types: + * (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace + * [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace ) + * | (?= \r? \n ) # 2. - Ahead a linebreak + * | $ # 3. - EOS + * | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace + * ) + * ) # End atomic group + * | + * .{1,16} # No valid word breaks, just break on the N'th character + * ) # (1 end) + * (?: \r? \n )? # Optional linebreak after Words/Characters + * | + * # -- Or, Linebreak + * (?: \r? \n | $ ) # Stand alone linebreak or at EOS + * ) + * + * @author KrisztiĂ¡n Ferenczi + * + * @see https://stackoverflow.com/a/20434776/1476819 + */ +final class OutputWrapper +{ + private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+'; + private const URL_PATTERN = 'https?://\S+'; + + public function __construct( + private bool $allowCutUrls = false + ) { + } + + public function wrap(string $text, int $width, string $break = "\n"): string + { + if (!$width) { + return $text; + } + + $tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT); + $limitPattern = "{1,$width}"; + $patternBlocks = [$tagPattern]; + if (!$this->allowCutUrls) { + $patternBlocks[] = self::URL_PATTERN; + } + $patternBlocks[] = '.'; + $blocks = implode('|', $patternBlocks); + $rowPattern = "(?:$blocks)$limitPattern"; + $pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern); + $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break); + + return str_replace(' '.$break, $break, $output); + } +} diff --git a/lib/symfony/console/Helper/ProcessHelper.php b/lib/symfony/console/Helper/ProcessHelper.php index 4ea3d724d..26d35a1a8 100644 --- a/lib/symfony/console/Helper/ProcessHelper.php +++ b/lib/symfony/console/Helper/ProcessHelper.php @@ -32,7 +32,7 @@ class ProcessHelper extends Helper * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR */ - public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process + public function run(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process { if (!class_exists(Process::class)) { throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); @@ -48,10 +48,6 @@ class ProcessHelper extends Helper $cmd = [$cmd]; } - if (!\is_array($cmd)) { - throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd))); - } - if (\is_string($cmd[0] ?? null)) { $process = new Process($cmd); $cmd = []; @@ -98,7 +94,7 @@ class ProcessHelper extends Helper * * @see run() */ - public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process + public function mustRun(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null): Process { $process = $this->run($output, $cmd, $error, $callback); @@ -134,9 +130,6 @@ class ProcessHelper extends Helper return str_replace('<', '\\<', $str); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'process'; diff --git a/lib/symfony/console/Helper/ProgressBar.php b/lib/symfony/console/Helper/ProgressBar.php index eb6aacb1a..64389c4a2 100644 --- a/lib/symfony/console/Helper/ProgressBar.php +++ b/lib/symfony/console/Helper/ProgressBar.php @@ -36,31 +36,33 @@ final class ProgressBar private const FORMAT_DEBUG_NOMAX = 'debug_nomax'; private const FORMAT_NORMAL_NOMAX = 'normal_nomax'; - private $barWidth = 28; - private $barChar; - private $emptyBarChar = '-'; - private $progressChar = '>'; - private $format; - private $internalFormat; - private $redrawFreq = 1; - private $writeCount; - private $lastWriteTime; - private $minSecondsBetweenRedraws = 0; - private $maxSecondsBetweenRedraws = 1; - private $output; - private $step = 0; - private $max; - private $startTime; - private $stepWidth; - private $percent = 0.0; - private $messages = []; - private $overwrite = true; - private $terminal; - private $previousMessage; - private $cursor; + private int $barWidth = 28; + private string $barChar; + private string $emptyBarChar = '-'; + private string $progressChar = '>'; + private ?string $format = null; + private ?string $internalFormat = null; + private ?int $redrawFreq = 1; + private int $writeCount = 0; + private float $lastWriteTime = 0; + private float $minSecondsBetweenRedraws = 0; + private float $maxSecondsBetweenRedraws = 1; + private OutputInterface $output; + private int $step = 0; + private int $startingStep = 0; + private ?int $max = null; + private int $startTime; + private int $stepWidth; + private float $percent = 0.0; + private array $messages = []; + private bool $overwrite = true; + private Terminal $terminal; + private ?string $previousMessage = null; + private Cursor $cursor; + private array $placeholders = []; - private static $formatters; - private static $formats; + private static array $formatters; + private static array $formats; /** * @param int $max Maximum steps (0 if unknown) @@ -93,18 +95,16 @@ 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. * - * @param string $name The placeholder name (including the delimiter char like %) - * @param callable $callable A PHP callable + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable(ProgressBar):string $callable A PHP callable */ public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } + self::$formatters ??= self::initPlaceholderFormatters(); self::$formatters[$name] = $callable; } @@ -116,13 +116,31 @@ final class ProgressBar */ public static function getPlaceholderFormatterDefinition(string $name): ?callable { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } + self::$formatters ??= self::initPlaceholderFormatters(); 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. * @@ -133,9 +151,7 @@ final class ProgressBar */ public static function setFormatDefinition(string $name, string $format): void { - if (!self::$formats) { - self::$formats = self::initFormats(); - } + self::$formats ??= self::initFormats(); self::$formats[$name] = $format; } @@ -147,9 +163,7 @@ final class ProgressBar */ public static function getFormatDefinition(string $name): ?string { - if (!self::$formats) { - self::$formats = self::initFormats(); - } + self::$formats ??= self::initFormats(); return self::$formats[$name] ?? null; } @@ -164,12 +178,12 @@ final class ProgressBar * @param string $message The text to associate with 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; } - public function getMessage(string $name = 'message') + public function getMessage(string $name = 'message'): string { return $this->messages[$name]; } @@ -206,11 +220,11 @@ final class ProgressBar public function getEstimated(): float { - if (!$this->step) { + if (0 === $this->step || $this->step === $this->startingStep) { return 0; } - return round((time() - $this->startTime) / $this->step * $this->max); + return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max); } public function getRemaining(): float @@ -219,10 +233,10 @@ final class ProgressBar return 0; } - return round((time() - $this->startTime) / $this->step * ($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); } @@ -232,7 +246,7 @@ final class ProgressBar return $this->barWidth; } - public function setBarCharacter(string $char) + public function setBarCharacter(string $char): void { $this->barChar = $char; } @@ -242,7 +256,7 @@ final class ProgressBar return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); } - public function setEmptyBarCharacter(string $char) + public function setEmptyBarCharacter(string $char): void { $this->emptyBarChar = $char; } @@ -252,7 +266,7 @@ final class ProgressBar return $this->emptyBarChar; } - public function setProgressCharacter(string $char) + public function setProgressCharacter(string $char): void { $this->progressChar = $char; } @@ -262,7 +276,7 @@ final class ProgressBar return $this->progressChar; } - public function setFormat(string $format) + public function setFormat(string $format): void { $this->format = null; $this->internalFormat = $format; @@ -273,7 +287,7 @@ final class ProgressBar * * @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; } @@ -291,7 +305,13 @@ final class ProgressBar /** * Returns an iterator that will automatically update the progress bar when iterated. * - * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + * @template TKey + * @template TValue + * + * @param iterable $iterable + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + * + * @return iterable */ public function iterate(iterable $iterable, int $max = null): iterable { @@ -309,13 +329,16 @@ final class ProgressBar /** * Starts the progress output. * - * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + * @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar) */ - public function start(int $max = null) + public function start(int $max = null, int $startAt = 0): void { $this->startTime = time(); - $this->step = 0; - $this->percent = 0.0; + $this->step = $startAt; + $this->startingStep = $startAt; + + $startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); @@ -329,7 +352,7 @@ final class ProgressBar * * @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); } @@ -337,12 +360,12 @@ final class ProgressBar /** * Sets whether to overwrite the progressbar, false for new line. */ - public function setOverwrite(bool $overwrite) + public function setOverwrite(bool $overwrite): void { $this->overwrite = $overwrite; } - public function setProgress(int $step) + public function setProgress(int $step): void { if ($this->max && $step > $this->max) { $this->max = $step; @@ -375,7 +398,7 @@ final class ProgressBar } } - public function setMaxSteps(int $max) + public function setMaxSteps(int $max): void { $this->format = null; $this->max = max(0, $max); @@ -435,7 +458,7 @@ final class ProgressBar $this->overwrite(''); } - private function setRealFormat(string $format) + private function setRealFormat(string $format): void { // try to use the _nomax variant if available if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { @@ -495,17 +518,13 @@ final class ProgressBar private function determineBestFormat(): string { - switch ($this->output->getVerbosity()) { + return match ($this->output->getVerbosity()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway - case OutputInterface::VERBOSITY_VERBOSE: - return $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX; - case OutputInterface::VERBOSITY_VERY_VERBOSE: - return $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX; - case OutputInterface::VERBOSITY_DEBUG: - return $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX; - default: - return $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX; - } + OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX, + OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX, + OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX, + default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX, + }; } private static function initPlaceholderFormatters(): array @@ -521,35 +540,25 @@ final class ProgressBar return $display; }, - 'elapsed' => function (self $bar) { - return Helper::formatTime(time() - $bar->getStartTime()); - }, + 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2), 'remaining' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } - return Helper::formatTime($bar->getRemaining()); + return Helper::formatTime($bar->getRemaining(), 2); }, 'estimated' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } - return Helper::formatTime($bar->getEstimated()); - }, - 'memory' => function (self $bar) { - return Helper::formatMemory(memory_get_usage(true)); - }, - 'current' => function (self $bar) { - 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); + return Helper::formatTime($bar->getEstimated(), 2); }, + 'memory' => fn (self $bar) => 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(), + 'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100), ]; } @@ -572,9 +581,11 @@ final class ProgressBar private function buildLine(): string { + \assert(null !== $this->format); + $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; $callback = function ($matches) { - if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { + if ($formatter = $this->getPlaceholderFormatter($matches[1])) { $text = $formatter($this, $this->output); } elseif (isset($this->messages[$matches[1]])) { $text = $this->messages[$matches[1]]; @@ -591,9 +602,7 @@ final class ProgressBar $line = preg_replace_callback($regex, $callback, $this->format); // gets string length for each sub line with multiline format - $linesLength = array_map(function ($subLine) { - return Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))); - }, explode("\n", $line)); + $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line)); $linesWidth = max($linesLength); diff --git a/lib/symfony/console/Helper/ProgressIndicator.php b/lib/symfony/console/Helper/ProgressIndicator.php index 3482343fc..79d47643e 100644 --- a/lib/symfony/console/Helper/ProgressIndicator.php +++ b/lib/symfony/console/Helper/ProgressIndicator.php @@ -31,20 +31,20 @@ class ProgressIndicator 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', ]; - private $output; - private $startTime; - private $format; - private $message; - private $indicatorValues; - private $indicatorCurrent; - private $indicatorChangeInterval; - private $indicatorUpdateTime; - private $started = false; + private OutputInterface $output; + private int $startTime; + private ?string $format = null; + private ?string $message = null; + private array $indicatorValues; + private int $indicatorCurrent; + private int $indicatorChangeInterval; + private float $indicatorUpdateTime; + private bool $started = false; /** * @var array */ - private static $formatters; + private static array $formatters; /** * @param int $indicatorChangeInterval Change interval in milliseconds @@ -54,14 +54,8 @@ class ProgressIndicator { $this->output = $output; - if (null === $format) { - $format = $this->determineBestFormat(); - } - - if (null === $indicatorValues) { - $indicatorValues = ['-', '\\', '|', '/']; - } - + $format ??= $this->determineBestFormat(); + $indicatorValues ??= ['-', '\\', '|', '/']; $indicatorValues = array_values($indicatorValues); if (2 > \count($indicatorValues)) { @@ -76,6 +70,8 @@ class ProgressIndicator /** * Sets the current indicator message. + * + * @return void */ public function setMessage(?string $message) { @@ -86,6 +82,8 @@ class ProgressIndicator /** * Starts the indicator output. + * + * @return void */ public function start(string $message) { @@ -104,6 +102,8 @@ class ProgressIndicator /** * Advances the indicator. + * + * @return void */ public function advance() { @@ -130,7 +130,7 @@ class ProgressIndicator /** * Finish the indicator with message. * - * @param $message + * @return void */ public function finish(string $message) { @@ -146,10 +146,8 @@ class ProgressIndicator /** * Gets the format for a given name. - * - * @return string|null */ - public static function getFormatDefinition(string $name) + public static function getFormatDefinition(string $name): ?string { return self::FORMATS[$name] ?? null; } @@ -158,31 +156,27 @@ class ProgressIndicator * Sets a placeholder formatter for a given name. * * This method also allow you to override an existing placeholder. + * + * @return void */ public static function setPlaceholderFormatterDefinition(string $name, callable $callable) { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } + self::$formatters ??= self::initPlaceholderFormatters(); self::$formatters[$name] = $callable; } /** * Gets the placeholder formatter for a given name (including the delimiter char like %). - * - * @return callable|null */ - public static function getPlaceholderFormatterDefinition(string $name) + public static function getPlaceholderFormatterDefinition(string $name): ?callable { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } + self::$formatters ??= self::initPlaceholderFormatters(); return self::$formatters[$name] ?? null; } - private function display() + private function display(): void { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; @@ -199,22 +193,19 @@ class ProgressIndicator private function determineBestFormat(): string { - switch ($this->output->getVerbosity()) { + return match ($this->output->getVerbosity()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway - case OutputInterface::VERBOSITY_VERBOSE: - return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; - case OutputInterface::VERBOSITY_VERY_VERBOSE: - case OutputInterface::VERBOSITY_DEBUG: - return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; - default: - return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; - } + OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi', + OutputInterface::VERBOSITY_VERY_VERBOSE, + OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi', + default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi', + }; } /** * Overwrites a previous message to the output. */ - private function overwrite(string $message) + private function overwrite(string $message): void { if ($this->output->isDecorated()) { $this->output->write("\x0D\x1B[2K"); @@ -229,21 +220,16 @@ class ProgressIndicator return round(microtime(true) * 1000); } + /** + * @return array + */ private static function initPlaceholderFormatters(): array { return [ - 'indicator' => function (self $indicator) { - return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; - }, - 'message' => function (self $indicator) { - return $indicator->message; - }, - 'elapsed' => function (self $indicator) { - return Helper::formatTime(time() - $indicator->startTime); - }, - 'memory' => function () { - return Helper::formatMemory(memory_get_usage(true)); - }, + 'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)], + 'message' => fn (self $indicator) => $indicator->message, + 'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2), + 'memory' => fn () => Helper::formatMemory(memory_get_usage(true)), ]; } } diff --git a/lib/symfony/console/Helper/QuestionHelper.php b/lib/symfony/console/Helper/QuestionHelper.php index 10602038c..f32813c6c 100644 --- a/lib/symfony/console/Helper/QuestionHelper.php +++ b/lib/symfony/console/Helper/QuestionHelper.php @@ -39,8 +39,8 @@ class QuestionHelper extends Helper */ private $inputStream; - private static $stty = true; - private static $stdinIsInteractive; + private static bool $stty = true; + private static bool $stdinIsInteractive; /** * Asks a question to the user. @@ -49,7 +49,7 @@ class QuestionHelper extends Helper * * @throws RuntimeException If there is no data to read in the input stream */ - public function ask(InputInterface $input, OutputInterface $output, Question $question) + public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); @@ -68,9 +68,7 @@ class QuestionHelper extends Helper return $this->doAsk($output, $question); } - $interviewer = function () use ($output, $question) { - return $this->doAsk($output, $question); - }; + $interviewer = fn () => $this->doAsk($output, $question); return $this->validateAttempts($interviewer, $output, $question); } catch (MissingInputException $exception) { @@ -84,16 +82,15 @@ class QuestionHelper extends Helper } } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'question'; } /** * Prevents usage of stty. + * + * @return void */ public static function disableStty() { @@ -103,11 +100,9 @@ class QuestionHelper extends Helper /** * Asks the question to the user. * - * @return mixed - * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ - private function doAsk(OutputInterface $output, Question $question) + private function doAsk(OutputInterface $output, Question $question): mixed { $this->writePrompt($output, $question); @@ -128,7 +123,18 @@ class QuestionHelper extends Helper } if (false === $ret) { + $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true; + + if (!$isBlocked) { + stream_set_blocking($inputStream, true); + } + $ret = $this->readInput($inputStream, $question); + + if (!$isBlocked) { + stream_set_blocking($inputStream, false); + } + if (false === $ret) { throw new MissingInputException('Aborted.'); } @@ -142,6 +148,7 @@ class QuestionHelper extends Helper } if ($output instanceof ConsoleSectionOutput) { + $output->addContent(''); // add EOL to the question $output->addContent($ret); } @@ -154,10 +161,7 @@ class QuestionHelper extends Helper return $ret; } - /** - * @return mixed - */ - private function getDefaultAnswer(Question $question) + private function getDefaultAnswer(Question $question): mixed { $default = $question->getDefault(); @@ -166,7 +170,7 @@ class QuestionHelper extends Helper } if ($validator = $question->getValidator()) { - return \call_user_func($question->getValidator(), $default); + return \call_user_func($validator, $default); } elseif ($question instanceof ChoiceQuestion) { $choices = $question->getChoices(); @@ -186,6 +190,8 @@ class QuestionHelper extends Helper /** * Outputs the question prompt. + * + * @return void */ protected function writePrompt(OutputInterface $output, Question $question) { @@ -205,7 +211,7 @@ class QuestionHelper extends Helper /** * @return string[] */ - protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag) + protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array { $messages = []; @@ -222,6 +228,8 @@ class QuestionHelper extends Helper /** * Outputs an error message. + * + * @return void */ protected function writeError(OutputInterface $output, \Exception $error) { @@ -321,9 +329,7 @@ class QuestionHelper extends Helper $matches = array_filter( $autocomplete($ret), - function ($match) use ($ret) { - return '' === $ret || str_starts_with($match, $ret); - } + fn ($match) => '' === $ret || str_starts_with($match, $ret) ); $numMatches = \count($matches); $ofs = -1; @@ -411,7 +417,7 @@ class QuestionHelper extends Helper $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; // handle code running from a phar - if ('phar:' === substr(__FILE__, 0, 5)) { + if (str_starts_with(__FILE__, 'phar:')) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; @@ -437,6 +443,11 @@ class QuestionHelper extends Helper $value = fgets($inputStream, 4096); + if (4095 === \strlen($value)) { + $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $errOutput->warning('The value was possibly truncated by your shell or terminal emulator'); + } + if (self::$stty && Terminal::hasSttyAvailable()) { shell_exec('stty '.$sttyMode); } @@ -457,11 +468,9 @@ class QuestionHelper extends Helper * * @param callable $interviewer A callable that will ask for a question and return the result * - * @return mixed The validated response - * * @throws \Exception In case the max number of attempts has been reached and no valid response has been given */ - private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) + private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed { $error = null; $attempts = $question->getMaxAttempts(); @@ -488,7 +497,7 @@ class QuestionHelper extends Helper return false; } - if (null !== self::$stdinIsInteractive) { + if (isset(self::$stdinIsInteractive)) { return self::$stdinIsInteractive; } @@ -500,13 +509,11 @@ class QuestionHelper extends Helper return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r')); } - if (!\function_exists('exec')) { + if (!\function_exists('shell_exec')) { return self::$stdinIsInteractive = true; } - exec('stty 2> /dev/null', $output, $status); - - return self::$stdinIsInteractive = 1 !== $status; + return self::$stdinIsInteractive = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); } /** @@ -514,10 +521,8 @@ class QuestionHelper extends Helper * * @param resource $inputStream The handler resource * @param Question $question The question being asked - * - * @return string|false The input received, false in case input could not be read */ - private function readInput($inputStream, Question $question) + private function readInput($inputStream, Question $question): string|false { if (!$question->isMultiline()) { $cp = $this->setIOCodepage(); @@ -543,11 +548,6 @@ class QuestionHelper extends Helper return $this->resetIOCodepage($cp, $ret); } - /** - * Sets console I/O to the host code page. - * - * @return int Previous code page in IBM/EBCDIC format - */ private function setIOCodepage(): int { if (\function_exists('sapi_windows_cp_set')) { @@ -562,12 +562,8 @@ class QuestionHelper extends Helper /** * Sets console I/O to the specified code page and converts the user input. - * - * @param string|false $input - * - * @return string|false */ - private function resetIOCodepage(int $cp, $input) + private function resetIOCodepage(int $cp, string|false $input): string|false { if (0 !== $cp) { sapi_windows_cp_set($cp); diff --git a/lib/symfony/console/Helper/SymfonyQuestionHelper.php b/lib/symfony/console/Helper/SymfonyQuestionHelper.php index 01f94aba4..8ebc84376 100644 --- a/lib/symfony/console/Helper/SymfonyQuestionHelper.php +++ b/lib/symfony/console/Helper/SymfonyQuestionHelper.php @@ -26,7 +26,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; class SymfonyQuestionHelper extends QuestionHelper { /** - * {@inheritdoc} + * @return void */ protected function writePrompt(OutputInterface $output, Question $question) { @@ -84,7 +84,7 @@ class SymfonyQuestionHelper extends QuestionHelper } /** - * {@inheritdoc} + * @return void */ protected function writeError(OutputInterface $output, \Exception $error) { diff --git a/lib/symfony/console/Helper/Table.php b/lib/symfony/console/Helper/Table.php index 3f2d99145..3c189580e 100644 --- a/lib/symfony/console/Helper/Table.php +++ b/lib/symfony/console/Helper/Table.php @@ -35,112 +35,63 @@ class Table private const SEPARATOR_BOTTOM = 3; private const BORDER_OUTSIDE = 0; private const BORDER_INSIDE = 1; + private const DISPLAY_ORIENTATION_DEFAULT = 'default'; + private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal'; + private const DISPLAY_ORIENTATION_VERTICAL = 'vertical'; - private $headerTitle; - private $footerTitle; + private ?string $headerTitle = null; + private ?string $footerTitle = null; + private array $headers = []; + private array $rows = []; + private array $effectiveColumnWidths = []; + private int $numberOfColumns; + private OutputInterface $output; + private TableStyle $style; + private array $columnStyles = []; + private array $columnWidths = []; + private array $columnMaxWidths = []; + private bool $rendered = false; + private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT; - /** - * Table headers. - */ - private $headers = []; - - /** - * Table rows. - */ - private $rows = []; - private $horizontal = false; - - /** - * Column widths cache. - */ - private $effectiveColumnWidths = []; - - /** - * Number of columns cache. - * - * @var int - */ - private $numberOfColumns; - - /** - * @var OutputInterface - */ - private $output; - - /** - * @var TableStyle - */ - private $style; - - /** - * @var array - */ - private $columnStyles = []; - - /** - * User set column widths. - * - * @var array - */ - private $columnWidths = []; - private $columnMaxWidths = []; - - /** - * @var array|null - */ - private static $styles; - - private $rendered = false; + private static array $styles; public function __construct(OutputInterface $output) { $this->output = $output; - if (!self::$styles) { - self::$styles = self::initStyles(); - } + self::$styles ??= self::initStyles(); $this->setStyle('default'); } /** * Sets a style definition. + * + * @return void */ public static function setStyleDefinition(string $name, TableStyle $style) { - if (!self::$styles) { - self::$styles = self::initStyles(); - } + self::$styles ??= self::initStyles(); self::$styles[$name] = $style; } /** * Gets a style definition by name. - * - * @return TableStyle */ - public static function getStyleDefinition(string $name) + public static function getStyleDefinition(string $name): TableStyle { - if (!self::$styles) { - self::$styles = self::initStyles(); - } + self::$styles ??= self::initStyles(); - if (isset(self::$styles[$name])) { - return self::$styles[$name]; - } - - throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } /** * Sets table style. * - * @param TableStyle|string $name The style name or a TableStyle instance - * * @return $this */ - public function setStyle($name) + public function setStyle(TableStyle|string $name): static { $this->style = $this->resolveStyle($name); @@ -149,10 +100,8 @@ class Table /** * Gets the current table style. - * - * @return TableStyle */ - public function getStyle() + public function getStyle(): TableStyle { return $this->style; } @@ -164,7 +113,7 @@ class Table * * @return $this */ - public function setColumnStyle(int $columnIndex, $name) + public function setColumnStyle(int $columnIndex, TableStyle|string $name): static { $this->columnStyles[$columnIndex] = $this->resolveStyle($name); @@ -175,10 +124,8 @@ class Table * Gets the current style for a column. * * If style was not set, it returns the global table style. - * - * @return TableStyle */ - public function getColumnStyle(int $columnIndex) + public function getColumnStyle(int $columnIndex): TableStyle { return $this->columnStyles[$columnIndex] ?? $this->getStyle(); } @@ -188,7 +135,7 @@ class Table * * @return $this */ - public function setColumnWidth(int $columnIndex, int $width) + public function setColumnWidth(int $columnIndex, int $width): static { $this->columnWidths[$columnIndex] = $width; @@ -200,7 +147,7 @@ class Table * * @return $this */ - public function setColumnWidths(array $widths) + public function setColumnWidths(array $widths): static { $this->columnWidths = []; foreach ($widths as $index => $width) { @@ -218,7 +165,7 @@ class Table * * @return $this */ - public function setColumnMaxWidth(int $columnIndex, int $width): self + public function setColumnMaxWidth(int $columnIndex, int $width): static { if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter()))); @@ -232,10 +179,10 @@ class Table /** * @return $this */ - public function setHeaders(array $headers) + public function setHeaders(array $headers): static { $headers = array_values($headers); - if (!empty($headers) && !\is_array($headers[0])) { + if ($headers && !\is_array($headers[0])) { $headers = [$headers]; } @@ -244,6 +191,9 @@ class Table return $this; } + /** + * @return $this + */ public function setRows(array $rows) { $this->rows = []; @@ -254,7 +204,7 @@ class Table /** * @return $this */ - public function addRows(array $rows) + public function addRows(array $rows): static { foreach ($rows as $row) { $this->addRow($row); @@ -266,7 +216,7 @@ class Table /** * @return $this */ - public function addRow($row) + public function addRow(TableSeparator|array $row): static { if ($row instanceof TableSeparator) { $this->rows[] = $row; @@ -274,10 +224,6 @@ class Table return $this; } - if (!\is_array($row)) { - throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); - } - $this->rows[] = array_values($row); return $this; @@ -288,7 +234,7 @@ class Table * * @return $this */ - public function appendRow($row): self + public function appendRow(TableSeparator|array $row): static { if (!$this->output instanceof ConsoleSectionOutput) { throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); @@ -307,7 +253,7 @@ class Table /** * @return $this */ - public function setRow($column, array $row) + public function setRow(int|string $column, array $row): static { $this->rows[$column] = $row; @@ -317,7 +263,7 @@ class Table /** * @return $this */ - public function setHeaderTitle(?string $title): self + public function setHeaderTitle(?string $title): static { $this->headerTitle = $title; @@ -327,7 +273,7 @@ class Table /** * @return $this */ - public function setFooterTitle(?string $title): self + public function setFooterTitle(?string $title): static { $this->footerTitle = $title; @@ -337,9 +283,19 @@ class Table /** * @return $this */ - public function setHorizontal(bool $horizontal = true): self + public function setHorizontal(bool $horizontal = true): static { - $this->horizontal = $horizontal; + $this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT; + + return $this; + } + + /** + * @return $this + */ + public function setVertical(bool $vertical = true): static + { + $this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT; return $this; } @@ -356,12 +312,19 @@ class Table * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ + * + * @return void */ public function render() { $divider = new TableSeparator(); - if ($this->horizontal) { - $rows = []; + $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2; + + $horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation; + $vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation; + + $rows = []; + if ($horizontal) { foreach ($this->headers[0] ?? [] as $i => $header) { $rows[$i] = [$header]; foreach ($this->rows as $row) { @@ -370,13 +333,60 @@ class Table } if (isset($row[$i])) { $rows[$i][] = $row[$i]; - } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) { + } elseif ($isCellWithColspan($rows[$i][0])) { // Noop, there is a "title" } else { $rows[$i][] = null; } } } + } elseif ($vertical) { + $formatter = $this->output->getFormatter(); + $maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0); + + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + if ($rows) { + $rows[] = [$divider]; + } + + $containsColspan = false; + foreach ($row as $cell) { + if ($containsColspan = $isCellWithColspan($cell)) { + break; + } + } + + $headers = $this->headers[0] ?? []; + $maxRows = max(\count($headers), \count($row)); + for ($i = 0; $i < $maxRows; ++$i) { + $cell = (string) ($row[$i] ?? ''); + + $parts = explode("\n", $cell); + foreach ($parts as $idx => $part) { + if ($headers && !$containsColspan) { + if (0 === $idx) { + $rows[] = [sprintf( + '%s: %s', + str_pad($headers[$i] ?? '', $maxHeaderLength, ' ', \STR_PAD_LEFT), + $part + )]; + } else { + $rows[] = [sprintf( + '%s %s', + str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT), + $part + )]; + } + } elseif ('' !== $cell) { + $rows[] = [$part]; + } + } + } + } } else { $rows = array_merge($this->headers, [$divider], $this->rows); } @@ -386,8 +396,8 @@ class Table $rowGroups = $this->buildTableRows($rows); $this->calculateColumnsWidth($rowGroups); - $isHeader = !$this->horizontal; - $isFirstRow = $this->horizontal; + $isHeader = !$horizontal; + $isFirstRow = $horizontal; $hasTitle = (bool) $this->headerTitle; foreach ($rowGroups as $rowGroup) { @@ -413,7 +423,7 @@ class Table if ($isHeader && !$isHeaderSeparatorRendered) { $this->renderRowSeparator( - $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + self::SEPARATOR_TOP, $hasTitle ? $this->headerTitle : null, $hasTitle ? $this->style->getHeaderTitleFormat() : null ); @@ -423,7 +433,7 @@ class Table if ($isFirstRow) { $this->renderRowSeparator( - $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $horizontal ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, $hasTitle ? $this->headerTitle : null, $hasTitle ? $this->style->getHeaderTitleFormat() : null ); @@ -431,7 +441,12 @@ class Table $hasTitle = false; } - if ($this->horizontal) { + if ($vertical) { + $isHeader = false; + $isFirstRow = false; + } + + if ($horizontal) { $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); } else { $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); @@ -451,9 +466,9 @@ 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 (0 === $count = $this->numberOfColumns) { + if (!$count = $this->numberOfColumns) { return; } @@ -516,7 +531,7 @@ class Table * * | 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); $columns = $this->getRowColumns($row); @@ -570,11 +585,11 @@ class Table $cellFormat = '<'.$tag.'>%s'; } - if (strstr($content, '')) { + if (str_contains($content, '')) { $content = str_replace('', '', $content); $width -= 3; } - if (strstr($content, '')) { + if (str_contains($content, '')) { $content = str_replace('', '', $content); $width -= \strlen(''); } @@ -589,7 +604,7 @@ class Table /** * Calculate number of columns for this table. */ - private function calculateNumberOfColumns(array $rows) + private function calculateNumberOfColumns(array $rows): void { $columns = [0]; foreach ($rows as $row) { @@ -618,10 +633,10 @@ class Table if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) { $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); } - if (!strstr($cell ?? '', "\n")) { + if (!str_contains($cell ?? '', "\n")) { continue; } - $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell))); + $escaped = implode("\n", array_map(OutputFormatter::escapeTrailingBackslash(...), explode("\n", $cell))); $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; $lines = explode("\n", str_replace("\n", "\n", $cell)); foreach ($lines as $lineKey => $line) { @@ -662,7 +677,7 @@ class Table ++$numberOfRows; // Add row for header separator } - if (\count($this->rows) > 0) { + if ($this->rows) { ++$numberOfRows; // Add row for footer separator } @@ -678,13 +693,13 @@ class Table { $unmergedRows = []; foreach ($rows[$line] as $column => $cell) { - if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { + if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) { throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell))); } if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; $lines = [$cell]; - if (strstr($cell, "\n")) { + if (str_contains($cell, "\n")) { $lines = explode("\n", str_replace("\n", "\n", $cell)); $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; @@ -728,7 +743,7 @@ class Table /** * fill cells for a row that contains colspan > 1. */ - private function fillCells(iterable $row) + private function fillCells(iterable $row): iterable { $newRow = []; @@ -790,7 +805,7 @@ class Table /** * Calculates columns widths. */ - private function calculateColumnsWidth(iterable $groups) + private function calculateColumnsWidth(iterable $groups): void { for ($column = 0; $column < $this->numberOfColumns; ++$column) { $lengths = []; @@ -805,7 +820,7 @@ class Table $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); $textLength = Helper::width($textContent); if ($textLength > 0) { - $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); + $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan())); foreach ($contentColumns as $position => $content) { $row[$i + $position] = $content; } @@ -844,10 +859,10 @@ class Table /** * Called after rendering to cleanup cache data. */ - private function cleanup() + private function cleanup(): void { $this->effectiveColumnWidths = []; - $this->numberOfColumns = null; + unset($this->numberOfColumns); } /** @@ -900,16 +915,12 @@ class Table ]; } - private function resolveStyle($name): TableStyle + private function resolveStyle(TableStyle|string $name): TableStyle { if ($name instanceof TableStyle) { return $name; } - if (isset(self::$styles[$name])) { - return self::$styles[$name]; - } - - throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } } diff --git a/lib/symfony/console/Helper/TableCell.php b/lib/symfony/console/Helper/TableCell.php index 1a7bc6ede..394b2bc95 100644 --- a/lib/symfony/console/Helper/TableCell.php +++ b/lib/symfony/console/Helper/TableCell.php @@ -18,8 +18,8 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; */ class TableCell { - private $value; - private $options = [ + private string $value; + private array $options = [ 'rowspan' => 1, 'colspan' => 1, 'style' => null, @@ -43,30 +43,24 @@ class TableCell /** * Returns the cell value. - * - * @return string */ - public function __toString() + public function __toString(): string { return $this->value; } /** * Gets number of colspan. - * - * @return int */ - public function getColspan() + public function getColspan(): int { return (int) $this->options['colspan']; } /** * Gets number of rowspan. - * - * @return int */ - public function getRowspan() + public function getRowspan(): int { return (int) $this->options['rowspan']; } diff --git a/lib/symfony/console/Helper/TableCellStyle.php b/lib/symfony/console/Helper/TableCellStyle.php index 19cd0ffc6..9419dcb40 100644 --- a/lib/symfony/console/Helper/TableCellStyle.php +++ b/lib/symfony/console/Helper/TableCellStyle.php @@ -32,7 +32,7 @@ class TableCellStyle 'right' => \STR_PAD_LEFT, ]; - private $options = [ + private array $options = [ 'fg' => 'default', 'bg' => 'default', 'options' => null, @@ -63,21 +63,16 @@ class TableCellStyle * * @return string[] */ - public function getTagOptions() + public function getTagOptions(): array { return array_filter( $this->getOptions(), - function ($key) { - return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]); - }, + fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]), \ARRAY_FILTER_USE_KEY ); } - /** - * @return int - */ - public function getPadByAlign() + public function getPadByAlign(): int { return self::ALIGN_MAP[$this->getOptions()['align']]; } diff --git a/lib/symfony/console/Helper/TableRows.php b/lib/symfony/console/Helper/TableRows.php index cbc07d294..97d07726e 100644 --- a/lib/symfony/console/Helper/TableRows.php +++ b/lib/symfony/console/Helper/TableRows.php @@ -16,7 +16,7 @@ namespace Symfony\Component\Console\Helper; */ class TableRows implements \IteratorAggregate { - private $generator; + private \Closure $generator; public function __construct(\Closure $generator) { diff --git a/lib/symfony/console/Helper/TableStyle.php b/lib/symfony/console/Helper/TableStyle.php index dfc41e6a4..bbad98e73 100644 --- a/lib/symfony/console/Helper/TableStyle.php +++ b/lib/symfony/console/Helper/TableStyle.php @@ -23,37 +23,37 @@ use Symfony\Component\Console\Exception\LogicException; */ class TableStyle { - private $paddingChar = ' '; - private $horizontalOutsideBorderChar = '-'; - private $horizontalInsideBorderChar = '-'; - private $verticalOutsideBorderChar = '|'; - private $verticalInsideBorderChar = '|'; - private $crossingChar = '+'; - private $crossingTopRightChar = '+'; - private $crossingTopMidChar = '+'; - private $crossingTopLeftChar = '+'; - private $crossingMidRightChar = '+'; - private $crossingBottomRightChar = '+'; - private $crossingBottomMidChar = '+'; - private $crossingBottomLeftChar = '+'; - private $crossingMidLeftChar = '+'; - private $crossingTopLeftBottomChar = '+'; - private $crossingTopMidBottomChar = '+'; - private $crossingTopRightBottomChar = '+'; - private $headerTitleFormat = ' %s '; - private $footerTitleFormat = ' %s '; - private $cellHeaderFormat = '%s'; - private $cellRowFormat = '%s'; - private $cellRowContentFormat = ' %s '; - private $borderFormat = '%s'; - private $padType = \STR_PAD_RIGHT; + private string $paddingChar = ' '; + private string $horizontalOutsideBorderChar = '-'; + private string $horizontalInsideBorderChar = '-'; + private string $verticalOutsideBorderChar = '|'; + private string $verticalInsideBorderChar = '|'; + private string $crossingChar = '+'; + private string $crossingTopRightChar = '+'; + private string $crossingTopMidChar = '+'; + private string $crossingTopLeftChar = '+'; + private string $crossingMidRightChar = '+'; + private string $crossingBottomRightChar = '+'; + private string $crossingBottomMidChar = '+'; + private string $crossingBottomLeftChar = '+'; + private string $crossingMidLeftChar = '+'; + private string $crossingTopLeftBottomChar = '+'; + private string $crossingTopMidBottomChar = '+'; + private string $crossingTopRightBottomChar = '+'; + private string $headerTitleFormat = ' %s '; + private string $footerTitleFormat = ' %s '; + private string $cellHeaderFormat = '%s'; + private string $cellRowFormat = '%s'; + private string $cellRowContentFormat = ' %s '; + private string $borderFormat = '%s'; + private int $padType = \STR_PAD_RIGHT; /** * Sets padding character, used for cell padding. * * @return $this */ - public function setPaddingChar(string $paddingChar) + public function setPaddingChar(string $paddingChar): static { if (!$paddingChar) { throw new LogicException('The padding char must not be empty.'); @@ -66,10 +66,8 @@ class TableStyle /** * Gets padding character, used for cell padding. - * - * @return string */ - public function getPaddingChar() + public function getPaddingChar(): string { return $this->paddingChar; } @@ -90,7 +88,7 @@ class TableStyle * * @return $this */ - public function setHorizontalBorderChars(string $outside, string $inside = null): self + public function setHorizontalBorderChars(string $outside, string $inside = null): static { $this->horizontalOutsideBorderChar = $outside; $this->horizontalInsideBorderChar = $inside ?? $outside; @@ -115,7 +113,7 @@ class TableStyle * * @return $this */ - public function setVerticalBorderChars(string $outside, string $inside = null): self + public function setVerticalBorderChars(string $outside, string $inside = null): static { $this->verticalOutsideBorderChar = $outside; $this->verticalInsideBorderChar = $inside ?? $outside; @@ -169,7 +167,7 @@ class TableStyle * * @return $this */ - public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self + public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): static { $this->crossingChar = $cross; $this->crossingTopLeftChar = $topLeft; @@ -199,10 +197,8 @@ class TableStyle /** * Gets crossing character. - * - * @return string */ - public function getCrossingChar() + public function getCrossingChar(): string { return $this->crossingChar; } @@ -235,7 +231,7 @@ class TableStyle * * @return $this */ - public function setCellHeaderFormat(string $cellHeaderFormat) + public function setCellHeaderFormat(string $cellHeaderFormat): static { $this->cellHeaderFormat = $cellHeaderFormat; @@ -244,10 +240,8 @@ class TableStyle /** * Gets header cell format. - * - * @return string */ - public function getCellHeaderFormat() + public function getCellHeaderFormat(): string { return $this->cellHeaderFormat; } @@ -257,7 +251,7 @@ class TableStyle * * @return $this */ - public function setCellRowFormat(string $cellRowFormat) + public function setCellRowFormat(string $cellRowFormat): static { $this->cellRowFormat = $cellRowFormat; @@ -266,10 +260,8 @@ class TableStyle /** * Gets row cell format. - * - * @return string */ - public function getCellRowFormat() + public function getCellRowFormat(): string { return $this->cellRowFormat; } @@ -279,7 +271,7 @@ class TableStyle * * @return $this */ - public function setCellRowContentFormat(string $cellRowContentFormat) + public function setCellRowContentFormat(string $cellRowContentFormat): static { $this->cellRowContentFormat = $cellRowContentFormat; @@ -288,10 +280,8 @@ class TableStyle /** * Gets row cell content format. - * - * @return string */ - public function getCellRowContentFormat() + public function getCellRowContentFormat(): string { return $this->cellRowContentFormat; } @@ -301,7 +291,7 @@ class TableStyle * * @return $this */ - public function setBorderFormat(string $borderFormat) + public function setBorderFormat(string $borderFormat): static { $this->borderFormat = $borderFormat; @@ -310,10 +300,8 @@ class TableStyle /** * Gets table border format. - * - * @return string */ - public function getBorderFormat() + public function getBorderFormat(): string { return $this->borderFormat; } @@ -323,7 +311,7 @@ class TableStyle * * @return $this */ - public function setPadType(int $padType) + public function setPadType(int $padType): static { if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) { throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); @@ -336,10 +324,8 @@ class TableStyle /** * Gets cell padding type. - * - * @return int */ - public function getPadType() + public function getPadType(): int { return $this->padType; } @@ -352,7 +338,7 @@ class TableStyle /** * @return $this */ - public function setHeaderTitleFormat(string $format): self + public function setHeaderTitleFormat(string $format): static { $this->headerTitleFormat = $format; @@ -367,7 +353,7 @@ class TableStyle /** * @return $this */ - public function setFooterTitleFormat(string $format): self + public function setFooterTitleFormat(string $format): static { $this->footerTitleFormat = $format; diff --git a/lib/symfony/console/Input/ArgvInput.php b/lib/symfony/console/Input/ArgvInput.php index 675b9ef58..59f9217ec 100644 --- a/lib/symfony/console/Input/ArgvInput.php +++ b/lib/symfony/console/Input/ArgvInput.php @@ -40,12 +40,12 @@ use Symfony\Component\Console\Exception\RuntimeException; */ class ArgvInput extends Input { - private $tokens; - private $parsed; + private array $tokens; + private array $parsed; public function __construct(array $argv = null, InputDefinition $definition = null) { - $argv = $argv ?? $_SERVER['argv'] ?? []; + $argv ??= $_SERVER['argv'] ?? []; // strip the application name array_shift($argv); @@ -55,13 +55,16 @@ class ArgvInput extends Input parent::__construct($definition); } + /** + * @return void + */ protected function setTokens(array $tokens) { $this->tokens = $tokens; } /** - * {@inheritdoc} + * @return void */ protected function parse() { @@ -92,7 +95,7 @@ class ArgvInput extends Input /** * Parses a short option. */ - private function parseShortOption(string $token) + private function parseShortOption(string $token): void { $name = substr($token, 1); @@ -113,7 +116,7 @@ class ArgvInput extends Input * * @throws RuntimeException When option given doesn't exist */ - private function parseShortOptionSet(string $name) + private function parseShortOptionSet(string $name): void { $len = \strlen($name); for ($i = 0; $i < $len; ++$i) { @@ -136,7 +139,7 @@ class ArgvInput extends Input /** * Parses a long option. */ - private function parseLongOption(string $token) + private function parseLongOption(string $token): void { $name = substr($token, 2); @@ -155,7 +158,7 @@ class ArgvInput extends Input * * @throws RuntimeException When too many arguments are given */ - private function parseArgument(string $token) + private function parseArgument(string $token): void { $c = \count($this->arguments); @@ -199,7 +202,7 @@ class ArgvInput extends Input * * @throws RuntimeException When option given doesn't exist */ - private function addShortOption(string $shortcut, $value) + private function addShortOption(string $shortcut, mixed $value): void { if (!$this->definition->hasShortcut($shortcut)) { throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); @@ -213,7 +216,7 @@ class ArgvInput extends Input * * @throws RuntimeException When option given doesn't exist */ - private function addLongOption(string $name, $value) + private function addLongOption(string $name, mixed $value): void { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { @@ -263,10 +266,7 @@ class ArgvInput extends Input } } - /** - * {@inheritdoc} - */ - public function getFirstArgument() + public function getFirstArgument(): ?string { $isOption = false; foreach ($this->tokens as $i => $token) { @@ -298,10 +298,7 @@ class ArgvInput extends Input return null; } - /** - * {@inheritdoc} - */ - public function hasParameterOption($values, bool $onlyParams = false) + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool { $values = (array) $values; @@ -323,10 +320,7 @@ class ArgvInput extends Input return false; } - /** - * {@inheritdoc} - */ - public function getParameterOption($values, $default = false, bool $onlyParams = false) + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed { $values = (array) $values; $tokens = $this->tokens; @@ -356,10 +350,8 @@ class ArgvInput extends Input /** * Returns a stringified representation of the args passed to the command. - * - * @return string */ - public function __toString() + public function __toString(): string { $tokens = array_map(function ($token) { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { diff --git a/lib/symfony/console/Input/ArrayInput.php b/lib/symfony/console/Input/ArrayInput.php index c65161484..355de61dd 100644 --- a/lib/symfony/console/Input/ArrayInput.php +++ b/lib/symfony/console/Input/ArrayInput.php @@ -25,7 +25,7 @@ use Symfony\Component\Console\Exception\InvalidOptionException; */ class ArrayInput extends Input { - private $parameters; + private array $parameters; public function __construct(array $parameters, InputDefinition $definition = null) { @@ -34,10 +34,7 @@ class ArrayInput extends Input parent::__construct($definition); } - /** - * {@inheritdoc} - */ - public function getFirstArgument() + public function getFirstArgument(): ?string { foreach ($this->parameters as $param => $value) { if ($param && \is_string($param) && '-' === $param[0]) { @@ -50,10 +47,7 @@ class ArrayInput extends Input return null; } - /** - * {@inheritdoc} - */ - public function hasParameterOption($values, bool $onlyParams = false) + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool { $values = (array) $values; @@ -74,10 +68,7 @@ class ArrayInput extends Input return false; } - /** - * {@inheritdoc} - */ - public function getParameterOption($values, $default = false, bool $onlyParams = false) + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed { $values = (array) $values; @@ -100,10 +91,8 @@ class ArrayInput extends Input /** * Returns a stringified representation of the args passed to the command. - * - * @return string */ - public function __toString() + public function __toString(): string { $params = []; foreach ($this->parameters as $param => $val) { @@ -117,7 +106,7 @@ class ArrayInput extends Input $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : ''); } } else { - $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val); + $params[] = \is_array($val) ? implode(' ', array_map($this->escapeToken(...), $val)) : $this->escapeToken($val); } } @@ -125,7 +114,7 @@ class ArrayInput extends Input } /** - * {@inheritdoc} + * @return void */ protected function parse() { @@ -148,7 +137,7 @@ class ArrayInput extends Input * * @throws InvalidOptionException When option given doesn't exist */ - private function addShortOption(string $shortcut, $value) + private function addShortOption(string $shortcut, mixed $value): void { if (!$this->definition->hasShortcut($shortcut)) { throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); @@ -163,7 +152,7 @@ class ArrayInput extends Input * @throws InvalidOptionException When option given doesn't exist * @throws InvalidOptionException When a required value is missing */ - private function addLongOption(string $name, $value) + private function addLongOption(string $name, mixed $value): void { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { @@ -194,12 +183,9 @@ class ArrayInput extends Input /** * Adds an argument value. * - * @param string|int $name The argument name - * @param mixed $value The value for the argument - * * @throws InvalidArgumentException When argument given doesn't exist */ - private function addArgument($name, $value) + private function addArgument(string|int $name, mixed $value): void { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); diff --git a/lib/symfony/console/Input/Input.php b/lib/symfony/console/Input/Input.php index d37460ed3..c7959a6ce 100644 --- a/lib/symfony/console/Input/Input.php +++ b/lib/symfony/console/Input/Input.php @@ -28,6 +28,7 @@ use Symfony\Component\Console\Exception\RuntimeException; abstract class Input implements InputInterface, StreamableInputInterface { protected $definition; + /** @var resource */ protected $stream; protected $options = []; protected $arguments = []; @@ -44,7 +45,7 @@ abstract class Input implements InputInterface, StreamableInputInterface } /** - * {@inheritdoc} + * @return void */ public function bind(InputDefinition $definition) { @@ -57,54 +58,45 @@ abstract class Input implements InputInterface, StreamableInputInterface /** * Processes command line arguments. + * + * @return void */ abstract protected function parse(); /** - * {@inheritdoc} + * @return void */ public function validate() { $definition = $this->definition; $givenArguments = $this->arguments; - $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { - return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); - }); + $missingArguments = array_filter(array_keys($definition->getArguments()), fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired()); if (\count($missingArguments) > 0) { throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); } } - /** - * {@inheritdoc} - */ - public function isInteractive() + public function isInteractive(): bool { return $this->interactive; } /** - * {@inheritdoc} + * @return void */ public function setInteractive(bool $interactive) { $this->interactive = $interactive; } - /** - * {@inheritdoc} - */ - public function getArguments() + public function getArguments(): array { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } - /** - * {@inheritdoc} - */ - public function getArgument(string $name) + public function getArgument(string $name): mixed { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); @@ -114,9 +106,9 @@ abstract class Input implements InputInterface, StreamableInputInterface } /** - * {@inheritdoc} + * @return void */ - public function setArgument(string $name, $value) + public function setArgument(string $name, mixed $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); @@ -125,26 +117,17 @@ abstract class Input implements InputInterface, StreamableInputInterface $this->arguments[$name] = $value; } - /** - * {@inheritdoc} - */ - public function hasArgument(string $name) + public function hasArgument(string $name): bool { return $this->definition->hasArgument($name); } - /** - * {@inheritdoc} - */ - public function getOptions() + public function getOptions(): array { return array_merge($this->definition->getOptionDefaults(), $this->options); } - /** - * {@inheritdoc} - */ - public function getOption(string $name) + public function getOption(string $name): mixed { if ($this->definition->hasNegation($name)) { if (null === $value = $this->getOption($this->definition->negationToName($name))) { @@ -162,9 +145,9 @@ abstract class Input implements InputInterface, StreamableInputInterface } /** - * {@inheritdoc} + * @return void */ - public function setOption(string $name, $value) + public function setOption(string $name, mixed $value) { if ($this->definition->hasNegation($name)) { $this->options[$this->definition->negationToName($name)] = !$value; @@ -177,26 +160,23 @@ abstract class Input implements InputInterface, StreamableInputInterface $this->options[$name] = $value; } - /** - * {@inheritdoc} - */ - public function hasOption(string $name) + public function hasOption(string $name): bool { return $this->definition->hasOption($name) || $this->definition->hasNegation($name); } /** * Escapes a token through escapeshellarg if it contains unsafe chars. - * - * @return string */ - public function escapeToken(string $token) + public function escapeToken(string $token): string { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } /** - * {@inheritdoc} + * @param resource $stream + * + * @return void */ public function setStream($stream) { @@ -204,7 +184,7 @@ abstract class Input implements InputInterface, StreamableInputInterface } /** - * {@inheritdoc} + * @return resource */ public function getStream() { diff --git a/lib/symfony/console/Input/InputArgument.php b/lib/symfony/console/Input/InputArgument.php index ecfcdad58..5cb151488 100644 --- a/lib/symfony/console/Input/InputArgument.php +++ b/lib/symfony/console/Input/InputArgument.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Console\Input; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; @@ -25,20 +29,22 @@ class InputArgument public const OPTIONAL = 2; public const IS_ARRAY = 4; - private $name; - private $mode; - private $default; - private $description; + private string $name; + private int $mode; + private string|int|bool|array|null|float $default; + private array|\Closure $suggestedValues; + private string $description; /** - * @param string $name The argument name - * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL - * @param string $description A description text - * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) + * @param string $name The argument name + * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY + * @param string $description A description text + * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @throws InvalidArgumentException When argument mode is not valid */ - public function __construct(string $name, int $mode = null, string $description = '', $default = null) + public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null, \Closure|array $suggestedValues = []) { if (null === $mode) { $mode = self::OPTIONAL; @@ -49,16 +55,15 @@ class InputArgument $this->name = $name; $this->mode = $mode; $this->description = $description; + $this->suggestedValues = $suggestedValues; $this->setDefault($default); } /** * Returns the argument name. - * - * @return string */ - public function getName() + public function getName(): string { return $this->name; } @@ -68,7 +73,7 @@ class InputArgument * * @return bool true if parameter mode is self::REQUIRED, false otherwise */ - public function isRequired() + public function isRequired(): bool { return self::REQUIRED === (self::REQUIRED & $this->mode); } @@ -78,7 +83,7 @@ class InputArgument * * @return bool true if mode is self::IS_ARRAY, false otherwise */ - public function isArray() + public function isArray(): bool { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } @@ -86,12 +91,15 @@ class InputArgument /** * Sets the default value. * - * @param string|bool|int|float|array|null $default + * @return void * * @throws LogicException When incorrect default value is given */ - public function setDefault($default = null) + public function setDefault(string|bool|int|float|array $default = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if ($this->isRequired() && null !== $default) { throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } @@ -109,20 +117,37 @@ class InputArgument /** * Returns the default value. - * - * @return string|bool|int|float|array|null */ - public function getDefault() + public function getDefault(): string|bool|int|float|array|null { return $this->default; } + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input))) { + throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } + /** * Returns the description text. - * - * @return string */ - public function getDescription() + public function getDescription(): string { return $this->description; } diff --git a/lib/symfony/console/Input/InputAwareInterface.php b/lib/symfony/console/Input/InputAwareInterface.php index 5a288de5d..0ad27b455 100644 --- a/lib/symfony/console/Input/InputAwareInterface.php +++ b/lib/symfony/console/Input/InputAwareInterface.php @@ -21,6 +21,8 @@ interface InputAwareInterface { /** * Sets the Console Input. + * + * @return void */ public function setInput(InputInterface $input); } diff --git a/lib/symfony/console/Input/InputDefinition.php b/lib/symfony/console/Input/InputDefinition.php index 11f704f0e..b7162d770 100644 --- a/lib/symfony/console/Input/InputDefinition.php +++ b/lib/symfony/console/Input/InputDefinition.php @@ -28,13 +28,13 @@ use Symfony\Component\Console\Exception\LogicException; */ class InputDefinition { - private $arguments; - private $requiredCount; - private $lastArrayArgument; - private $lastOptionalArgument; - private $options; - private $negations; - private $shortcuts; + private array $arguments = []; + private int $requiredCount = 0; + private ?InputArgument $lastArrayArgument = null; + private ?InputArgument $lastOptionalArgument = null; + private array $options = []; + private array $negations = []; + private array $shortcuts = []; /** * @param array $definition An array of InputArgument and InputOption instance @@ -46,6 +46,8 @@ class InputDefinition /** * Sets the definition of the input. + * + * @return void */ public function setDefinition(array $definition) { @@ -67,6 +69,8 @@ class InputDefinition * Sets the InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects + * + * @return void */ public function setArguments(array $arguments = []) { @@ -81,6 +85,8 @@ class InputDefinition * Adds an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects + * + * @return void */ public function addArguments(?array $arguments = []) { @@ -92,6 +98,8 @@ class InputDefinition } /** + * @return void + * * @throws LogicException When incorrect argument is given */ public function addArgument(InputArgument $argument) @@ -124,13 +132,9 @@ class InputDefinition /** * Returns an InputArgument by name or by position. * - * @param string|int $name The InputArgument name or position - * - * @return InputArgument - * * @throws InvalidArgumentException When argument given doesn't exist */ - public function getArgument($name) + public function getArgument(string|int $name): InputArgument { if (!$this->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); @@ -143,12 +147,8 @@ class InputDefinition /** * Returns true if an InputArgument object exists by name or position. - * - * @param string|int $name The InputArgument name or position - * - * @return bool */ - public function hasArgument($name) + public function hasArgument(string|int $name): bool { $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; @@ -160,27 +160,23 @@ class InputDefinition * * @return InputArgument[] */ - public function getArguments() + public function getArguments(): array { return $this->arguments; } /** * Returns the number of InputArguments. - * - * @return int */ - public function getArgumentCount() + public function getArgumentCount(): int { return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments); } /** * Returns the number of required InputArguments. - * - * @return int */ - public function getArgumentRequiredCount() + public function getArgumentRequiredCount(): int { return $this->requiredCount; } @@ -188,7 +184,7 @@ class InputDefinition /** * @return array */ - public function getArgumentDefaults() + public function getArgumentDefaults(): array { $values = []; foreach ($this->arguments as $argument) { @@ -202,6 +198,8 @@ class InputDefinition * Sets the InputOption objects. * * @param InputOption[] $options An array of InputOption objects + * + * @return void */ public function setOptions(array $options = []) { @@ -215,6 +213,8 @@ class InputDefinition * Adds an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects + * + * @return void */ public function addOptions(array $options = []) { @@ -224,6 +224,8 @@ class InputDefinition } /** + * @return void + * * @throws LogicException When option given already exist */ public function addOption(InputOption $option) @@ -262,11 +264,9 @@ class InputDefinition /** * Returns an InputOption by name. * - * @return InputOption - * * @throws InvalidArgumentException When option given doesn't exist */ - public function getOption(string $name) + public function getOption(string $name): InputOption { if (!$this->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); @@ -280,10 +280,8 @@ class InputDefinition * * This method can't be used to check if the user included the option when * executing the command (use getOption() instead). - * - * @return bool */ - public function hasOption(string $name) + public function hasOption(string $name): bool { return isset($this->options[$name]); } @@ -293,17 +291,15 @@ class InputDefinition * * @return InputOption[] */ - public function getOptions() + public function getOptions(): array { return $this->options; } /** * Returns true if an InputOption object exists by shortcut. - * - * @return bool */ - public function hasShortcut(string $name) + public function hasShortcut(string $name): bool { return isset($this->shortcuts[$name]); } @@ -318,10 +314,8 @@ class InputDefinition /** * Gets an InputOption by shortcut. - * - * @return InputOption */ - public function getOptionForShortcut(string $shortcut) + public function getOptionForShortcut(string $shortcut): InputOption { return $this->getOption($this->shortcutToName($shortcut)); } @@ -329,7 +323,7 @@ class InputDefinition /** * @return array */ - public function getOptionDefaults() + public function getOptionDefaults(): array { $values = []; foreach ($this->options as $option) { @@ -373,10 +367,8 @@ class InputDefinition /** * Gets the synopsis. - * - * @return string */ - public function getSynopsis(bool $short = false) + public function getSynopsis(bool $short = false): string { $elements = []; diff --git a/lib/symfony/console/Input/InputInterface.php b/lib/symfony/console/Input/InputInterface.php index 628b6037a..aaed5fd01 100644 --- a/lib/symfony/console/Input/InputInterface.php +++ b/lib/symfony/console/Input/InputInterface.php @@ -18,15 +18,16 @@ use Symfony\Component\Console\Exception\RuntimeException; * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier + * + * @method string __toString() Returns a stringified representation of the args passed to the command. + * InputArguments MUST be escaped as well as the InputOption values passed to the command. */ interface InputInterface { /** * Returns the first argument from the raw parameters (not parsed). - * - * @return string|null */ - public function getFirstArgument(); + public function getFirstArgument(): ?string; /** * Returns true if the raw parameters (not parsed) contain a value. @@ -38,10 +39,8 @@ interface InputInterface * * @param string|array $values The values to look for in the raw parameters (can be an array) * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal - * - * @return bool */ - public function hasParameterOption($values, bool $onlyParams = false); + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool; /** * Returns the value of a raw option (not parsed). @@ -57,11 +56,13 @@ interface InputInterface * * @return mixed */ - public function getParameterOption($values, $default = false, bool $onlyParams = false); + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false); /** * Binds the current Input instance with the given arguments and options. * + * @return void + * * @throws RuntimeException */ public function bind(InputDefinition $definition); @@ -69,6 +70,8 @@ interface InputInterface /** * Validates the input. * + * @return void + * * @throws RuntimeException When not enough arguments are given */ public function validate(); @@ -78,7 +81,7 @@ interface InputInterface * * @return array */ - public function getArguments(); + public function getArguments(): array; /** * Returns the argument value for a given argument name. @@ -92,25 +95,23 @@ interface InputInterface /** * Sets an argument value by name. * - * @param mixed $value The argument value + * @return void * * @throws InvalidArgumentException When argument given doesn't exist */ - public function setArgument(string $name, $value); + public function setArgument(string $name, mixed $value); /** * Returns true if an InputArgument object exists by name or position. - * - * @return bool */ - public function hasArgument(string $name); + public function hasArgument(string $name): bool; /** * Returns all the given options merged with the default values. * * @return array */ - public function getOptions(); + public function getOptions(): array; /** * Returns the option value for a given option name. @@ -124,28 +125,26 @@ interface InputInterface /** * Sets an option value by name. * - * @param mixed $value The option value + * @return void * * @throws InvalidArgumentException When option given doesn't exist */ - public function setOption(string $name, $value); + public function setOption(string $name, mixed $value); /** * Returns true if an InputOption object exists by name. - * - * @return bool */ - public function hasOption(string $name); + public function hasOption(string $name): bool; /** * Is this input means interactive? - * - * @return bool */ - public function isInteractive(); + public function isInteractive(): bool; /** * Sets the input interactivity. + * + * @return void */ public function setInteractive(bool $interactive); } diff --git a/lib/symfony/console/Input/InputOption.php b/lib/symfony/console/Input/InputOption.php index 2bec34fe1..fdf88dcc2 100644 --- a/lib/symfony/console/Input/InputOption.php +++ b/lib/symfony/console/Input/InputOption.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Console\Input; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; @@ -46,20 +50,22 @@ class InputOption */ public const VALUE_NEGATABLE = 16; - private $name; - private $shortcut; - private $mode; - private $default; - private $description; + private string $name; + private string|array|null $shortcut; + private int $mode; + private string|int|bool|array|null|float $default; + private array|\Closure $suggestedValues; + private string $description; /** - * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int|null $mode The option mode: One of the VALUE_* constants - * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @throws InvalidArgumentException If option mode is invalid or incompatible */ - public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) + public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null, array|\Closure $suggestedValues = []) { if (str_starts_with($name, '--')) { $name = substr($name, 2); @@ -96,7 +102,11 @@ class InputOption $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; + $this->suggestedValues = $suggestedValues; + if ($suggestedValues && !$this->acceptValue()) { + throw new LogicException('Cannot set suggested values if the option does not accept a value.'); + } if ($this->isArray() && !$this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } @@ -109,20 +119,16 @@ class InputOption /** * Returns the option shortcut. - * - * @return string|null */ - public function getShortcut() + public function getShortcut(): ?string { return $this->shortcut; } /** * Returns the option name. - * - * @return string */ - public function getName() + public function getName(): string { return $this->name; } @@ -132,7 +138,7 @@ class InputOption * * @return bool true if value mode is not self::VALUE_NONE, false otherwise */ - public function acceptValue() + public function acceptValue(): bool { return $this->isValueRequired() || $this->isValueOptional(); } @@ -142,7 +148,7 @@ class InputOption * * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise */ - public function isValueRequired() + public function isValueRequired(): bool { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } @@ -152,7 +158,7 @@ class InputOption * * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise */ - public function isValueOptional() + public function isValueOptional(): bool { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } @@ -162,7 +168,7 @@ class InputOption * * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise */ - public function isArray() + public function isArray(): bool { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } @@ -173,10 +179,13 @@ class InputOption } /** - * @param string|bool|int|float|array|null $default + * @return void */ - public function setDefault($default = null) + public function setDefault(string|bool|int|float|array $default = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } @@ -194,30 +203,45 @@ class InputOption /** * Returns the default value. - * - * @return string|bool|int|float|array|null */ - public function getDefault() + public function getDefault(): string|bool|int|float|array|null { return $this->default; } /** * Returns the description text. - * - * @return string */ - public function getDescription() + public function getDescription(): string { return $this->description; } + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input))) { + throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } + /** * Checks whether the given option equals this one. - * - * @return bool */ - public function equals(self $option) + public function equals(self $option): bool { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() diff --git a/lib/symfony/console/Input/StreamableInputInterface.php b/lib/symfony/console/Input/StreamableInputInterface.php index d7e462f24..4b95fcb11 100644 --- a/lib/symfony/console/Input/StreamableInputInterface.php +++ b/lib/symfony/console/Input/StreamableInputInterface.php @@ -25,6 +25,8 @@ interface StreamableInputInterface extends InputInterface * This is mainly useful for testing purpose. * * @param resource $stream The input stream + * + * @return void */ public function setStream($stream); diff --git a/lib/symfony/console/Input/StringInput.php b/lib/symfony/console/Input/StringInput.php index 56bb66cbf..82bd21440 100644 --- a/lib/symfony/console/Input/StringInput.php +++ b/lib/symfony/console/Input/StringInput.php @@ -24,6 +24,9 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; */ class StringInput extends ArgvInput { + /** + * @deprecated since Symfony 6.1 + */ public const REGEX_STRING = '([^\s]+?)(?:\s|(? OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, @@ -40,7 +40,7 @@ class ConsoleLogger extends AbstractLogger LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, ]; - private $formatLevelMap = [ + private array $formatLevelMap = [ LogLevel::EMERGENCY => self::ERROR, LogLevel::ALERT => self::ERROR, LogLevel::CRITICAL => self::ERROR, @@ -50,7 +50,7 @@ class ConsoleLogger extends AbstractLogger LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::INFO, ]; - private $errored = false; + private bool $errored = false; public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) { @@ -59,12 +59,7 @@ class ConsoleLogger extends AbstractLogger $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } - /** - * {@inheritdoc} - * - * @return void - */ - public function log($level, $message, array $context = []) + public function log($level, $message, array $context = []): void { if (!isset($this->verbosityLevelMap[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); @@ -89,10 +84,8 @@ class ConsoleLogger extends AbstractLogger /** * Returns true when any messages have been logged at error levels. - * - * @return bool */ - public function hasErrored() + public function hasErrored(): bool { return $this->errored; } @@ -110,12 +103,12 @@ class ConsoleLogger extends AbstractLogger $replacements = []; foreach ($context as $key => $val) { - if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + if (null === $val || \is_scalar($val) || $val instanceof \Stringable) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof \DateTimeInterface) { - $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); + $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339); } elseif (\is_object($val)) { - $replacements["{{$key}}"] = '[object '.\get_class($val).']'; + $replacements["{{$key}}"] = '[object '.$val::class.']'; } else { $replacements["{{$key}}"] = '['.\gettype($val).']'; } diff --git a/lib/symfony/console/Messenger/RunCommandContext.php b/lib/symfony/console/Messenger/RunCommandContext.php new file mode 100644 index 000000000..2ee5415c6 --- /dev/null +++ b/lib/symfony/console/Messenger/RunCommandContext.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +/** + * @author Kevin Bond + */ +final class RunCommandContext +{ + public function __construct( + public readonly RunCommandMessage $message, + public readonly int $exitCode, + public readonly string $output, + ) { + } +} diff --git a/lib/symfony/console/Messenger/RunCommandMessage.php b/lib/symfony/console/Messenger/RunCommandMessage.php new file mode 100644 index 000000000..b530c438c --- /dev/null +++ b/lib/symfony/console/Messenger/RunCommandMessage.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +use Symfony\Component\Console\Exception\RunCommandFailedException; + +/** + * @author Kevin Bond + */ +class RunCommandMessage implements \Stringable +{ + /** + * @param bool $throwOnFailure If the command has a non-zero exit code, throw {@see RunCommandFailedException} + * @param bool $catchExceptions @see Application::setCatchExceptions() + */ + public function __construct( + public readonly string $input, + public readonly bool $throwOnFailure = true, + public readonly bool $catchExceptions = false, + ) { + } + + public function __toString(): string + { + return $this->input; + } +} diff --git a/lib/symfony/console/Messenger/RunCommandMessageHandler.php b/lib/symfony/console/Messenger/RunCommandMessageHandler.php new file mode 100644 index 000000000..14f9c1764 --- /dev/null +++ b/lib/symfony/console/Messenger/RunCommandMessageHandler.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RunCommandFailedException; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * @author Kevin Bond + */ +final class RunCommandMessageHandler +{ + public function __construct(private readonly Application $application) + { + } + + public function __invoke(RunCommandMessage $message): RunCommandContext + { + $input = new StringInput($message->input); + $output = new BufferedOutput(); + + $this->application->setCatchExceptions($message->catchExceptions); + + try { + $exitCode = $this->application->run($input, $output); + } catch (\Throwable $e) { + throw new RunCommandFailedException($e, new RunCommandContext($message, Command::FAILURE, $output->fetch())); + } + + if ($message->throwOnFailure && Command::SUCCESS !== $exitCode) { + throw new RunCommandFailedException(sprintf('Command "%s" exited with code "%s".', $message->input, $exitCode), new RunCommandContext($message, $exitCode, $output->fetch())); + } + + return new RunCommandContext($message, $exitCode, $output->fetch()); + } +} diff --git a/lib/symfony/console/Output/AnsiColorMode.php b/lib/symfony/console/Output/AnsiColorMode.php new file mode 100644 index 000000000..5f9f744fe --- /dev/null +++ b/lib/symfony/console/Output/AnsiColorMode.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Fabien Potencier + * @author Julien Boudry + */ +enum AnsiColorMode +{ + /* + * Classical 4-bit Ansi colors, including 8 classical colors and 8 bright color. Output syntax is "ESC[${foreGroundColorcode};${backGroundColorcode}m" + * Must be compatible with all terminals and it's the minimal version supported. + */ + case Ansi4; + + /* + * 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" + * Should be compatible with most terminals. + */ + case Ansi8; + + /* + * 24-bit Ansi colors (RGB). + * Output syntax is: "ESC[38;2;${foreGroundColorcodeRed};${foreGroundColorcodeGreen};${foreGroundColorcodeBlue};48;2;${backGroundColorcodeRed};${backGroundColorcodeGreen};${backGroundColorcodeBlue}m" + * May be compatible with many modern terminals. + */ + case Ansi24; + + /** + * Converts an RGB hexadecimal color to the corresponding Ansi code. + */ + public function convertFromHexToAnsiColorCode(string $hexColor): string + { + $hexColor = str_replace('#', '', $hexColor); + + if (3 === \strlen($hexColor)) { + $hexColor = $hexColor[0].$hexColor[0].$hexColor[1].$hexColor[1].$hexColor[2].$hexColor[2]; + } + + if (6 !== \strlen($hexColor)) { + throw new InvalidArgumentException(sprintf('Invalid "#%s" color.', $hexColor)); + } + + $color = hexdec($hexColor); + + $r = ($color >> 16) & 255; + $g = ($color >> 8) & 255; + $b = $color & 255; + + return match ($this) { + self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b), + self::Ansi8 => '8;5;'.((string) $this->convertFromRGB($r, $g, $b)), + self::Ansi24 => sprintf('8;2;%d;%d;%d', $r, $g, $b) + }; + } + + private function convertFromRGB(int $r, int $g, int $b): int + { + return match ($this) { + self::Ansi4 => $this->degradeHexColorToAnsi4($r, $g, $b), + self::Ansi8 => $this->degradeHexColorToAnsi8($r, $g, $b), + default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}.") + }; + } + + private function degradeHexColorToAnsi4(int $r, int $g, int $b): int + { + return round($b / 255) << 2 | (round($g / 255) << 1) | round($r / 255); + } + + /** + * Inspired from https://github.com/ajalt/colormath/blob/e464e0da1b014976736cf97250063248fc77b8e7/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/Ansi256.kt code (MIT license). + */ + private function degradeHexColorToAnsi8(int $r, int $g, int $b): int + { + if ($r === $g && $g === $b) { + if ($r < 8) { + return 16; + } + + if ($r > 248) { + return 231; + } + + return (int) round(($r - 8) / 247 * 24) + 232; + } else { + return 16 + + (36 * (int) round($r / 255 * 5)) + + (6 * (int) round($g / 255 * 5)) + + (int) round($b / 255 * 5); + } + } +} diff --git a/lib/symfony/console/Output/BufferedOutput.php b/lib/symfony/console/Output/BufferedOutput.php index d37c6e323..ef5099bfd 100644 --- a/lib/symfony/console/Output/BufferedOutput.php +++ b/lib/symfony/console/Output/BufferedOutput.php @@ -16,14 +16,12 @@ namespace Symfony\Component\Console\Output; */ class BufferedOutput extends Output { - private $buffer = ''; + private string $buffer = ''; /** * Empties buffer and returns its content. - * - * @return string */ - public function fetch() + public function fetch(): string { $content = $this->buffer; $this->buffer = ''; @@ -32,7 +30,7 @@ class BufferedOutput extends Output } /** - * {@inheritdoc} + * @return void */ protected function doWrite(string $message, bool $newline) { diff --git a/lib/symfony/console/Output/ConsoleOutput.php b/lib/symfony/console/Output/ConsoleOutput.php index f19f9ebf4..c1eb7cd14 100644 --- a/lib/symfony/console/Output/ConsoleOutput.php +++ b/lib/symfony/console/Output/ConsoleOutput.php @@ -29,8 +29,8 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface; */ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface { - private $stderr; - private $consoleSectionOutputs = []; + private OutputInterface $stderr; + private array $consoleSectionOutputs = []; /** * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) @@ -65,7 +65,7 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface } /** - * {@inheritdoc} + * @return void */ public function setDecorated(bool $decorated) { @@ -74,7 +74,7 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface } /** - * {@inheritdoc} + * @return void */ public function setFormatter(OutputFormatterInterface $formatter) { @@ -83,7 +83,7 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface } /** - * {@inheritdoc} + * @return void */ public function setVerbosity(int $level) { @@ -91,16 +91,13 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface $this->stderr->setVerbosity($level); } - /** - * {@inheritdoc} - */ - public function getErrorOutput() + public function getErrorOutput(): OutputInterface { return $this->stderr; } /** - * {@inheritdoc} + * @return void */ public function setErrorOutput(OutputInterface $error) { @@ -110,10 +107,8 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface /** * Returns true if current environment supports writing console output to * STDOUT. - * - * @return bool */ - protected function hasStdoutSupport() + protected function hasStdoutSupport(): bool { return false === $this->isRunningOS400(); } @@ -121,10 +116,8 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface /** * Returns true if current environment supports writing console output to * STDERR. - * - * @return bool */ - protected function hasStderrSupport() + protected function hasStderrSupport(): bool { return false === $this->isRunningOS400(); } diff --git a/lib/symfony/console/Output/ConsoleOutputInterface.php b/lib/symfony/console/Output/ConsoleOutputInterface.php index 6b6635f58..9c0049c8f 100644 --- a/lib/symfony/console/Output/ConsoleOutputInterface.php +++ b/lib/symfony/console/Output/ConsoleOutputInterface.php @@ -21,11 +21,12 @@ interface ConsoleOutputInterface extends OutputInterface { /** * Gets the OutputInterface for errors. - * - * @return OutputInterface */ - public function getErrorOutput(); + public function getErrorOutput(): OutputInterface; + /** + * @return void + */ public function setErrorOutput(OutputInterface $error); public function section(): ConsoleSectionOutput; diff --git a/lib/symfony/console/Output/ConsoleSectionOutput.php b/lib/symfony/console/Output/ConsoleSectionOutput.php index 8f1649758..21c4a44a8 100644 --- a/lib/symfony/console/Output/ConsoleSectionOutput.php +++ b/lib/symfony/console/Output/ConsoleSectionOutput.php @@ -21,10 +21,11 @@ use Symfony\Component\Console\Terminal; */ class ConsoleSectionOutput extends StreamOutput { - private $content = []; - private $lines = 0; - private $sections; - private $terminal; + private array $content = []; + private int $lines = 0; + private array $sections; + private Terminal $terminal; + private int $maxHeight = 0; /** * @param resource $stream @@ -38,10 +39,29 @@ class ConsoleSectionOutput extends StreamOutput $this->terminal = new Terminal(); } + /** + * Defines a maximum number of lines for this section. + * + * When more lines are added, the section will automatically scroll to the + * end (i.e. remove the first lines to comply with the max height). + */ + public function setMaxHeight(int $maxHeight): void + { + // when changing max height, clear output of current section and redraw again with the new height + $previousMaxHeight = $this->maxHeight; + $this->maxHeight = $maxHeight; + $existingContent = $this->popStreamContentUntilCurrentSection($previousMaxHeight ? min($previousMaxHeight, $this->lines) : $this->lines); + + parent::doWrite($this->getVisibleContent(), false); + parent::doWrite($existingContent, false); + } + /** * 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 + * + * @return void */ public function clear(int $lines = null) { @@ -50,7 +70,7 @@ class ConsoleSectionOutput extends StreamOutput } if ($lines) { - array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content + array_splice($this->content, -$lines); } else { $lines = $this->lines; $this->content = []; @@ -58,15 +78,15 @@ class ConsoleSectionOutput extends StreamOutput $this->lines -= $lines; - parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); + parent::doWrite($this->popStreamContentUntilCurrentSection($this->maxHeight ? min($this->maxHeight, $lines) : $lines), false); } /** * Overwrites the previous output with a new message. * - * @param array|string $message + * @return void */ - public function overwrite($message) + public function overwrite(string|iterable $message) { $this->clear(); $this->writeln($message); @@ -77,34 +97,110 @@ class ConsoleSectionOutput extends StreamOutput return implode('', $this->content); } - /** - * @internal - */ - public function addContent(string $input) + public function getVisibleContent(): string { - foreach (explode(\PHP_EOL, $input) as $lineContent) { - $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; - $this->content[] = $lineContent; - $this->content[] = \PHP_EOL; + if (0 === $this->maxHeight) { + return $this->getContent(); } + + return implode('', \array_slice($this->content, -$this->maxHeight)); } /** - * {@inheritdoc} + * @internal + */ + public function addContent(string $input, bool $newline = true): int + { + $width = $this->terminal->getWidth(); + $lines = explode(\PHP_EOL, $input); + $linesAdded = 0; + $count = \count($lines) - 1; + foreach ($lines as $i => $lineContent) { + // re-add the line break (that has been removed in the above `explode()` for + // - every line that is not the last line + // - if $newline is required, also add it to the last line + if ($i < $count || $newline) { + $lineContent .= \PHP_EOL; + } + + // skip line if there is no text (or newline for that matter) + if ('' === $lineContent) { + continue; + } + + // For the first line, check if the previous line (last entry of `$this->content`) + // needs to be continued (i.e. does not end with a line break). + if (0 === $i + && (false !== $lastLine = end($this->content)) + && !str_ends_with($lastLine, \PHP_EOL) + ) { + // deduct the line count of the previous line + $this->lines -= (int) ceil($this->getDisplayLength($lastLine) / $width) ?: 1; + // concatenate previous and new line + $lineContent = $lastLine.$lineContent; + // replace last entry of `$this->content` with the new expanded line + array_splice($this->content, -1, 1, $lineContent); + } else { + // otherwise just add the new content + $this->content[] = $lineContent; + } + + $linesAdded += (int) ceil($this->getDisplayLength($lineContent) / $width) ?: 1; + } + + $this->lines += $linesAdded; + + return $linesAdded; + } + + /** + * @internal + */ + public function addNewLineOfInputSubmit(): void + { + $this->content[] = \PHP_EOL; + ++$this->lines; + } + + /** + * @return void */ protected function doWrite(string $message, bool $newline) { + // Simulate newline behavior for consistent output formatting, avoiding extra logic + if (!$newline && str_ends_with($message, \PHP_EOL)) { + $message = substr($message, 0, -\strlen(\PHP_EOL)); + $newline = true; + } + if (!$this->isDecorated()) { parent::doWrite($message, $newline); return; } - $erasedContent = $this->popStreamContentUntilCurrentSection(); + // Check if the previous line (last entry of `$this->content`) needs to be continued + // (i.e. does not end with a line break). In which case, it needs to be erased first. + $linesToClear = $deleteLastLine = ($lastLine = end($this->content) ?: '') && !str_ends_with($lastLine, \PHP_EOL) ? 1 : 0; - $this->addContent($message); + $linesAdded = $this->addContent($message, $newline); - parent::doWrite($message, true); + if ($lineOverflow = $this->maxHeight > 0 && $this->lines > $this->maxHeight) { + // on overflow, clear the whole section and redraw again (to remove the first lines) + $linesToClear = $this->maxHeight; + } + + $erasedContent = $this->popStreamContentUntilCurrentSection($linesToClear); + + if ($lineOverflow) { + // redraw existing lines of the section + $previousLinesOfSection = \array_slice($this->content, $this->lines - $this->maxHeight, $this->maxHeight - $linesAdded); + parent::doWrite(implode('', $previousLinesOfSection), false); + } + + // if the last line was removed, re-print its content together with the new content. + // otherwise, just print the new content. + parent::doWrite($deleteLastLine ? $lastLine.$message : $message, true); parent::doWrite($erasedContent, false); } @@ -122,8 +218,13 @@ class ConsoleSectionOutput extends StreamOutput break; } - $numberOfLinesToClear += $section->lines; - $erasedContent[] = $section->getContent(); + $numberOfLinesToClear += $section->maxHeight ? min($section->lines, $section->maxHeight) : $section->lines; + if ('' !== $sectionContent = $section->getVisibleContent()) { + if (!str_ends_with($sectionContent, \PHP_EOL)) { + $sectionContent .= \PHP_EOL; + } + $erasedContent[] = $sectionContent; + } } if ($numberOfLinesToClear > 0) { diff --git a/lib/symfony/console/Output/NullOutput.php b/lib/symfony/console/Output/NullOutput.php index 3bbe63ea0..f3aa15b1d 100644 --- a/lib/symfony/console/Output/NullOutput.php +++ b/lib/symfony/console/Output/NullOutput.php @@ -24,104 +24,80 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface; */ class NullOutput implements OutputInterface { - private $formatter; + private NullOutputFormatter $formatter; /** - * {@inheritdoc} + * @return void */ public function setFormatter(OutputFormatterInterface $formatter) { // do nothing } - /** - * {@inheritdoc} - */ - public function getFormatter() + public function getFormatter(): OutputFormatterInterface { - if ($this->formatter) { - return $this->formatter; - } // to comply with the interface we must return a OutputFormatterInterface - return $this->formatter = new NullOutputFormatter(); + return $this->formatter ??= new NullOutputFormatter(); } /** - * {@inheritdoc} + * @return void */ public function setDecorated(bool $decorated) { // do nothing } - /** - * {@inheritdoc} - */ - public function isDecorated() + public function isDecorated(): bool { return false; } /** - * {@inheritdoc} + * @return void */ public function setVerbosity(int $level) { // do nothing } - /** - * {@inheritdoc} - */ - public function getVerbosity() + public function getVerbosity(): int { return self::VERBOSITY_QUIET; } - /** - * {@inheritdoc} - */ - public function isQuiet() + public function isQuiet(): bool { return true; } - /** - * {@inheritdoc} - */ - public function isVerbose() + public function isVerbose(): bool + { + return false; + } + + public function isVeryVerbose(): bool + { + return false; + } + + public function isDebug(): bool { return false; } /** - * {@inheritdoc} + * @return void */ - public function isVeryVerbose() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function isDebug() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function writeln($messages, int $options = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) { // do nothing } /** - * {@inheritdoc} + * @return void */ - public function write($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 } diff --git a/lib/symfony/console/Output/Output.php b/lib/symfony/console/Output/Output.php index d7c5fb2d1..3a06311a8 100644 --- a/lib/symfony/console/Output/Output.php +++ b/lib/symfony/console/Output/Output.php @@ -29,8 +29,8 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface; */ abstract class Output implements OutputInterface { - private $verbosity; - private $formatter; + private int $verbosity; + private OutputFormatterInterface $formatter; /** * @param int|null $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) @@ -45,97 +45,76 @@ abstract class Output implements OutputInterface } /** - * {@inheritdoc} + * @return void */ public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } - /** - * {@inheritdoc} - */ - public function getFormatter() + public function getFormatter(): OutputFormatterInterface { return $this->formatter; } /** - * {@inheritdoc} + * @return void */ public function setDecorated(bool $decorated) { $this->formatter->setDecorated($decorated); } - /** - * {@inheritdoc} - */ - public function isDecorated() + public function isDecorated(): bool { return $this->formatter->isDecorated(); } /** - * {@inheritdoc} + * @return void */ public function setVerbosity(int $level) { $this->verbosity = $level; } - /** - * {@inheritdoc} - */ - public function getVerbosity() + public function getVerbosity(): int { return $this->verbosity; } - /** - * {@inheritdoc} - */ - public function isQuiet() + public function isQuiet(): bool { return self::VERBOSITY_QUIET === $this->verbosity; } - /** - * {@inheritdoc} - */ - public function isVerbose() + public function isVerbose(): bool { return self::VERBOSITY_VERBOSE <= $this->verbosity; } - /** - * {@inheritdoc} - */ - public function isVeryVerbose() + public function isVeryVerbose(): bool { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } - /** - * {@inheritdoc} - */ - public function isDebug() + public function isDebug(): bool { return self::VERBOSITY_DEBUG <= $this->verbosity; } /** - * {@inheritdoc} + * @return void */ - public function writeln($messages, int $options = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) { $this->write($messages, true, $options); } /** - * {@inheritdoc} + * @return void */ - public function write($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)) { $messages = [$messages]; @@ -169,6 +148,8 @@ abstract class Output implements OutputInterface /** * Writes a message to the output. + * + * @return void */ abstract protected function doWrite(string $message, bool $newline); } diff --git a/lib/symfony/console/Output/OutputInterface.php b/lib/symfony/console/Output/OutputInterface.php index 55caab80b..19a817901 100644 --- a/lib/symfony/console/Output/OutputInterface.php +++ b/lib/symfony/console/Output/OutputInterface.php @@ -33,78 +33,79 @@ interface OutputInterface /** * Writes a message to the output. * - * @param string|iterable $messages The message as an iterable of strings or a single string - * @param bool $newline Whether to add a newline - * @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 + * @param bool $newline Whether to add a newline + * @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 + * + * @return void */ - public function write($messages, bool $newline = false, int $options = 0); + public function write(string|iterable $messages, bool $newline = false, int $options = 0); /** * Writes a message to the output and adds a newline at the end. * - * @param string|iterable $messages The message as an iterable of strings or a single string - * @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 + * @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 + * + * @return void */ - public function writeln($messages, int $options = 0); + public function writeln(string|iterable $messages, int $options = 0); /** * Sets the verbosity of the output. + * + * @param self::VERBOSITY_* $level + * + * @return void */ public function setVerbosity(int $level); /** * Gets the current verbosity of the output. * - * @return int + * @return self::VERBOSITY_* */ - public function getVerbosity(); + public function getVerbosity(): int; /** * Returns whether verbosity is quiet (-q). - * - * @return bool */ - public function isQuiet(); + public function isQuiet(): bool; /** * Returns whether verbosity is verbose (-v). - * - * @return bool */ - public function isVerbose(); + public function isVerbose(): bool; /** * Returns whether verbosity is very verbose (-vv). - * - * @return bool */ - public function isVeryVerbose(); + public function isVeryVerbose(): bool; /** * Returns whether verbosity is debug (-vvv). - * - * @return bool */ - public function isDebug(); + public function isDebug(): bool; /** * Sets the decorated flag. + * + * @return void */ public function setDecorated(bool $decorated); /** * Gets the decorated flag. - * - * @return bool */ - public function isDecorated(); + public function isDecorated(): bool; + /** + * @return void + */ public function setFormatter(OutputFormatterInterface $formatter); /** * Returns current output formatter instance. - * - * @return OutputFormatterInterface */ - public function getFormatter(); + public function getFormatter(): OutputFormatterInterface; } diff --git a/lib/symfony/console/Output/StreamOutput.php b/lib/symfony/console/Output/StreamOutput.php index 7f5551827..da5eefb6f 100644 --- a/lib/symfony/console/Output/StreamOutput.php +++ b/lib/symfony/console/Output/StreamOutput.php @@ -29,6 +29,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface; */ class StreamOutput extends Output { + /** @var resource */ private $stream; /** @@ -47,9 +48,7 @@ class StreamOutput extends Output $this->stream = $stream; - if (null === $decorated) { - $decorated = $this->hasColorSupport(); - } + $decorated ??= $this->hasColorSupport(); parent::__construct($verbosity, $decorated, $formatter); } @@ -65,7 +64,7 @@ class StreamOutput extends Output } /** - * {@inheritdoc} + * @return void */ protected function doWrite(string $message, bool $newline) { @@ -91,7 +90,7 @@ class StreamOutput extends Output * * @return bool true if the stream supports colorization, false otherwise */ - protected function hasColorSupport() + protected function hasColorSupport(): bool { // Follow https://no-color.org/ if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { diff --git a/lib/symfony/console/Output/TrimmedBufferOutput.php b/lib/symfony/console/Output/TrimmedBufferOutput.php index 3f4d375f4..b00445ece 100644 --- a/lib/symfony/console/Output/TrimmedBufferOutput.php +++ b/lib/symfony/console/Output/TrimmedBufferOutput.php @@ -21,8 +21,8 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface; */ class TrimmedBufferOutput extends Output { - private $maxLength; - private $buffer = ''; + private int $maxLength; + private string $buffer = ''; public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) { @@ -36,10 +36,8 @@ class TrimmedBufferOutput extends Output /** * Empties buffer and returns its content. - * - * @return string */ - public function fetch() + public function fetch(): string { $content = $this->buffer; $this->buffer = ''; @@ -48,7 +46,7 @@ class TrimmedBufferOutput extends Output } /** - * {@inheritdoc} + * @return void */ protected function doWrite(string $message, bool $newline) { diff --git a/lib/symfony/console/Question/ChoiceQuestion.php b/lib/symfony/console/Question/ChoiceQuestion.php index bf1f90487..e449ff683 100644 --- a/lib/symfony/console/Question/ChoiceQuestion.php +++ b/lib/symfony/console/Question/ChoiceQuestion.php @@ -20,17 +20,17 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; */ class ChoiceQuestion extends Question { - private $choices; - private $multiselect = false; - private $prompt = ' > '; - private $errorMessage = 'Value "%s" is invalid'; + private array $choices; + private bool $multiselect = false; + private string $prompt = ' > '; + private string $errorMessage = 'Value "%s" is invalid'; /** * @param string $question The question to ask to the user * @param array $choices The list of available choices * @param mixed $default The default answer to return */ - public function __construct(string $question, array $choices, $default = null) + public function __construct(string $question, array $choices, mixed $default = null) { if (!$choices) { throw new \LogicException('Choice question must have at least 1 choice available.'); @@ -45,10 +45,8 @@ class ChoiceQuestion extends Question /** * Returns available choices. - * - * @return array */ - public function getChoices() + public function getChoices(): array { return $this->choices; } @@ -60,7 +58,7 @@ class ChoiceQuestion extends Question * * @return $this */ - public function setMultiselect(bool $multiselect) + public function setMultiselect(bool $multiselect): static { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); @@ -70,20 +68,16 @@ class ChoiceQuestion extends Question /** * Returns whether the choices are multiselect. - * - * @return bool */ - public function isMultiselect() + public function isMultiselect(): bool { return $this->multiselect; } /** * Gets the prompt for choices. - * - * @return string */ - public function getPrompt() + public function getPrompt(): string { return $this->prompt; } @@ -93,7 +87,7 @@ class ChoiceQuestion extends Question * * @return $this */ - public function setPrompt(string $prompt) + public function setPrompt(string $prompt): static { $this->prompt = $prompt; @@ -107,7 +101,7 @@ class ChoiceQuestion extends Question * * @return $this */ - public function setErrorMessage(string $errorMessage) + public function setErrorMessage(string $errorMessage): static { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); diff --git a/lib/symfony/console/Question/ConfirmationQuestion.php b/lib/symfony/console/Question/ConfirmationQuestion.php index 4228521b9..40eab2429 100644 --- a/lib/symfony/console/Question/ConfirmationQuestion.php +++ b/lib/symfony/console/Question/ConfirmationQuestion.php @@ -18,7 +18,7 @@ namespace Symfony\Component\Console\Question; */ class ConfirmationQuestion extends Question { - private $trueAnswerRegex; + private string $trueAnswerRegex; /** * @param string $question The question to ask to the user diff --git a/lib/symfony/console/Question/Question.php b/lib/symfony/console/Question/Question.php index 3a73f04b2..26896bb53 100644 --- a/lib/symfony/console/Question/Question.php +++ b/lib/symfony/console/Question/Question.php @@ -21,22 +21,22 @@ use Symfony\Component\Console\Exception\LogicException; */ class Question { - private $question; - private $attempts; - private $hidden = false; - private $hiddenFallback = true; - private $autocompleterCallback; - private $validator; - private $default; - private $normalizer; - private $trimmable = true; - private $multiline = false; + private string $question; + private ?int $attempts = null; + private bool $hidden = false; + private bool $hiddenFallback = true; + private ?\Closure $autocompleterCallback = null; + private ?\Closure $validator = null; + private string|int|bool|null|float $default; + private ?\Closure $normalizer = null; + private bool $trimmable = true; + private bool $multiline = false; /** * @param string $question The question to ask to the user * @param string|bool|int|float|null $default The default answer to return if the user enters nothing */ - public function __construct(string $question, $default = null) + public function __construct(string $question, string|bool|int|float $default = null) { $this->question = $question; $this->default = $default; @@ -44,20 +44,16 @@ class Question /** * Returns the question. - * - * @return string */ - public function getQuestion() + public function getQuestion(): string { return $this->question; } /** * Returns the default answer. - * - * @return string|bool|int|float|null */ - public function getDefault() + public function getDefault(): string|bool|int|float|null { return $this->default; } @@ -75,7 +71,7 @@ class Question * * @return $this */ - public function setMultiline(bool $multiline): self + public function setMultiline(bool $multiline): static { $this->multiline = $multiline; @@ -84,10 +80,8 @@ class Question /** * Returns whether the user response must be hidden. - * - * @return bool */ - public function isHidden() + public function isHidden(): bool { return $this->hidden; } @@ -99,7 +93,7 @@ class Question * * @throws LogicException In case the autocompleter is also used */ - public function setHidden(bool $hidden) + public function setHidden(bool $hidden): static { if ($this->autocompleterCallback) { throw new LogicException('A hidden question cannot use the autocompleter.'); @@ -112,10 +106,8 @@ class Question /** * In case the response cannot be hidden, whether to fallback on non-hidden question or not. - * - * @return bool */ - public function isHiddenFallback() + public function isHiddenFallback(): bool { return $this->hiddenFallback; } @@ -125,7 +117,7 @@ class Question * * @return $this */ - public function setHiddenFallback(bool $fallback) + public function setHiddenFallback(bool $fallback): static { $this->hiddenFallback = $fallback; @@ -134,10 +126,8 @@ class Question /** * Gets values for the autocompleter. - * - * @return iterable|null */ - public function getAutocompleterValues() + public function getAutocompleterValues(): ?iterable { $callback = $this->getAutocompleterCallback(); @@ -151,18 +141,17 @@ class Question * * @throws LogicException */ - public function setAutocompleterValues(?iterable $values) + public function setAutocompleterValues(?iterable $values): static { if (\is_array($values)) { $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); - $callback = static function () use ($values) { - return $values; - }; + $callback = static fn () => $values; } elseif ($values instanceof \Traversable) { - $valueCache = null; - $callback = static function () use ($values, &$valueCache) { - return $valueCache ?? $valueCache = iterator_to_array($values, false); + $callback = static function () use ($values) { + static $valueCache; + + return $valueCache ??= iterator_to_array($values, false); }; } else { $callback = null; @@ -186,13 +175,16 @@ class Question * * @return $this */ - public function setAutocompleterCallback(callable $callback = null): self + public function setAutocompleterCallback(callable $callback = null): static { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if ($this->hidden && null !== $callback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } - $this->autocompleterCallback = $callback; + $this->autocompleterCallback = null === $callback ? null : $callback(...); return $this; } @@ -202,19 +194,20 @@ class Question * * @return $this */ - public function setValidator(callable $validator = null) + public function setValidator(callable $validator = null): static { - $this->validator = $validator; + if (1 > \func_num_args()) { + trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } + $this->validator = null === $validator ? null : $validator(...); return $this; } /** * Gets the validator for the question. - * - * @return callable|null */ - public function getValidator() + public function getValidator(): ?callable { return $this->validator; } @@ -228,7 +221,7 @@ class Question * * @throws InvalidArgumentException in case the number of attempts is invalid */ - public function setMaxAttempts(?int $attempts) + public function setMaxAttempts(?int $attempts): static { if (null !== $attempts && $attempts < 1) { throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); @@ -243,10 +236,8 @@ class Question * Gets the maximum number of attempts. * * Null means an unlimited number of attempts. - * - * @return int|null */ - public function getMaxAttempts() + public function getMaxAttempts(): ?int { return $this->attempts; } @@ -258,9 +249,9 @@ class Question * * @return $this */ - public function setNormalizer(callable $normalizer) + public function setNormalizer(callable $normalizer): static { - $this->normalizer = $normalizer; + $this->normalizer = $normalizer(...); return $this; } @@ -269,14 +260,15 @@ class Question * Gets the normalizer for the response. * * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. - * - * @return callable|null */ - public function getNormalizer() + public function getNormalizer(): ?callable { return $this->normalizer; } + /** + * @return bool + */ protected function isAssoc(array $array) { return (bool) \count(array_filter(array_keys($array), 'is_string')); @@ -290,7 +282,7 @@ class Question /** * @return $this */ - public function setTrimmable(bool $trimmable): self + public function setTrimmable(bool $trimmable): static { $this->trimmable = $trimmable; diff --git a/lib/symfony/console/README.md b/lib/symfony/console/README.md index c4c129989..92f70e714 100644 --- a/lib/symfony/console/README.md +++ b/lib/symfony/console/README.md @@ -7,14 +7,7 @@ interfaces. Sponsor ------- -The Console component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2]. - -Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and -fix your projects. We provide a wide range of professional services including development, -consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps. -We are a worker cooperative! - -Help Symfony by [sponsoring][3] its development! +Help Symfony by [sponsoring][1] its development! Resources --------- @@ -31,6 +24,4 @@ Credits `Resources/bin/hiddeninput.exe` is a third party binary provided within this 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 +[1]: https://symfony.com/sponsor diff --git a/lib/symfony/console/Resources/completion.bash b/lib/symfony/console/Resources/completion.bash index 64b87ccf7..0d76eacc3 100644 --- a/lib/symfony/console/Resources/completion.bash +++ b/lib/symfony/console/Resources/completion.bash @@ -6,6 +6,16 @@ # https://symfony.com/doc/current/contributing/code/license.html _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 IFS=$'\n' local sf_cmd="${COMP_WORDS[0]}" @@ -25,7 +35,7 @@ _sf_{{ COMMAND_NAME }}() { local cur prev words cword _get_comp_words_by_ref -n := cur prev words cword - local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-S{{ VERSION }}") + local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a{{ VERSION }}") for w in ${words[@]}; do w=$(printf -- '%b' "$w") # remove quotes from typed values diff --git a/lib/symfony/console/Resources/completion.fish b/lib/symfony/console/Resources/completion.fish new file mode 100644 index 000000000..1c34292ae --- /dev/null +++ b/lib/symfony/console/Resources/completion.fish @@ -0,0 +1,29 @@ +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +function _sf_{{ COMMAND_NAME }} + set sf_cmd (commandline -o) + set c (count (commandline -oc)) + + set completecmd "$sf_cmd[1]" "_complete" "--no-interaction" "-sfish" "-a{{ VERSION }}" + + for i in $sf_cmd + if [ $i != "" ] + set completecmd $completecmd "-i$i" + end + end + + set completecmd $completecmd "-c$c" + + set sfcomplete ($completecmd) + + for i in $sfcomplete + echo $i + end +end + +complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f diff --git a/lib/symfony/console/Resources/completion.zsh b/lib/symfony/console/Resources/completion.zsh new file mode 100644 index 000000000..ff76fe5fa --- /dev/null +++ b/lib/symfony/console/Resources/completion.zsh @@ -0,0 +1,82 @@ +#compdef {{ COMMAND_NAME }} + +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +# +# zsh completions for {{ COMMAND_NAME }} +# +# References: +# - https://github.com/spf13/cobra/blob/master/zsh_completions.go +# - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash +# +_sf_{{ COMMAND_NAME }}() { + local lastParam flagPrefix requestComp out comp + local -a completions + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") lastParam=${words[-1]} + + # For zsh, when completing a flag with an = (e.g., {{ COMMAND_NAME }} -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a{{ VERSION }} -c$((CURRENT-1))" i="" + for w in ${words[@]}; do + w=$(printf -- '%b' "$w") + # remove quotes from typed values + quote="${w:0:1}" + if [ "$quote" = \' ]; then + w="${w%\'}" + w="${w#\'}" + elif [ "$quote" = \" ]; then + w="${w%\"}" + w="${w#\"}" + fi + # empty values are ignored + if [ ! -z "$w" ]; then + i="${i}-i${w} " + fi + done + + # Ensure at least 1 input + if [ "${i}" = "" ]; then + requestComp="${requestComp} -i\" \"" + else + requestComp="${requestComp} ${i}" + fi + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + local tab=$(printf '\t') + comp=${comp//$tab/:} + completions+=${comp} + fi + done < <(printf "%s\n" "${out[@]}") + + # Let inbuilt _describe handle completions + eval _describe "completions" completions $flagPrefix + return $? +} + +compdef _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} diff --git a/lib/symfony/console/SignalRegistry/SignalMap.php b/lib/symfony/console/SignalRegistry/SignalMap.php new file mode 100644 index 000000000..de419bda7 --- /dev/null +++ b/lib/symfony/console/SignalRegistry/SignalMap.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\SignalRegistry; + +/** + * @author GrĂ©goire Pineau + */ +class SignalMap +{ + private static array $map; + + public static function getSignalName(int $signal): ?string + { + if (!\extension_loaded('pcntl')) { + return null; + } + + if (!isset(self::$map)) { + $r = new \ReflectionExtension('pcntl'); + $c = $r->getConstants(); + $map = array_filter($c, fn ($k) => str_starts_with($k, 'SIG') && !str_starts_with($k, 'SIG_'), \ARRAY_FILTER_USE_KEY); + self::$map = array_flip($map); + } + + return self::$map[$signal] ?? null; + } +} diff --git a/lib/symfony/console/SignalRegistry/SignalRegistry.php b/lib/symfony/console/SignalRegistry/SignalRegistry.php index 6bee24a42..ef2e5f04e 100644 --- a/lib/symfony/console/SignalRegistry/SignalRegistry.php +++ b/lib/symfony/console/SignalRegistry/SignalRegistry.php @@ -13,7 +13,7 @@ namespace Symfony\Component\Console\SignalRegistry; final class SignalRegistry { - private $signalHandlers = []; + private array $signalHandlers = []; public function __construct() { @@ -34,20 +34,12 @@ final class SignalRegistry $this->signalHandlers[$signal][] = $signalHandler; - pcntl_signal($signal, [$this, 'handle']); + pcntl_signal($signal, $this->handle(...)); } public static function isSupported(): bool { - if (!\function_exists('pcntl_signal')) { - return false; - } - - if (\in_array('pcntl_signal', explode(',', \ini_get('disable_functions')))) { - return false; - } - - return true; + return \function_exists('pcntl_signal'); } /** diff --git a/lib/symfony/console/SingleCommandApplication.php b/lib/symfony/console/SingleCommandApplication.php index e93c1821b..4f0b5ba3c 100644 --- a/lib/symfony/console/SingleCommandApplication.php +++ b/lib/symfony/console/SingleCommandApplication.php @@ -20,14 +20,14 @@ use Symfony\Component\Console\Output\OutputInterface; */ class SingleCommandApplication extends Command { - private $version = 'UNKNOWN'; - private $autoExit = true; - private $running = false; + private string $version = 'UNKNOWN'; + private bool $autoExit = true; + private bool $running = false; /** * @return $this */ - public function setVersion(string $version): self + public function setVersion(string $version): static { $this->version = $version; @@ -39,7 +39,7 @@ class SingleCommandApplication extends Command * * @return $this */ - public function setAutoExit(bool $autoExit): self + public function setAutoExit(bool $autoExit): static { $this->autoExit = $autoExit; diff --git a/lib/symfony/console/Style/OutputStyle.php b/lib/symfony/console/Style/OutputStyle.php index 67a98ff07..ddfa8decc 100644 --- a/lib/symfony/console/Style/OutputStyle.php +++ b/lib/symfony/console/Style/OutputStyle.php @@ -23,7 +23,7 @@ use Symfony\Component\Console\Output\OutputInterface; */ abstract class OutputStyle implements OutputInterface, StyleInterface { - private $output; + private OutputInterface $output; public function __construct(OutputInterface $output) { @@ -31,117 +31,96 @@ abstract class OutputStyle implements OutputInterface, StyleInterface } /** - * {@inheritdoc} + * @return void */ public function newLine(int $count = 1) { $this->output->write(str_repeat(\PHP_EOL, $count)); } - /** - * @return ProgressBar - */ - public function createProgressBar(int $max = 0) + public function createProgressBar(int $max = 0): ProgressBar { return new ProgressBar($this->output, $max); } /** - * {@inheritdoc} + * @return void */ - public function write($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); } /** - * {@inheritdoc} + * @return void */ - public function writeln($messages, int $type = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } /** - * {@inheritdoc} + * @return void */ public function setVerbosity(int $level) { $this->output->setVerbosity($level); } - /** - * {@inheritdoc} - */ - public function getVerbosity() + public function getVerbosity(): int { return $this->output->getVerbosity(); } /** - * {@inheritdoc} + * @return void */ public function setDecorated(bool $decorated) { $this->output->setDecorated($decorated); } - /** - * {@inheritdoc} - */ - public function isDecorated() + public function isDecorated(): bool { return $this->output->isDecorated(); } /** - * {@inheritdoc} + * @return void */ public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } - /** - * {@inheritdoc} - */ - public function getFormatter() + public function getFormatter(): OutputFormatterInterface { return $this->output->getFormatter(); } - /** - * {@inheritdoc} - */ - public function isQuiet() + public function isQuiet(): bool { return $this->output->isQuiet(); } - /** - * {@inheritdoc} - */ - public function isVerbose() + public function isVerbose(): bool { return $this->output->isVerbose(); } - /** - * {@inheritdoc} - */ - public function isVeryVerbose() + public function isVeryVerbose(): bool { return $this->output->isVeryVerbose(); } - /** - * {@inheritdoc} - */ - public function isDebug() + public function isDebug(): bool { return $this->output->isDebug(); } + /** + * @return OutputInterface + */ protected function getErrorOutput() { if (!$this->output instanceof ConsoleOutputInterface) { diff --git a/lib/symfony/console/Style/StyleInterface.php b/lib/symfony/console/Style/StyleInterface.php index 38d23b77e..e25a65bd2 100644 --- a/lib/symfony/console/Style/StyleInterface.php +++ b/lib/symfony/console/Style/StyleInterface.php @@ -20,113 +20,119 @@ interface StyleInterface { /** * Formats a command title. + * + * @return void */ public function title(string $message); /** * Formats a section title. + * + * @return void */ public function section(string $message); /** * Formats a list. + * + * @return void */ public function listing(array $elements); /** * Formats informational text. * - * @param string|array $message + * @return void */ - public function text($message); + public function text(string|array $message); /** * Formats a success result bar. * - * @param string|array $message + * @return void */ - public function success($message); + public function success(string|array $message); /** * Formats an error result bar. * - * @param string|array $message + * @return void */ - public function error($message); + public function error(string|array $message); /** * Formats an warning result bar. * - * @param string|array $message + * @return void */ - public function warning($message); + public function warning(string|array $message); /** * Formats a note admonition. * - * @param string|array $message + * @return void */ - public function note($message); + public function note(string|array $message); /** * Formats a caution admonition. * - * @param string|array $message + * @return void */ - public function caution($message); + public function caution(string|array $message); /** * Formats a table. + * + * @return void */ public function table(array $headers, array $rows); /** * Asks a question. - * - * @return mixed */ - public function ask(string $question, string $default = null, callable $validator = null); + public function ask(string $question, string $default = null, callable $validator = null): mixed; /** * Asks a question with the user input hidden. - * - * @return mixed */ - public function askHidden(string $question, callable $validator = null); + public function askHidden(string $question, callable $validator = null): mixed; /** * Asks for confirmation. - * - * @return bool */ - public function confirm(string $question, bool $default = true); + public function confirm(string $question, bool $default = true): bool; /** * Asks a choice question. - * - * @param string|int|null $default - * - * @return mixed */ - public function choice(string $question, array $choices, $default = null); + public function choice(string $question, array $choices, mixed $default = null): mixed; /** * Add newline(s). + * + * @return void */ public function newLine(int $count = 1); /** * Starts the progress output. + * + * @return void */ public function progressStart(int $max = 0); /** * Advances the progress output X steps. + * + * @return void */ public function progressAdvance(int $step = 1); /** * Finishes the progress output. + * + * @return void */ public function progressFinish(); } diff --git a/lib/symfony/console/Style/SymfonyStyle.php b/lib/symfony/console/Style/SymfonyStyle.php index e3c5ac8e7..43d2edf5a 100644 --- a/lib/symfony/console/Style/SymfonyStyle.php +++ b/lib/symfony/console/Style/SymfonyStyle.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\OutputWrapper; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\SymfonyQuestionHelper; use Symfony\Component\Console\Helper\Table; @@ -22,6 +23,7 @@ use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\TrimmedBufferOutput; use Symfony\Component\Console\Question\ChoiceQuestion; @@ -38,12 +40,12 @@ class SymfonyStyle extends OutputStyle { public const MAX_LINE_LENGTH = 120; - private $input; - private $output; - private $questionHelper; - private $progressBar; - private $lineLength; - private $bufferedOutput; + private InputInterface $input; + private OutputInterface $output; + private SymfonyQuestionHelper $questionHelper; + private ProgressBar $progressBar; + private int $lineLength; + private TrimmedBufferOutput $bufferedOutput; public function __construct(InputInterface $input, OutputInterface $output) { @@ -59,9 +61,9 @@ class SymfonyStyle extends OutputStyle /** * Formats a message as a block of text. * - * @param string|array $messages The message to write in the block + * @return void */ - public function block($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) { $messages = \is_array($messages) ? array_values($messages) : [$messages]; @@ -71,7 +73,7 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ public function title(string $message) { @@ -84,7 +86,7 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ public function section(string $message) { @@ -97,23 +99,21 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ public function listing(array $elements) { $this->autoPrependText(); - $elements = array_map(function ($element) { - return sprintf(' * %s', $element); - }, $elements); + $elements = array_map(fn ($element) => sprintf(' * %s', $element), $elements); $this->writeln($elements); $this->newLine(); } /** - * {@inheritdoc} + * @return void */ - public function text($message) + public function text(string|array $message) { $this->autoPrependText(); @@ -126,41 +126,41 @@ class SymfonyStyle extends OutputStyle /** * Formats a command comment. * - * @param string|array $message + * @return void */ - public function comment($message) + public function comment(string|array $message) { $this->block($message, null, null, ' // ', false, false); } /** - * {@inheritdoc} + * @return void */ - public function success($message) + public function success(string|array $message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } /** - * {@inheritdoc} + * @return void */ - public function error($message) + public function error(string|array $message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } /** - * {@inheritdoc} + * @return void */ - public function warning($message) + public function warning(string|array $message) { $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); } /** - * {@inheritdoc} + * @return void */ - public function note($message) + public function note(string|array $message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } @@ -168,23 +168,23 @@ class SymfonyStyle extends OutputStyle /** * Formats an info message. * - * @param string|array $message + * @return void */ - public function info($message) + public function info(string|array $message) { $this->block($message, 'INFO', 'fg=green', ' ', true); } /** - * {@inheritdoc} + * @return void */ - public function caution($message) + public function caution(string|array $message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } /** - * {@inheritdoc} + * @return void */ public function table(array $headers, array $rows) { @@ -199,6 +199,8 @@ class SymfonyStyle extends OutputStyle /** * Formats a horizontal table. + * + * @return void */ public function horizontalTable(array $headers, array $rows) { @@ -220,9 +222,9 @@ class SymfonyStyle extends OutputStyle * * ['key' => 'value'] * * new TableSeparator() * - * @param string|array|TableSeparator ...$list + * @return void */ - public function definitionList(...$list) + public function definitionList(string|array|TableSeparator ...$list) { $headers = []; $row = []; @@ -247,10 +249,7 @@ class SymfonyStyle extends OutputStyle $this->horizontalTable($headers, [$row]); } - /** - * {@inheritdoc} - */ - public function ask(string $question, string $default = null, callable $validator = null) + public function ask(string $question, string $default = null, callable $validator = null): mixed { $question = new Question($question, $default); $question->setValidator($validator); @@ -258,10 +257,7 @@ class SymfonyStyle extends OutputStyle return $this->askQuestion($question); } - /** - * {@inheritdoc} - */ - public function askHidden(string $question, callable $validator = null) + public function askHidden(string $question, callable $validator = null): mixed { $question = new Question($question); @@ -271,29 +267,26 @@ class SymfonyStyle extends OutputStyle return $this->askQuestion($question); } - /** - * {@inheritdoc} - */ - public function confirm(string $question, bool $default = true) + public function confirm(string $question, bool $default = true): bool { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } - /** - * {@inheritdoc} - */ - public function choice(string $question, array $choices, $default = null) + public function choice(string $question, array $choices, mixed $default = null, bool $multiSelect = false): mixed { if (null !== $default) { $values = array_flip($choices); $default = $values[$default] ?? $default; } - return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + $questionChoice = new ChoiceQuestion($question, $choices, $default); + $questionChoice->setMultiselect($multiSelect); + + return $this->askQuestion($questionChoice); } /** - * {@inheritdoc} + * @return void */ public function progressStart(int $max = 0) { @@ -302,7 +295,7 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ public function progressAdvance(int $step = 1) { @@ -310,19 +303,16 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); - $this->progressBar = null; + unset($this->progressBar); } - /** - * {@inheritdoc} - */ - public function createProgressBar(int $max = 0) + public function createProgressBar(int $max = 0): ProgressBar { $progressBar = parent::createProgressBar($max); @@ -337,6 +327,14 @@ class SymfonyStyle extends OutputStyle /** * @see ProgressBar::iterate() + * + * @template TKey + * @template TValue + * + * @param iterable $iterable + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + * + * @return iterable */ public function progressIterate(iterable $iterable, int $max = null): iterable { @@ -345,22 +343,22 @@ class SymfonyStyle extends OutputStyle $this->newLine(2); } - /** - * @return mixed - */ - public function askQuestion(Question $question) + public function askQuestion(Question $question): mixed { if ($this->input->isInteractive()) { $this->autoPrependBlock(); } - if (!$this->questionHelper) { - $this->questionHelper = new SymfonyQuestionHelper(); - } + $this->questionHelper ??= new SymfonyQuestionHelper(); $answer = $this->questionHelper->ask($this->input, $this, $question); if ($this->input->isInteractive()) { + if ($this->output instanceof ConsoleSectionOutput) { + // add the new line of the `return` to submit the input to ConsoleSectionOutput, because ConsoleSectionOutput is holding all it's lines. + // this is relevant when a `ConsoleSectionOutput::clear` is called. + $this->output->addNewLineOfInputSubmit(); + } $this->newLine(); $this->bufferedOutput->write("\n"); } @@ -369,9 +367,9 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ - public function writeln($messages, int $type = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; @@ -384,9 +382,9 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ - public function write($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)) { $messages = [$messages]; @@ -399,7 +397,7 @@ class SymfonyStyle extends OutputStyle } /** - * {@inheritdoc} + * @return void */ public function newLine(int $count = 1) { @@ -409,10 +407,8 @@ class SymfonyStyle extends OutputStyle /** * Returns a new instance which makes use of stderr if available. - * - * @return self */ - public function getErrorStyle() + public function getErrorStyle(): self { return new self($this->input, $this->getErrorOutput()); } @@ -428,11 +424,8 @@ class SymfonyStyle extends OutputStyle private function getProgressBar(): ProgressBar { - if (!$this->progressBar) { - throw new RuntimeException('The ProgressBar is not started.'); - } - - return $this->progressBar; + return $this->progressBar + ?? throw new RuntimeException('The ProgressBar is not started.'); } private function autoPrependBlock(): void @@ -452,7 +445,7 @@ class SymfonyStyle extends OutputStyle { $fetched = $this->bufferedOutput->fetch(); // Prepend new line if last char isn't EOL: - if (!str_ends_with($fetched, "\n")) { + if ($fetched && !str_ends_with($fetched, "\n")) { $this->newLine(); } } @@ -471,22 +464,25 @@ class SymfonyStyle extends OutputStyle if (null !== $type) { $type = sprintf('[%s] ', $type); - $indentLength = \strlen($type); + $indentLength = Helper::width($type); $lineIndentation = str_repeat(' ', $indentLength); } // wrap and add newlines for each element + $outputWrapper = new OutputWrapper(); foreach ($messages as $key => $message) { if ($escape) { $message = OutputFormatter::escape($message); } - $decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message)); - $messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength); - $messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true)); - foreach ($messageLines as $messageLine) { - $lines[] = $messageLine; - } + $lines = array_merge( + $lines, + explode(\PHP_EOL, $outputWrapper->wrap( + $message, + $this->lineLength - $prefixLength - $indentLength, + \PHP_EOL + )) + ); if (\count($messages) > 1 && $key < \count($messages) - 1) { $lines[] = ''; diff --git a/lib/symfony/console/Terminal.php b/lib/symfony/console/Terminal.php index 08c53535b..3eda0376b 100644 --- a/lib/symfony/console/Terminal.php +++ b/lib/symfony/console/Terminal.php @@ -11,18 +11,79 @@ namespace Symfony\Component\Console; +use Symfony\Component\Console\Output\AnsiColorMode; + class Terminal { - private static $width; - private static $height; - private static $stty; + public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4; + + private static ?AnsiColorMode $colorMode = null; + private static ?int $width = null; + private static ?int $height = null; + private static ?bool $stty = null; + + /** + * About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + * For more information about true color support with terminals https://github.com/termstandard/colors/. + */ + public static function getColorMode(): AnsiColorMode + { + // Use Cache from previous run (or user forced mode) + if (null !== self::$colorMode) { + return self::$colorMode; + } + + // Try with $COLORTERM first + if (\is_string($colorterm = getenv('COLORTERM'))) { + $colorterm = strtolower($colorterm); + + if (str_contains($colorterm, 'truecolor')) { + self::setColorMode(AnsiColorMode::Ansi24); + + return self::$colorMode; + } + + if (str_contains($colorterm, '256color')) { + self::setColorMode(AnsiColorMode::Ansi8); + + return self::$colorMode; + } + } + + // Try with $TERM + if (\is_string($term = getenv('TERM'))) { + $term = strtolower($term); + + if (str_contains($term, 'truecolor')) { + self::setColorMode(AnsiColorMode::Ansi24); + + return self::$colorMode; + } + + if (str_contains($term, '256color')) { + self::setColorMode(AnsiColorMode::Ansi8); + + return self::$colorMode; + } + } + + self::setColorMode(self::DEFAULT_COLOR_MODE); + + return self::$colorMode; + } + + /** + * Force a terminal color mode rendering. + */ + public static function setColorMode(?AnsiColorMode $colorMode): void + { + self::$colorMode = $colorMode; + } /** * Gets the terminal width. - * - * @return int */ - public function getWidth() + public function getWidth(): int { $width = getenv('COLUMNS'); if (false !== $width) { @@ -38,10 +99,8 @@ class Terminal /** * Gets the terminal height. - * - * @return int */ - public function getHeight() + public function getHeight(): int { $height = getenv('LINES'); if (false !== $height) { @@ -64,20 +123,19 @@ class Terminal return self::$stty; } - // skip check if exec function is disabled - if (!\function_exists('exec')) { + // skip check if shell_exec function is disabled + if (!\function_exists('shell_exec')) { return false; } - exec('stty 2>&1', $output, $exitcode); - - return self::$stty = 0 === $exitcode; + 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 (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) { + $ansicon = getenv('ANSICON'); + if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) { // extract [w, H] from "wxh (WxH)" // or [w, h] from "wxh" self::$width = (int) $matches[1]; @@ -107,14 +165,14 @@ class Terminal /** * Initializes dimensions using the output of an stty columns line. */ - private static function initDimensionsUsingStty() + private static function initDimensionsUsingStty(): void { if ($sttyString = self::getSttyColumns()) { - if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) { // extract [w, h] from "rows h; columns w;" self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; - } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) { // extract [w, h] from "; h rows; w columns" self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; @@ -143,10 +201,10 @@ class Terminal */ private static function getSttyColumns(): ?string { - return self::readFromProcess('stty -a | grep columns'); + return self::readFromProcess(['stty', '-a']); } - private static function readFromProcess(string $command): ?string + private static function readFromProcess(string|array $command): ?string { if (!\function_exists('proc_open')) { return null; @@ -157,6 +215,8 @@ class Terminal 2 => ['pipe', 'w'], ]; + $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0; + $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); if (!\is_resource($process)) { return null; @@ -167,6 +227,10 @@ class Terminal fclose($pipes[2]); proc_close($process); + if ($cp) { + sapi_windows_cp_set($cp); + } + return $info; } } diff --git a/lib/symfony/console/Tester/ApplicationTester.php b/lib/symfony/console/Tester/ApplicationTester.php index 3a262e81c..58aee54d6 100644 --- a/lib/symfony/console/Tester/ApplicationTester.php +++ b/lib/symfony/console/Tester/ApplicationTester.php @@ -28,7 +28,7 @@ class ApplicationTester { use TesterTrait; - private $application; + private Application $application; public function __construct(Application $application) { @@ -47,7 +47,7 @@ class ApplicationTester * * @return int The command exit code */ - public function run(array $input, array $options = []) + public function run(array $input, array $options = []): int { $prevShellVerbosity = getenv('SHELL_VERBOSITY'); diff --git a/lib/symfony/console/Tester/CommandCompletionTester.php b/lib/symfony/console/Tester/CommandCompletionTester.php index ade732752..a90fe52ef 100644 --- a/lib/symfony/console/Tester/CommandCompletionTester.php +++ b/lib/symfony/console/Tester/CommandCompletionTester.php @@ -22,7 +22,7 @@ use Symfony\Component\Console\Completion\CompletionSuggestions; */ class CommandCompletionTester { - private $command; + private Command $command; public function __construct(Command $command) { diff --git a/lib/symfony/console/Tester/CommandTester.php b/lib/symfony/console/Tester/CommandTester.php index 6c15c25fb..2ff813b7d 100644 --- a/lib/symfony/console/Tester/CommandTester.php +++ b/lib/symfony/console/Tester/CommandTester.php @@ -24,7 +24,7 @@ class CommandTester { use TesterTrait; - private $command; + private Command $command; public function __construct(Command $command) { @@ -46,7 +46,7 @@ class CommandTester * * @return int The command exit code */ - public function execute(array $input, array $options = []) + public function execute(array $input, array $options = []): int { // set the command name automatically if the application requires // this argument and no command name was passed diff --git a/lib/symfony/console/Tester/Constraint/CommandIsSuccessful.php b/lib/symfony/console/Tester/Constraint/CommandIsSuccessful.php index a47324237..09c6194b9 100644 --- a/lib/symfony/console/Tester/Constraint/CommandIsSuccessful.php +++ b/lib/symfony/console/Tester/Constraint/CommandIsSuccessful.php @@ -16,33 +16,21 @@ use Symfony\Component\Console\Command\Command; final class CommandIsSuccessful extends Constraint { - /** - * {@inheritdoc} - */ public function toString(): string { return 'is successful'; } - /** - * {@inheritdoc} - */ protected function matches($other): bool { return Command::SUCCESS === $other; } - /** - * {@inheritdoc} - */ protected function failureDescription($other): string { return 'the command '.$this->toString(); } - /** - * {@inheritdoc} - */ protected function additionalFailureDescription($other): string { $mapping = [ diff --git a/lib/symfony/console/Tester/TesterTrait.php b/lib/symfony/console/Tester/TesterTrait.php index 40bc58177..1ab7a70aa 100644 --- a/lib/symfony/console/Tester/TesterTrait.php +++ b/lib/symfony/console/Tester/TesterTrait.php @@ -23,25 +23,20 @@ use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful; */ trait TesterTrait { - /** @var StreamOutput */ - private $output; - private $inputs = []; - private $captureStreamsIndependently = false; - /** @var InputInterface */ - private $input; - /** @var int */ - private $statusCode; + private StreamOutput $output; + private array $inputs = []; + private bool $captureStreamsIndependently = false; + private InputInterface $input; + private int $statusCode; /** * Gets the display returned by the last execution of the command or application. * * @throws \RuntimeException If it's called before the execute method - * - * @return string */ - public function getDisplay(bool $normalize = false) + public function getDisplay(bool $normalize = false): string { - if (null === $this->output) { + if (!isset($this->output)) { throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); } @@ -60,10 +55,8 @@ trait TesterTrait * Gets the output written to STDERR by the application. * * @param bool $normalize Whether to normalize end of lines to \n or not - * - * @return string */ - public function getErrorOutput(bool $normalize = false) + public function getErrorOutput(bool $normalize = false): string { if (!$this->captureStreamsIndependently) { throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); @@ -82,20 +75,16 @@ trait TesterTrait /** * Gets the input instance used by the last execution of the command or application. - * - * @return InputInterface */ - public function getInput() + public function getInput(): InputInterface { return $this->input; } /** * Gets the output instance used by the last execution of the command or application. - * - * @return OutputInterface */ - public function getOutput() + public function getOutput(): OutputInterface { return $this->output; } @@ -104,16 +93,10 @@ trait TesterTrait * Gets the status code returned by the last execution of the command or application. * * @throws \RuntimeException If it's called before the execute method - * - * @return int */ - public function getStatusCode() + public function getStatusCode(): int { - if (null === $this->statusCode) { - throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); - } - - return $this->statusCode; + return $this->statusCode ?? throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); } public function assertCommandIsSuccessful(string $message = ''): void @@ -129,7 +112,7 @@ trait TesterTrait * * @return $this */ - public function setInputs(array $inputs) + public function setInputs(array $inputs): static { $this->inputs = $inputs; @@ -145,9 +128,9 @@ trait TesterTrait * * verbosity: Sets the output verbosity flag * * 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 = $options['capture_stderr_separately'] ?? false; if (!$this->captureStreamsIndependently) { $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { @@ -169,12 +152,10 @@ trait TesterTrait $reflectedOutput = new \ReflectionObject($this->output); $strErrProperty = $reflectedOutput->getProperty('stderr'); - $strErrProperty->setAccessible(true); $strErrProperty->setValue($this->output, $errorOutput); $reflectedParent = $reflectedOutput->getParentClass(); $streamProperty = $reflectedParent->getProperty('stream'); - $streamProperty->setAccessible(true); $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); } } diff --git a/lib/symfony/console/composer.json b/lib/symfony/console/composer.json index 9a565068c..1610f7341 100644 --- a/lib/symfony/console/composer.json +++ b/lib/symfony/console/composer.json @@ -2,7 +2,7 @@ "name": "symfony/console", "type": "library", "description": "Eases the creation of beautiful and testable command line interfaces", - "keywords": ["console", "cli", "command line", "terminal"], + "keywords": ["console", "cli", "command-line", "terminal"], "homepage": "https://symfony.com", "license": "MIT", "authors": [ @@ -16,39 +16,34 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" }, "require-dev": { - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0", - "psr/log": "^1|^2" + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0", + "psr/log": "^1|^2|^3" }, "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "suggest": { - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "", - "psr/log": "For using the console logger" + "psr/log-implementation": "1.0|2.0|3.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" }, diff --git a/lib/symfony/css-selector/CHANGELOG.md b/lib/symfony/css-selector/CHANGELOG.md index de81fa2e7..c035d6b3d 100644 --- a/lib/symfony/css-selector/CHANGELOG.md +++ b/lib/symfony/css-selector/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +----- + + * Add support for `:scope` + 4.4.0 ----- diff --git a/lib/symfony/css-selector/CssSelectorConverter.php b/lib/symfony/css-selector/CssSelectorConverter.php index bbb6afe21..7120a2950 100644 --- a/lib/symfony/css-selector/CssSelectorConverter.php +++ b/lib/symfony/css-selector/CssSelectorConverter.php @@ -26,11 +26,11 @@ use Symfony\Component\CssSelector\XPath\Translator; */ class CssSelectorConverter { - private $translator; - private $cache; + private Translator $translator; + private array $cache; - private static $xmlCache = []; - private static $htmlCache = []; + private static array $xmlCache = []; + private static array $htmlCache = []; /** * @param bool $html Whether HTML support should be enabled. Disable it for XML documents @@ -59,11 +59,9 @@ class CssSelectorConverter * * Optionally, a prefix can be added to the resulting XPath * expression with the $prefix parameter. - * - * @return string */ - public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::') + public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string { - return $this->cache[$prefix][$cssExpr] ?? $this->cache[$prefix][$cssExpr] = $this->translator->cssToXPath($cssExpr, $prefix); + return $this->cache[$prefix][$cssExpr] ??= $this->translator->cssToXPath($cssExpr, $prefix); } } diff --git a/lib/symfony/css-selector/Exception/SyntaxErrorException.php b/lib/symfony/css-selector/Exception/SyntaxErrorException.php index 7deacf9c5..5a9d8078d 100644 --- a/lib/symfony/css-selector/Exception/SyntaxErrorException.php +++ b/lib/symfony/css-selector/Exception/SyntaxErrorException.php @@ -23,42 +23,32 @@ use Symfony\Component\CssSelector\Parser\Token; */ class SyntaxErrorException extends ParseException { - /** - * @return self - */ - public static function unexpectedToken(string $expectedValue, Token $foundToken) + public static function unexpectedToken(string $expectedValue, Token $foundToken): self { return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken)); } - /** - * @return self - */ - public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation) + public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation): self { return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation)); } - /** - * @return self - */ - public static function unclosedString(int $position) + public static function unclosedString(int $position): self { return new self(sprintf('Unclosed/invalid string at %s.', $position)); } - /** - * @return self - */ - public static function nestedNot() + public static function nestedNot(): self { return new self('Got nested ::not().'); } - /** - * @return self - */ - public static function stringAsFunctionArgument() + public static function notAtTheStartOfASelector(string $pseudoElement): self + { + return new self(sprintf('Got immediate child pseudo-element ":%s" not at the start of a selector', $pseudoElement)); + } + + public static function stringAsFunctionArgument(): self { return new self('String not allowed as function argument.'); } diff --git a/lib/symfony/css-selector/LICENSE b/lib/symfony/css-selector/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/lib/symfony/css-selector/LICENSE +++ b/lib/symfony/css-selector/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/css-selector/Node/AbstractNode.php b/lib/symfony/css-selector/Node/AbstractNode.php index 1306aeacb..d99e80a87 100644 --- a/lib/symfony/css-selector/Node/AbstractNode.php +++ b/lib/symfony/css-selector/Node/AbstractNode.php @@ -23,17 +23,10 @@ namespace Symfony\Component\CssSelector\Node; */ abstract class AbstractNode implements NodeInterface { - /** - * @var string - */ - private $nodeName; + private string $nodeName; public function getNodeName(): string { - if (null === $this->nodeName) { - $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); - } - - return $this->nodeName; + return $this->nodeName ??= preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); } } diff --git a/lib/symfony/css-selector/Node/AttributeNode.php b/lib/symfony/css-selector/Node/AttributeNode.php index 0b6e0ee0a..ba4df3100 100644 --- a/lib/symfony/css-selector/Node/AttributeNode.php +++ b/lib/symfony/css-selector/Node/AttributeNode.php @@ -23,11 +23,11 @@ namespace Symfony\Component\CssSelector\Node; */ class AttributeNode extends AbstractNode { - private $selector; - private $namespace; - private $attribute; - private $operator; - private $value; + private NodeInterface $selector; + private ?string $namespace; + private string $attribute; + private string $operator; + private ?string $value; public function __construct(NodeInterface $selector, ?string $namespace, string $attribute, string $operator, ?string $value) { @@ -63,9 +63,6 @@ class AttributeNode extends AbstractNode return $this->value; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); diff --git a/lib/symfony/css-selector/Node/ClassNode.php b/lib/symfony/css-selector/Node/ClassNode.php index 1efca808d..717445870 100644 --- a/lib/symfony/css-selector/Node/ClassNode.php +++ b/lib/symfony/css-selector/Node/ClassNode.php @@ -23,8 +23,8 @@ namespace Symfony\Component\CssSelector\Node; */ class ClassNode extends AbstractNode { - private $selector; - private $name; + private NodeInterface $selector; + private string $name; public function __construct(NodeInterface $selector, string $name) { @@ -42,9 +42,6 @@ class ClassNode extends AbstractNode return $this->name; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); diff --git a/lib/symfony/css-selector/Node/CombinedSelectorNode.php b/lib/symfony/css-selector/Node/CombinedSelectorNode.php index a217a45ed..19fecb700 100644 --- a/lib/symfony/css-selector/Node/CombinedSelectorNode.php +++ b/lib/symfony/css-selector/Node/CombinedSelectorNode.php @@ -23,9 +23,9 @@ namespace Symfony\Component\CssSelector\Node; */ class CombinedSelectorNode extends AbstractNode { - private $selector; - private $combinator; - private $subSelector; + private NodeInterface $selector; + private string $combinator; + private NodeInterface $subSelector; public function __construct(NodeInterface $selector, string $combinator, NodeInterface $subSelector) { @@ -49,9 +49,6 @@ class CombinedSelectorNode extends AbstractNode return $this->subSelector; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); diff --git a/lib/symfony/css-selector/Node/ElementNode.php b/lib/symfony/css-selector/Node/ElementNode.php index fbf8ea0f9..39f4d9cc0 100644 --- a/lib/symfony/css-selector/Node/ElementNode.php +++ b/lib/symfony/css-selector/Node/ElementNode.php @@ -23,8 +23,8 @@ namespace Symfony\Component\CssSelector\Node; */ class ElementNode extends AbstractNode { - private $namespace; - private $element; + private ?string $namespace; + private ?string $element; public function __construct(string $namespace = null, string $element = null) { @@ -42,9 +42,6 @@ class ElementNode extends AbstractNode return $this->element; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return new Specificity(0, 0, $this->element ? 1 : 0); diff --git a/lib/symfony/css-selector/Node/FunctionNode.php b/lib/symfony/css-selector/Node/FunctionNode.php index c464cf7c0..938a82b1a 100644 --- a/lib/symfony/css-selector/Node/FunctionNode.php +++ b/lib/symfony/css-selector/Node/FunctionNode.php @@ -25,9 +25,9 @@ use Symfony\Component\CssSelector\Parser\Token; */ class FunctionNode extends AbstractNode { - private $selector; - private $name; - private $arguments; + private NodeInterface $selector; + private string $name; + private array $arguments; /** * @param Token[] $arguments @@ -57,9 +57,6 @@ class FunctionNode extends AbstractNode return $this->arguments; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); @@ -67,9 +64,7 @@ class FunctionNode extends AbstractNode public function __toString(): string { - $arguments = implode(', ', array_map(function (Token $token) { - return "'".$token->getValue()."'"; - }, $this->arguments)); + $arguments = implode(', ', array_map(fn (Token $token) => "'".$token->getValue()."'", $this->arguments)); return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); } diff --git a/lib/symfony/css-selector/Node/HashNode.php b/lib/symfony/css-selector/Node/HashNode.php index 94114c095..3af8e8474 100644 --- a/lib/symfony/css-selector/Node/HashNode.php +++ b/lib/symfony/css-selector/Node/HashNode.php @@ -23,8 +23,8 @@ namespace Symfony\Component\CssSelector\Node; */ class HashNode extends AbstractNode { - private $selector; - private $id; + private NodeInterface $selector; + private string $id; public function __construct(NodeInterface $selector, string $id) { @@ -42,9 +42,6 @@ class HashNode extends AbstractNode return $this->id; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); diff --git a/lib/symfony/css-selector/Node/NegationNode.php b/lib/symfony/css-selector/Node/NegationNode.php index f00522fb9..f80675838 100644 --- a/lib/symfony/css-selector/Node/NegationNode.php +++ b/lib/symfony/css-selector/Node/NegationNode.php @@ -23,8 +23,8 @@ namespace Symfony\Component\CssSelector\Node; */ class NegationNode extends AbstractNode { - private $selector; - private $subSelector; + private NodeInterface $selector; + private NodeInterface $subSelector; public function __construct(NodeInterface $selector, NodeInterface $subSelector) { @@ -42,9 +42,6 @@ class NegationNode extends AbstractNode return $this->subSelector; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); diff --git a/lib/symfony/css-selector/Node/NodeInterface.php b/lib/symfony/css-selector/Node/NodeInterface.php index b078d26d4..7d541f9c8 100644 --- a/lib/symfony/css-selector/Node/NodeInterface.php +++ b/lib/symfony/css-selector/Node/NodeInterface.php @@ -21,11 +21,9 @@ namespace Symfony\Component\CssSelector\Node; * * @internal */ -interface NodeInterface +interface NodeInterface extends \Stringable { public function getNodeName(): string; public function getSpecificity(): Specificity; - - public function __toString(): string; } diff --git a/lib/symfony/css-selector/Node/PseudoNode.php b/lib/symfony/css-selector/Node/PseudoNode.php index 12b7bd266..c21cd6e92 100644 --- a/lib/symfony/css-selector/Node/PseudoNode.php +++ b/lib/symfony/css-selector/Node/PseudoNode.php @@ -23,8 +23,8 @@ namespace Symfony\Component\CssSelector\Node; */ class PseudoNode extends AbstractNode { - private $selector; - private $identifier; + private NodeInterface $selector; + private string $identifier; public function __construct(NodeInterface $selector, string $identifier) { @@ -42,9 +42,6 @@ class PseudoNode extends AbstractNode return $this->identifier; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); diff --git a/lib/symfony/css-selector/Node/SelectorNode.php b/lib/symfony/css-selector/Node/SelectorNode.php index 6e52b2fa7..0b09ab5dc 100644 --- a/lib/symfony/css-selector/Node/SelectorNode.php +++ b/lib/symfony/css-selector/Node/SelectorNode.php @@ -23,8 +23,8 @@ namespace Symfony\Component\CssSelector\Node; */ class SelectorNode extends AbstractNode { - private $tree; - private $pseudoElement; + private NodeInterface $tree; + private ?string $pseudoElement; public function __construct(NodeInterface $tree, string $pseudoElement = null) { @@ -42,9 +42,6 @@ class SelectorNode extends AbstractNode return $this->pseudoElement; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); diff --git a/lib/symfony/css-selector/Node/Specificity.php b/lib/symfony/css-selector/Node/Specificity.php index b00f6d281..bb8e5e346 100644 --- a/lib/symfony/css-selector/Node/Specificity.php +++ b/lib/symfony/css-selector/Node/Specificity.php @@ -29,9 +29,9 @@ class Specificity public const B_FACTOR = 10; public const C_FACTOR = 1; - private $a; - private $b; - private $c; + private int $a; + private int $b; + private int $c; public function __construct(int $a, int $b, int $c) { diff --git a/lib/symfony/css-selector/Parser/Handler/CommentHandler.php b/lib/symfony/css-selector/Parser/Handler/CommentHandler.php index 93f318844..cc01d1e6e 100644 --- a/lib/symfony/css-selector/Parser/Handler/CommentHandler.php +++ b/lib/symfony/css-selector/Parser/Handler/CommentHandler.php @@ -26,9 +26,6 @@ use Symfony\Component\CssSelector\Parser\TokenStream; */ class CommentHandler implements HandlerInterface { - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { if ('/*' !== $reader->getSubstring(2)) { diff --git a/lib/symfony/css-selector/Parser/Handler/HashHandler.php b/lib/symfony/css-selector/Parser/Handler/HashHandler.php index 7ae9b438c..b29042f56 100644 --- a/lib/symfony/css-selector/Parser/Handler/HashHandler.php +++ b/lib/symfony/css-selector/Parser/Handler/HashHandler.php @@ -29,8 +29,8 @@ use Symfony\Component\CssSelector\Parser\TokenStream; */ class HashHandler implements HandlerInterface { - private $patterns; - private $escaping; + private TokenizerPatterns $patterns; + private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { @@ -38,9 +38,6 @@ class HashHandler implements HandlerInterface $this->escaping = $escaping; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getHashPattern()); diff --git a/lib/symfony/css-selector/Parser/Handler/IdentifierHandler.php b/lib/symfony/css-selector/Parser/Handler/IdentifierHandler.php index 7b2a14e2c..25c0761e9 100644 --- a/lib/symfony/css-selector/Parser/Handler/IdentifierHandler.php +++ b/lib/symfony/css-selector/Parser/Handler/IdentifierHandler.php @@ -29,8 +29,8 @@ use Symfony\Component\CssSelector\Parser\TokenStream; */ class IdentifierHandler implements HandlerInterface { - private $patterns; - private $escaping; + private TokenizerPatterns $patterns; + private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { @@ -38,9 +38,6 @@ class IdentifierHandler implements HandlerInterface $this->escaping = $escaping; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getIdentifierPattern()); diff --git a/lib/symfony/css-selector/Parser/Handler/NumberHandler.php b/lib/symfony/css-selector/Parser/Handler/NumberHandler.php index 8291a68d1..e3eb7afe3 100644 --- a/lib/symfony/css-selector/Parser/Handler/NumberHandler.php +++ b/lib/symfony/css-selector/Parser/Handler/NumberHandler.php @@ -28,16 +28,13 @@ use Symfony\Component\CssSelector\Parser\TokenStream; */ class NumberHandler implements HandlerInterface { - private $patterns; + private TokenizerPatterns $patterns; public function __construct(TokenizerPatterns $patterns) { $this->patterns = $patterns; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getNumberPattern()); diff --git a/lib/symfony/css-selector/Parser/Handler/StringHandler.php b/lib/symfony/css-selector/Parser/Handler/StringHandler.php index 6ce83cdc9..5fd44df26 100644 --- a/lib/symfony/css-selector/Parser/Handler/StringHandler.php +++ b/lib/symfony/css-selector/Parser/Handler/StringHandler.php @@ -31,8 +31,8 @@ use Symfony\Component\CssSelector\Parser\TokenStream; */ class StringHandler implements HandlerInterface { - private $patterns; - private $escaping; + private TokenizerPatterns $patterns; + private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { @@ -40,9 +40,6 @@ class StringHandler implements HandlerInterface $this->escaping = $escaping; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $quote = $reader->getSubstring(1); diff --git a/lib/symfony/css-selector/Parser/Handler/WhitespaceHandler.php b/lib/symfony/css-selector/Parser/Handler/WhitespaceHandler.php index 21345e32c..eb41c3f7f 100644 --- a/lib/symfony/css-selector/Parser/Handler/WhitespaceHandler.php +++ b/lib/symfony/css-selector/Parser/Handler/WhitespaceHandler.php @@ -27,9 +27,6 @@ use Symfony\Component\CssSelector\Parser\TokenStream; */ class WhitespaceHandler implements HandlerInterface { - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern('~^[ \t\r\n\f]+~'); diff --git a/lib/symfony/css-selector/Parser/Parser.php b/lib/symfony/css-selector/Parser/Parser.php index d73489edf..5313d3435 100644 --- a/lib/symfony/css-selector/Parser/Parser.php +++ b/lib/symfony/css-selector/Parser/Parser.php @@ -19,7 +19,7 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer; * CSS selector parser. * * This component is a port of the Python cssselect library, - * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect. * * @author Jean-François Simon * @@ -27,16 +27,13 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer; */ class Parser implements ParserInterface { - private $tokenizer; + private Tokenizer $tokenizer; public function __construct(Tokenizer $tokenizer = null) { $this->tokenizer = $tokenizer ?? new Tokenizer(); } - /** - * {@inheritdoc} - */ public function parse(string $source): array { $reader = new Reader($source); @@ -60,9 +57,7 @@ class Parser implements ParserInterface } } - $joined = trim(implode('', array_map(function (Token $token) { - return $token->getValue(); - }, $tokens))); + $joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens))); $int = function ($string) { if (!is_numeric($string)) { @@ -197,7 +192,18 @@ class Parser implements ParserInterface if (!$stream->getPeek()->isDelimiter(['('])) { $result = new Node\PseudoNode($result, $identifier); - + if ('Pseudo[Element[*]:scope]' === $result->__toString()) { + $used = \count($stream->getUsed()); + if (!(2 === $used + || 3 === $used && $stream->getUsed()[0]->isWhiteSpace() + || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([',']) + || $used >= 4 + && $stream->getUsed()[$used - 3]->isWhiteSpace() + && $stream->getUsed()[$used - 4]->isDelimiter([',']) + )) { + throw SyntaxErrorException::notAtTheStartOfASelector('scope'); + } + } continue; } @@ -242,7 +248,7 @@ class Parser implements ParserInterface } } - if (empty($arguments)) { + if (!$arguments) { throw SyntaxErrorException::unexpectedToken('at least one argument', $next); } diff --git a/lib/symfony/css-selector/Parser/Reader.php b/lib/symfony/css-selector/Parser/Reader.php index 4b43effed..7f6ae7a60 100644 --- a/lib/symfony/css-selector/Parser/Reader.php +++ b/lib/symfony/css-selector/Parser/Reader.php @@ -23,9 +23,9 @@ namespace Symfony\Component\CssSelector\Parser; */ class Reader { - private $source; - private $length; - private $position = 0; + private string $source; + private int $length; + private int $position = 0; public function __construct(string $source) { @@ -53,17 +53,17 @@ class Reader return substr($this->source, $this->position + $offset, $length); } - public function getOffset(string $string) + /** + * @return int|false + */ + public function getOffset(string $string): int|bool { $position = strpos($this->source, $string, $this->position); return false === $position ? false : $position - $this->position; } - /** - * @return array|false - */ - public function findPattern(string $pattern) + public function findPattern(string $pattern): array|false { $source = substr($this->source, $this->position); @@ -74,12 +74,12 @@ class Reader return false; } - public function moveForward(int $length) + public function moveForward(int $length): void { $this->position += $length; } - public function moveToEnd() + public function moveToEnd(): void { $this->position = $this->length; } diff --git a/lib/symfony/css-selector/Parser/Shortcut/ClassParser.php b/lib/symfony/css-selector/Parser/Shortcut/ClassParser.php index 17fa8c27e..f0ce61184 100644 --- a/lib/symfony/css-selector/Parser/Shortcut/ClassParser.php +++ b/lib/symfony/css-selector/Parser/Shortcut/ClassParser.php @@ -28,9 +28,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface; */ class ClassParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an optional namespace, optional element, and required class diff --git a/lib/symfony/css-selector/Parser/Shortcut/ElementParser.php b/lib/symfony/css-selector/Parser/Shortcut/ElementParser.php index 8b9a86386..a448e4a87 100644 --- a/lib/symfony/css-selector/Parser/Shortcut/ElementParser.php +++ b/lib/symfony/css-selector/Parser/Shortcut/ElementParser.php @@ -27,9 +27,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface; */ class ElementParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an optional namespace, required element or `*` diff --git a/lib/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php b/lib/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php index 222df5cd2..a63919120 100644 --- a/lib/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php +++ b/lib/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php @@ -31,9 +31,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface; */ class EmptyStringParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an empty string diff --git a/lib/symfony/css-selector/Parser/Shortcut/HashParser.php b/lib/symfony/css-selector/Parser/Shortcut/HashParser.php index fb07ee6cf..6683126a3 100644 --- a/lib/symfony/css-selector/Parser/Shortcut/HashParser.php +++ b/lib/symfony/css-selector/Parser/Shortcut/HashParser.php @@ -28,9 +28,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface; */ class HashParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an optional namespace, optional element, and required id diff --git a/lib/symfony/css-selector/Parser/Token.php b/lib/symfony/css-selector/Parser/Token.php index a053203c0..b50441a8e 100644 --- a/lib/symfony/css-selector/Parser/Token.php +++ b/lib/symfony/css-selector/Parser/Token.php @@ -31,9 +31,9 @@ class Token public const TYPE_NUMBER = 'number'; public const TYPE_STRING = 'string'; - private $type; - private $value; - private $position; + private ?string $type; + private ?string $value; + private ?int $position; public function __construct(?string $type, ?string $value, ?int $position) { @@ -68,7 +68,7 @@ class Token return false; } - if (empty($values)) { + if (!$values) { return true; } diff --git a/lib/symfony/css-selector/Parser/TokenStream.php b/lib/symfony/css-selector/Parser/TokenStream.php index 2085f2dd7..8b72d5dbc 100644 --- a/lib/symfony/css-selector/Parser/TokenStream.php +++ b/lib/symfony/css-selector/Parser/TokenStream.php @@ -29,34 +29,23 @@ class TokenStream /** * @var Token[] */ - private $tokens = []; + private array $tokens = []; /** * @var Token[] */ - private $used = []; + private array $used = []; - /** - * @var int - */ - private $cursor = 0; - - /** - * @var Token|null - */ - private $peeked; - - /** - * @var bool - */ - private $peeking = false; + private int $cursor = 0; + private ?Token $peeked; + private bool $peeking = false; /** * Pushes a token. * * @return $this */ - public function push(Token $token): self + public function push(Token $token): static { $this->tokens[] = $token; @@ -68,7 +57,7 @@ class TokenStream * * @return $this */ - public function freeze(): self + public function freeze(): static { return $this; } @@ -156,7 +145,7 @@ class TokenStream /** * Skips next whitespace if any. */ - public function skipWhitespace() + public function skipWhitespace(): void { $peek = $this->getPeek(); diff --git a/lib/symfony/css-selector/Parser/Tokenizer/Tokenizer.php b/lib/symfony/css-selector/Parser/Tokenizer/Tokenizer.php index e0dcc5bf1..35c96a484 100644 --- a/lib/symfony/css-selector/Parser/Tokenizer/Tokenizer.php +++ b/lib/symfony/css-selector/Parser/Tokenizer/Tokenizer.php @@ -31,7 +31,7 @@ class Tokenizer /** * @var Handler\HandlerInterface[] */ - private $handlers; + private array $handlers; public function __construct() { diff --git a/lib/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php b/lib/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php index 013e827d2..8c4b9f742 100644 --- a/lib/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php +++ b/lib/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php @@ -23,7 +23,7 @@ namespace Symfony\Component\CssSelector\Parser\Tokenizer; */ class TokenizerEscaping { - private $patterns; + private TokenizerPatterns $patterns; public function __construct(TokenizerPatterns $patterns) { diff --git a/lib/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php b/lib/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php index 5f16ac48f..3c77cf091 100644 --- a/lib/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php +++ b/lib/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php @@ -23,18 +23,18 @@ namespace Symfony\Component\CssSelector\Parser\Tokenizer; */ class TokenizerPatterns { - private $unicodeEscapePattern; - private $simpleEscapePattern; - private $newLineEscapePattern; - private $escapePattern; - private $stringEscapePattern; - private $nonAsciiPattern; - private $nmCharPattern; - private $nmStartPattern; - private $identifierPattern; - private $hashPattern; - private $numberPattern; - private $quotedStringPattern; + private string $unicodeEscapePattern; + private string $simpleEscapePattern; + private string $newLineEscapePattern; + private string $escapePattern; + private string $stringEscapePattern; + private string $nonAsciiPattern; + private string $nmCharPattern; + private string $nmStartPattern; + private string $identifierPattern; + private string $hashPattern; + private string $numberPattern; + private string $quotedStringPattern; public function __construct() { @@ -49,22 +49,22 @@ class TokenizerPatterns $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; - $this->quotedStringPattern = '([^\n\r\f%s]|'.$this->stringEscapePattern.')*'; + $this->quotedStringPattern = '([^\n\r\f\\\\%s]|'.$this->stringEscapePattern.')*'; } public function getNewLineEscapePattern(): string { - return '~^'.$this->newLineEscapePattern.'~'; + return '~'.$this->newLineEscapePattern.'~'; } public function getSimpleEscapePattern(): string { - return '~^'.$this->simpleEscapePattern.'~'; + return '~'.$this->simpleEscapePattern.'~'; } public function getUnicodeEscapePattern(): string { - return '~^'.$this->unicodeEscapePattern.'~i'; + return '~'.$this->unicodeEscapePattern.'~i'; } public function getIdentifierPattern(): string diff --git a/lib/symfony/css-selector/XPath/Extension/AbstractExtension.php b/lib/symfony/css-selector/XPath/Extension/AbstractExtension.php index 44e0035a7..495f88291 100644 --- a/lib/symfony/css-selector/XPath/Extension/AbstractExtension.php +++ b/lib/symfony/css-selector/XPath/Extension/AbstractExtension.php @@ -23,41 +23,26 @@ namespace Symfony\Component\CssSelector\XPath\Extension; */ abstract class AbstractExtension implements ExtensionInterface { - /** - * {@inheritdoc} - */ public function getNodeTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getCombinationTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getFunctionTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getPseudoClassTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getAttributeMatchingTranslators(): array { return []; diff --git a/lib/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php b/lib/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php index a9879f1be..3c785e97a 100644 --- a/lib/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php +++ b/lib/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php @@ -26,20 +26,17 @@ use Symfony\Component\CssSelector\XPath\XPathExpr; */ class AttributeMatchingExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getAttributeMatchingTranslators(): array { return [ - 'exists' => [$this, 'translateExists'], - '=' => [$this, 'translateEquals'], - '~=' => [$this, 'translateIncludes'], - '|=' => [$this, 'translateDashMatch'], - '^=' => [$this, 'translatePrefixMatch'], - '$=' => [$this, 'translateSuffixMatch'], - '*=' => [$this, 'translateSubstringMatch'], - '!=' => [$this, 'translateDifferent'], + 'exists' => $this->translateExists(...), + '=' => $this->translateEquals(...), + '~=' => $this->translateIncludes(...), + '|=' => $this->translateDashMatch(...), + '^=' => $this->translatePrefixMatch(...), + '$=' => $this->translateSuffixMatch(...), + '*=' => $this->translateSubstringMatch(...), + '!=' => $this->translateDifferent(...), ]; } @@ -109,9 +106,6 @@ class AttributeMatchingExtension extends AbstractExtension )); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'attribute-matching'; diff --git a/lib/symfony/css-selector/XPath/Extension/CombinationExtension.php b/lib/symfony/css-selector/XPath/Extension/CombinationExtension.php index aee976e94..f78d48883 100644 --- a/lib/symfony/css-selector/XPath/Extension/CombinationExtension.php +++ b/lib/symfony/css-selector/XPath/Extension/CombinationExtension.php @@ -25,16 +25,13 @@ use Symfony\Component\CssSelector\XPath\XPathExpr; */ class CombinationExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getCombinationTranslators(): array { return [ - ' ' => [$this, 'translateDescendant'], - '>' => [$this, 'translateChild'], - '+' => [$this, 'translateDirectAdjacent'], - '~' => [$this, 'translateIndirectAdjacent'], + ' ' => $this->translateDescendant(...), + '>' => $this->translateChild(...), + '+' => $this->translateDirectAdjacent(...), + '~' => $this->translateIndirectAdjacent(...), ]; } @@ -61,9 +58,6 @@ class CombinationExtension extends AbstractExtension return $xpath->join('/following-sibling::', $combinedXpath); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'combination'; diff --git a/lib/symfony/css-selector/XPath/Extension/FunctionExtension.php b/lib/symfony/css-selector/XPath/Extension/FunctionExtension.php index d3f7222a4..4b9d7bc26 100644 --- a/lib/symfony/css-selector/XPath/Extension/FunctionExtension.php +++ b/lib/symfony/css-selector/XPath/Extension/FunctionExtension.php @@ -30,18 +30,15 @@ use Symfony\Component\CssSelector\XPath\XPathExpr; */ class FunctionExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctionTranslators(): array { return [ - 'nth-child' => [$this, 'translateNthChild'], - 'nth-last-child' => [$this, 'translateNthLastChild'], - 'nth-of-type' => [$this, 'translateNthOfType'], - 'nth-last-of-type' => [$this, 'translateNthLastOfType'], - 'contains' => [$this, 'translateContains'], - 'lang' => [$this, 'translateLang'], + 'nth-child' => $this->translateNthChild(...), + 'nth-last-child' => $this->translateNthLastChild(...), + 'nth-of-type' => $this->translateNthOfType(...), + 'nth-last-of-type' => $this->translateNthLastOfType(...), + 'contains' => $this->translateContains(...), + 'lang' => $this->translateLang(...), ]; } @@ -161,9 +158,6 @@ class FunctionExtension extends AbstractExtension )); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'function'; diff --git a/lib/symfony/css-selector/XPath/Extension/HtmlExtension.php b/lib/symfony/css-selector/XPath/Extension/HtmlExtension.php index 6edc08581..4653add5d 100644 --- a/lib/symfony/css-selector/XPath/Extension/HtmlExtension.php +++ b/lib/symfony/css-selector/XPath/Extension/HtmlExtension.php @@ -36,30 +36,24 @@ class HtmlExtension extends AbstractExtension ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); } - /** - * {@inheritdoc} - */ public function getPseudoClassTranslators(): array { return [ - 'checked' => [$this, 'translateChecked'], - 'link' => [$this, 'translateLink'], - 'disabled' => [$this, 'translateDisabled'], - 'enabled' => [$this, 'translateEnabled'], - 'selected' => [$this, 'translateSelected'], - 'invalid' => [$this, 'translateInvalid'], - 'hover' => [$this, 'translateHover'], - 'visited' => [$this, 'translateVisited'], + 'checked' => $this->translateChecked(...), + 'link' => $this->translateLink(...), + 'disabled' => $this->translateDisabled(...), + 'enabled' => $this->translateEnabled(...), + 'selected' => $this->translateSelected(...), + 'invalid' => $this->translateInvalid(...), + 'hover' => $this->translateHover(...), + 'visited' => $this->translateVisited(...), ]; } - /** - * {@inheritdoc} - */ public function getFunctionTranslators(): array { return [ - 'lang' => [$this, 'translateLang'], + 'lang' => $this->translateLang(...), ]; } @@ -177,9 +171,6 @@ class HtmlExtension extends AbstractExtension return $xpath->addCondition('0'); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'html'; diff --git a/lib/symfony/css-selector/XPath/Extension/NodeExtension.php b/lib/symfony/css-selector/XPath/Extension/NodeExtension.php index aa6f3f704..49e894ade 100644 --- a/lib/symfony/css-selector/XPath/Extension/NodeExtension.php +++ b/lib/symfony/css-selector/XPath/Extension/NodeExtension.php @@ -31,7 +31,7 @@ class NodeExtension extends AbstractExtension public const ATTRIBUTE_NAME_IN_LOWER_CASE = 2; public const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4; - private $flags; + private int $flags; public function __construct(int $flags = 0) { @@ -41,7 +41,7 @@ class NodeExtension extends AbstractExtension /** * @return $this */ - public function setFlag(int $flag, bool $on): self + public function setFlag(int $flag, bool $on): static { if ($on && !$this->hasFlag($flag)) { $this->flags += $flag; @@ -59,21 +59,18 @@ class NodeExtension extends AbstractExtension return (bool) ($this->flags & $flag); } - /** - * {@inheritdoc} - */ public function getNodeTranslators(): array { return [ - 'Selector' => [$this, 'translateSelector'], - 'CombinedSelector' => [$this, 'translateCombinedSelector'], - 'Negation' => [$this, 'translateNegation'], - 'Function' => [$this, 'translateFunction'], - 'Pseudo' => [$this, 'translatePseudo'], - 'Attribute' => [$this, 'translateAttribute'], - 'Class' => [$this, 'translateClass'], - 'Hash' => [$this, 'translateHash'], - 'Element' => [$this, 'translateElement'], + 'Selector' => $this->translateSelector(...), + 'CombinedSelector' => $this->translateCombinedSelector(...), + 'Negation' => $this->translateNegation(...), + 'Function' => $this->translateFunction(...), + 'Pseudo' => $this->translatePseudo(...), + 'Attribute' => $this->translateAttribute(...), + 'Class' => $this->translateClass(...), + 'Hash' => $this->translateHash(...), + 'Element' => $this->translateElement(...), ]; } @@ -182,9 +179,6 @@ class NodeExtension extends AbstractExtension return $xpath; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'node'; diff --git a/lib/symfony/css-selector/XPath/Extension/PseudoClassExtension.php b/lib/symfony/css-selector/XPath/Extension/PseudoClassExtension.php index a50b0486a..aada83291 100644 --- a/lib/symfony/css-selector/XPath/Extension/PseudoClassExtension.php +++ b/lib/symfony/css-selector/XPath/Extension/PseudoClassExtension.php @@ -26,20 +26,18 @@ use Symfony\Component\CssSelector\XPath\XPathExpr; */ class PseudoClassExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getPseudoClassTranslators(): array { return [ - 'root' => [$this, 'translateRoot'], - 'first-child' => [$this, 'translateFirstChild'], - 'last-child' => [$this, 'translateLastChild'], - 'first-of-type' => [$this, 'translateFirstOfType'], - 'last-of-type' => [$this, 'translateLastOfType'], - 'only-child' => [$this, 'translateOnlyChild'], - 'only-of-type' => [$this, 'translateOnlyOfType'], - 'empty' => [$this, 'translateEmpty'], + 'root' => $this->translateRoot(...), + 'scope' => $this->translateScopePseudo(...), + 'first-child' => $this->translateFirstChild(...), + 'last-child' => $this->translateLastChild(...), + 'first-of-type' => $this->translateFirstOfType(...), + 'last-of-type' => $this->translateLastOfType(...), + 'only-child' => $this->translateOnlyChild(...), + 'only-of-type' => $this->translateOnlyOfType(...), + 'empty' => $this->translateEmpty(...), ]; } @@ -48,6 +46,11 @@ class PseudoClassExtension extends AbstractExtension return $xpath->addCondition('not(parent::*)'); } + public function translateScopePseudo(XPathExpr $xpath): XPathExpr + { + return $xpath->addCondition('1'); + } + public function translateFirstChild(XPathExpr $xpath): XPathExpr { return $xpath @@ -112,9 +115,6 @@ class PseudoClassExtension extends AbstractExtension return $xpath->addCondition('not(*) and not(string-length())'); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'pseudo-class'; diff --git a/lib/symfony/css-selector/XPath/Translator.php b/lib/symfony/css-selector/XPath/Translator.php index 8ce473036..83e855b5c 100644 --- a/lib/symfony/css-selector/XPath/Translator.php +++ b/lib/symfony/css-selector/XPath/Translator.php @@ -30,23 +30,23 @@ use Symfony\Component\CssSelector\Parser\ParserInterface; */ class Translator implements TranslatorInterface { - private $mainParser; + private ParserInterface $mainParser; /** * @var ParserInterface[] */ - private $shortcutParsers = []; + private array $shortcutParsers = []; /** * @var Extension\ExtensionInterface[] */ - private $extensions = []; + private array $extensions = []; - private $nodeTranslators = []; - private $combinationTranslators = []; - private $functionTranslators = []; - private $pseudoClassTranslators = []; - private $attributeMatchingTranslators = []; + private array $nodeTranslators = []; + private array $combinationTranslators = []; + private array $functionTranslators = []; + private array $pseudoClassTranslators = []; + private array $attributeMatchingTranslators = []; public function __construct(ParserInterface $parser = null) { @@ -87,9 +87,6 @@ class Translator implements TranslatorInterface return sprintf('concat(%s)', implode(', ', $parts)); } - /** - * {@inheritdoc} - */ public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string { $selectors = $this->parseSelectors($cssExpr); @@ -106,9 +103,6 @@ class Translator implements TranslatorInterface return implode(' | ', $selectors); } - /** - * {@inheritdoc} - */ public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string { return ($prefix ?: '').$this->nodeToXPath($selector); @@ -117,7 +111,7 @@ class Translator implements TranslatorInterface /** * @return $this */ - public function registerExtension(Extension\ExtensionInterface $extension): self + public function registerExtension(Extension\ExtensionInterface $extension): static { $this->extensions[$extension->getName()] = $extension; @@ -145,7 +139,7 @@ class Translator implements TranslatorInterface /** * @return $this */ - public function registerParserShortcut(ParserInterface $shortcut): self + public function registerParserShortcut(ParserInterface $shortcut): static { $this->shortcutParsers[] = $shortcut; @@ -220,7 +214,7 @@ class Translator implements TranslatorInterface foreach ($this->shortcutParsers as $shortcut) { $tokens = $shortcut->parse($css); - if (!empty($tokens)) { + if ($tokens) { return $tokens; } } diff --git a/lib/symfony/css-selector/XPath/XPathExpr.php b/lib/symfony/css-selector/XPath/XPathExpr.php index e45ce7d8c..a76e30bec 100644 --- a/lib/symfony/css-selector/XPath/XPathExpr.php +++ b/lib/symfony/css-selector/XPath/XPathExpr.php @@ -23,9 +23,9 @@ namespace Symfony\Component\CssSelector\XPath; */ class XPathExpr { - private $path; - private $element; - private $condition; + private string $path; + private string $element; + private string $condition; public function __construct(string $path = '', string $element = '*', string $condition = '', bool $starPrefix = false) { @@ -46,7 +46,7 @@ class XPathExpr /** * @return $this */ - public function addCondition(string $condition): self + public function addCondition(string $condition): static { $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; @@ -61,7 +61,7 @@ class XPathExpr /** * @return $this */ - public function addNameTest(): self + public function addNameTest(): static { if ('*' !== $this->element) { $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); @@ -74,7 +74,7 @@ class XPathExpr /** * @return $this */ - public function addStarPrefix(): self + public function addStarPrefix(): static { $this->path .= '*/'; @@ -86,7 +86,7 @@ class XPathExpr * * @return $this */ - public function join(string $combiner, self $expr): self + public function join(string $combiner, self $expr): static { $path = $this->__toString().$combiner; diff --git a/lib/symfony/css-selector/composer.json b/lib/symfony/css-selector/composer.json index f0b712495..c08fdc2cd 100644 --- a/lib/symfony/css-selector/composer.json +++ b/lib/symfony/css-selector/composer.json @@ -20,8 +20,7 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "autoload": { "psr-4": { "Symfony\\Component\\CssSelector\\": "" }, diff --git a/lib/symfony/dependency-injection/Alias.php b/lib/symfony/dependency-injection/Alias.php index 71bfef51e..c5b91edf0 100644 --- a/lib/symfony/dependency-injection/Alias.php +++ b/lib/symfony/dependency-injection/Alias.php @@ -17,9 +17,9 @@ class Alias { private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%alias_id%" service alias is deprecated. You should stop using it, as it will be removed in the future.'; - private $id; - private $public; - private $deprecation = []; + private string $id; + private bool $public; + private array $deprecation = []; public function __construct(string $id, bool $public = false) { @@ -29,10 +29,8 @@ class Alias /** * Checks if this DI Alias should be public or not. - * - * @return bool */ - public function isPublic() + public function isPublic(): bool { return $this->public; } @@ -42,33 +40,17 @@ class Alias * * @return $this */ - public function setPublic(bool $boolean) + public function setPublic(bool $boolean): static { $this->public = $boolean; return $this; } - /** - * Sets if this Alias is private. - * - * @return $this - * - * @deprecated since Symfony 5.2, use setPublic() instead - */ - public function setPrivate(bool $boolean) - { - trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__); - - return $this->setPublic(!$boolean); - } - /** * Whether this alias is private. - * - * @return bool */ - public function isPrivate() + public function isPrivate(): bool { return !$this->public; } @@ -85,28 +67,8 @@ class Alias * * @throws InvalidArgumentException when the message template is invalid */ - public function setDeprecated(/* string $package, string $version, string $message */) + public function setDeprecated(string $package, string $version, string $message): static { - $args = \func_get_args(); - - if (\func_num_args() < 3) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); - - $status = $args[0] ?? true; - - if (!$status) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); - } - - $message = (string) ($args[1] ?? null); - $package = $version = ''; - } else { - $status = true; - $package = (string) $args[0]; - $version = (string) $args[1]; - $message = (string) $args[2]; - } - if ('' !== $message) { if (preg_match('#[\r\n]|\*/#', $message)) { throw new InvalidArgumentException('Invalid characters found in deprecation template.'); @@ -117,7 +79,7 @@ class Alias } } - $this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE] : []; + $this->deprecation = ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE]; return $this; } @@ -127,16 +89,6 @@ class Alias return (bool) $this->deprecation; } - /** - * @deprecated since Symfony 5.1, use "getDeprecation()" instead. - */ - public function getDeprecationMessage(string $id): string - { - trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); - - return $this->getDeprecation($id)['message']; - } - /** * @param string $id Service id relying on this definition */ @@ -149,12 +101,7 @@ class Alias ]; } - /** - * Returns the Id of this alias. - * - * @return string - */ - public function __toString() + public function __toString(): string { return $this->id; } diff --git a/lib/symfony/dependency-injection/Argument/AbstractArgument.php b/lib/symfony/dependency-injection/Argument/AbstractArgument.php index 3ba5ff33b..b04f9b848 100644 --- a/lib/symfony/dependency-injection/Argument/AbstractArgument.php +++ b/lib/symfony/dependency-injection/Argument/AbstractArgument.php @@ -16,8 +16,8 @@ namespace Symfony\Component\DependencyInjection\Argument; */ final class AbstractArgument { - private $text; - private $context; + private string $text; + private string $context = ''; public function __construct(string $text = '') { diff --git a/lib/symfony/dependency-injection/Argument/ArgumentInterface.php b/lib/symfony/dependency-injection/Argument/ArgumentInterface.php index b46eb77be..3b39f3662 100644 --- a/lib/symfony/dependency-injection/Argument/ArgumentInterface.php +++ b/lib/symfony/dependency-injection/Argument/ArgumentInterface.php @@ -18,10 +18,10 @@ namespace Symfony\Component\DependencyInjection\Argument; */ interface ArgumentInterface { - /** - * @return array - */ - public function getValues(); + public function getValues(): array; + /** + * @return void + */ public function setValues(array $values); } diff --git a/lib/symfony/dependency-injection/Argument/BoundArgument.php b/lib/symfony/dependency-injection/Argument/BoundArgument.php index c2afe2cfa..be24e20af 100644 --- a/lib/symfony/dependency-injection/Argument/BoundArgument.php +++ b/lib/symfony/dependency-injection/Argument/BoundArgument.php @@ -20,15 +20,15 @@ final class BoundArgument implements ArgumentInterface public const DEFAULTS_BINDING = 1; public const INSTANCEOF_BINDING = 2; - private static $sequence = 0; + private static int $sequence = 0; - private $value; - private $identifier; - private $used; - private $type; - private $file; + private mixed $value; + private ?int $identifier = null; + private ?bool $used = null; + private int $type; + private ?string $file; - public function __construct($value, bool $trackUsage = true, int $type = 0, string $file = null) + public function __construct(mixed $value, bool $trackUsage = true, int $type = 0, string $file = null) { $this->value = $value; if ($trackUsage) { @@ -40,18 +40,12 @@ final class BoundArgument implements ArgumentInterface $this->file = $file; } - /** - * {@inheritdoc} - */ public function getValues(): array { return [$this->value, $this->identifier, $this->used, $this->type, $this->file]; } - /** - * {@inheritdoc} - */ - public function setValues(array $values) + public function setValues(array $values): void { if (5 === \count($values)) { [$this->value, $this->identifier, $this->used, $this->type, $this->file] = $values; diff --git a/lib/symfony/dependency-injection/Argument/IteratorArgument.php b/lib/symfony/dependency-injection/Argument/IteratorArgument.php index d413678a1..aedd1e659 100644 --- a/lib/symfony/dependency-injection/Argument/IteratorArgument.php +++ b/lib/symfony/dependency-injection/Argument/IteratorArgument.php @@ -18,5 +18,23 @@ namespace Symfony\Component\DependencyInjection\Argument; */ class IteratorArgument implements ArgumentInterface { - use ReferenceSetArgumentTrait; + private array $values; + + public function __construct(array $values) + { + $this->setValues($values); + } + + public function getValues(): array + { + return $this->values; + } + + /** + * @return void + */ + public function setValues(array $values) + { + $this->values = $values; + } } diff --git a/lib/symfony/dependency-injection/Argument/LazyClosure.php b/lib/symfony/dependency-injection/Argument/LazyClosure.php new file mode 100644 index 000000000..230363a95 --- /dev/null +++ b/lib/symfony/dependency-injection/Argument/LazyClosure.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class LazyClosure +{ + public readonly object $service; + + public function __construct( + private \Closure $initializer, + ) { + unset($this->service); + } + + public function __get(mixed $name): mixed + { + if ('service' !== $name) { + throw new InvalidArgumentException(sprintf('Cannot read property "%s" from a lazy closure.', $name)); + } + + if (isset($this->initializer)) { + $this->service = ($this->initializer)(); + unset($this->initializer); + } + + return $this->service; + } + + public static function getCode(string $initializer, array $callable, Definition $definition, ContainerBuilder $container, ?string $id): string + { + $method = $callable[1]; + $asClosure = 'Closure' === ($definition->getClass() ?: 'Closure'); + + if ($asClosure) { + $class = ($callable[0] instanceof Reference ? $container->findDefinition($callable[0]) : $callable[0])->getClass(); + } else { + $class = $definition->getClass(); + } + + $r = $container->getReflectionClass($class); + + if (null !== $id) { + $id = sprintf(' for service "%s"', $id); + } + + if (!$asClosure) { + $id = str_replace('%', '%%', (string) $id); + + if (!$r || !$r->isInterface()) { + throw new RuntimeException(sprintf("Cannot create adapter{$id} because \"%s\" is not an interface.", $class)); + } + if (1 !== \count($method = $r->getMethods())) { + throw new RuntimeException(sprintf("Cannot create adapter{$id} because interface \"%s\" doesn't have exactly one method.", $class)); + } + $method = $method[0]->name; + } elseif (!$r || !$r->hasMethod($method)) { + throw new RuntimeException("Cannot create lazy closure{$id} because its corresponding callable is invalid."); + } + + $methodReflector = $r->getMethod($method); + $code = ProxyHelper::exportSignature($methodReflector, true, $args); + + if ($asClosure) { + $code = ' { '.preg_replace('/: static$/', ': \\'.$r->name, $code); + } else { + $code = ' implements \\'.$r->name.' { '.$code; + } + + $code = 'new class('.$initializer.') extends \\'.self::class + .$code.' { '.($methodReflector->hasReturnType() && 'void' === (string) $methodReflector->getReturnType() ? '' : 'return ').'$this->service->'.$callable[1].'('.$args.'); } ' + .'}'; + + return $asClosure ? '('.$code.')->'.$method.'(...)' : $code; + } +} diff --git a/lib/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php b/lib/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php index 150c9bf57..293d9a0a1 100644 --- a/lib/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php +++ b/lib/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php @@ -11,16 +11,20 @@ namespace Symfony\Component\DependencyInjection\Argument; +trigger_deprecation('symfony/dependency-injection', '6.1', '"%s" is deprecated.', ReferenceSetArgumentTrait::class); + use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; /** * @author Titouan Galopin * @author Nicolas Grekas + * + * @deprecated since Symfony 6.1 */ trait ReferenceSetArgumentTrait { - private $values; + private array $values; /** * @param Reference[] $values @@ -33,13 +37,15 @@ trait ReferenceSetArgumentTrait /** * @return Reference[] */ - public function getValues() + public function getValues(): array { return $this->values; } /** * @param Reference[] $values The service references to put in the set + * + * @return void */ public function setValues(array $values) { diff --git a/lib/symfony/dependency-injection/Argument/RewindableGenerator.php b/lib/symfony/dependency-injection/Argument/RewindableGenerator.php index 41fec786f..9fee3743a 100644 --- a/lib/symfony/dependency-injection/Argument/RewindableGenerator.php +++ b/lib/symfony/dependency-injection/Argument/RewindableGenerator.php @@ -16,16 +16,13 @@ namespace Symfony\Component\DependencyInjection\Argument; */ class RewindableGenerator implements \IteratorAggregate, \Countable { - private $generator; - private $count; + private \Closure $generator; + private \Closure|int $count; - /** - * @param int|callable $count - */ - public function __construct(callable $generator, $count) + public function __construct(callable $generator, int|callable $count) { - $this->generator = $generator; - $this->count = $count; + $this->generator = $generator(...); + $this->count = \is_int($count) ? $count : $count(...); } public function getIterator(): \Traversable @@ -37,7 +34,7 @@ class RewindableGenerator implements \IteratorAggregate, \Countable public function count(): int { - if (\is_callable($count = $this->count)) { + if (!\is_int($count = $this->count)) { $this->count = $count(); } diff --git a/lib/symfony/dependency-injection/Argument/ServiceClosureArgument.php b/lib/symfony/dependency-injection/Argument/ServiceClosureArgument.php index 6331affa4..be86412bc 100644 --- a/lib/symfony/dependency-injection/Argument/ServiceClosureArgument.php +++ b/lib/symfony/dependency-injection/Argument/ServiceClosureArgument.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection\Argument; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -use Symfony\Component\DependencyInjection\Reference; /** * Represents a service wrapped in a memoizing closure. @@ -21,28 +20,25 @@ use Symfony\Component\DependencyInjection\Reference; */ class ServiceClosureArgument implements ArgumentInterface { - private $values; + private array $values; - public function __construct(Reference $reference) + public function __construct(mixed $value) { - $this->values = [$reference]; + $this->values = [$value]; } - /** - * {@inheritdoc} - */ - public function getValues() + public function getValues(): array { return $this->values; } /** - * {@inheritdoc} + * @return void */ public function setValues(array $values) { - if ([0] !== array_keys($values) || !($values[0] instanceof Reference || null === $values[0])) { - throw new InvalidArgumentException('A ServiceClosureArgument must hold one and only one Reference.'); + if ([0] !== array_keys($values)) { + throw new InvalidArgumentException('A ServiceClosureArgument must hold one and only one value.'); } $this->values = $values; diff --git a/lib/symfony/dependency-injection/Argument/ServiceLocator.php b/lib/symfony/dependency-injection/Argument/ServiceLocator.php index bc138fe23..e58293489 100644 --- a/lib/symfony/dependency-injection/Argument/ServiceLocator.php +++ b/lib/symfony/dependency-injection/Argument/ServiceLocator.php @@ -20,9 +20,9 @@ use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; */ class ServiceLocator extends BaseServiceLocator { - private $factory; - private $serviceMap; - private $serviceTypes; + private \Closure $factory; + private array $serviceMap; + private ?array $serviceTypes; public function __construct(\Closure $factory, array $serviceMap, array $serviceTypes = null) { @@ -32,21 +32,17 @@ class ServiceLocator extends BaseServiceLocator parent::__construct($serviceMap); } - /** - * {@inheritdoc} - * - * @return mixed - */ - public function get(string $id) + public function get(string $id): mixed { - return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id); + return match (\count($this->serviceMap[$id] ?? [])) { + 0 => parent::get($id), + 1 => $this->serviceMap[$id][0], + default => ($this->factory)(...$this->serviceMap[$id]), + }; } - /** - * {@inheritdoc} - */ public function getProvidedServices(): array { - return $this->serviceTypes ?? $this->serviceTypes = array_map(function () { return '?'; }, $this->serviceMap); + return $this->serviceTypes ??= array_map(fn () => '?', $this->serviceMap); } } diff --git a/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php b/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php index fcbf478c6..de533fcca 100644 --- a/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php +++ b/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php @@ -11,8 +11,6 @@ namespace Symfony\Component\DependencyInjection\Argument; -use Symfony\Component\DependencyInjection\Reference; - /** * Represents a closure acting as a service locator. * @@ -20,25 +18,34 @@ use Symfony\Component\DependencyInjection\Reference; */ class ServiceLocatorArgument implements ArgumentInterface { - use ReferenceSetArgumentTrait; + private array $values; + private ?TaggedIteratorArgument $taggedIteratorArgument = null; - private $taggedIteratorArgument; - - /** - * @param Reference[]|TaggedIteratorArgument $values - */ - public function __construct($values = []) + public function __construct(array|TaggedIteratorArgument $values = []) { if ($values instanceof TaggedIteratorArgument) { $this->taggedIteratorArgument = $values; - $this->values = []; - } else { - $this->setValues($values); + $values = []; } + + $this->setValues($values); } public function getTaggedIteratorArgument(): ?TaggedIteratorArgument { return $this->taggedIteratorArgument; } + + public function getValues(): array + { + return $this->values; + } + + /** + * @return void + */ + public function setValues(array $values) + { + $this->values = $values; + } } diff --git a/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php b/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php index 1ba8de790..b4e982c45 100644 --- a/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php +++ b/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php @@ -18,11 +18,13 @@ namespace Symfony\Component\DependencyInjection\Argument; */ class TaggedIteratorArgument extends IteratorArgument { - private $tag; - private $indexAttribute; - private $defaultIndexMethod; - private $defaultPriorityMethod; - private $needsIndexes = false; + private string $tag; + private mixed $indexAttribute; + private ?string $defaultIndexMethod; + private ?string $defaultPriorityMethod; + private bool $needsIndexes; + private array $exclude; + private bool $excludeSelf = true; /** * @param string $tag The name of the tag identifying the target services @@ -30,8 +32,10 @@ class TaggedIteratorArgument extends IteratorArgument * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute * @param bool $needsIndexes Whether indexes are required and should be generated when computing the map * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute + * @param array $exclude Services to exclude from the iterator + * @param bool $excludeSelf Whether to automatically exclude the referencing service from the iterator */ - public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null) + public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = [], bool $excludeSelf = true) { parent::__construct([]); @@ -44,8 +48,13 @@ class TaggedIteratorArgument extends IteratorArgument $this->defaultIndexMethod = $defaultIndexMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Name' : null); $this->needsIndexes = $needsIndexes; $this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Priority' : null); + $this->exclude = $exclude; + $this->excludeSelf = $excludeSelf; } + /** + * @return string + */ public function getTag() { return $this->tag; @@ -70,4 +79,14 @@ class TaggedIteratorArgument extends IteratorArgument { return $this->defaultPriorityMethod; } + + public function getExclude(): array + { + return $this->exclude; + } + + public function excludeSelf(): bool + { + return $this->excludeSelf; + } } diff --git a/lib/symfony/dependency-injection/Attribute/AsAlias.php b/lib/symfony/dependency-injection/Attribute/AsAlias.php new file mode 100644 index 000000000..806895989 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AsAlias.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell under which alias a service should be registered or to use the implemented interface if no parameter is given. + * + * @author Alan Poulain + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +final class AsAlias +{ + public function __construct( + public ?string $id = null, + public bool $public = false, + ) { + } +} diff --git a/lib/symfony/dependency-injection/Attribute/AsDecorator.php b/lib/symfony/dependency-injection/Attribute/AsDecorator.php new file mode 100644 index 000000000..0f80c16e9 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AsDecorator.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsDecorator +{ + public function __construct( + public string $decorates, + public int $priority = 0, + public int $onInvalid = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, + ) { + } +} diff --git a/lib/symfony/dependency-injection/Attribute/Autoconfigure.php b/lib/symfony/dependency-injection/Attribute/Autoconfigure.php index abab04010..4560ed696 100644 --- a/lib/symfony/dependency-injection/Attribute/Autoconfigure.php +++ b/lib/symfony/dependency-injection/Attribute/Autoconfigure.php @@ -29,6 +29,7 @@ class Autoconfigure public ?bool $autowire = null, public ?array $properties = null, public array|string|null $configurator = null, + public ?string $constructor = null, ) { } } diff --git a/lib/symfony/dependency-injection/Attribute/Autowire.php b/lib/symfony/dependency-injection/Attribute/Autowire.php new file mode 100644 index 000000000..c17eb1370 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/Autowire.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * Attribute to tell a parameter how to be autowired. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class Autowire +{ + public readonly string|array|Expression|Reference|ArgumentInterface|null $value; + public readonly bool|array $lazy; + + /** + * Use only ONE of the following. + * + * @param string|array|ArgumentInterface|null $value Value to inject (ie "%kernel.project_dir%/some/path") + * @param string|null $service Service ID (ie "some.service") + * @param string|null $expression Expression (ie 'service("some.service").someMethod()') + * @param string|null $env Environment variable name (ie 'SOME_ENV_VARIABLE') + * @param string|null $param Parameter name (ie 'some.parameter.name') + * @param bool|class-string|class-string[] $lazy Whether to use lazy-loading for this argument + */ + public function __construct( + string|array|ArgumentInterface $value = null, + string $service = null, + string $expression = null, + string $env = null, + string $param = null, + bool|string|array $lazy = false, + ) { + if ($this->lazy = \is_string($lazy) ? [$lazy] : $lazy) { + if (null !== ($expression ?? $env ?? $param)) { + throw new LogicException('#[Autowire] attribute cannot be $lazy and use $expression, $env, or $param.'); + } + if (null !== $value && null !== $service) { + throw new LogicException('#[Autowire] attribute cannot declare $value and $service at the same time.'); + } + } elseif (!(null !== $value xor null !== $service xor null !== $expression xor null !== $env xor null !== $param)) { + throw new LogicException('#[Autowire] attribute must declare exactly one of $service, $expression, $env, $param or $value.'); + } + + if (\is_string($value) && str_starts_with($value, '@')) { + match (true) { + str_starts_with($value, '@@') => $value = substr($value, 1), + str_starts_with($value, '@=') => $expression = substr($value, 2), + default => $service = substr($value, 1), + }; + } + + $this->value = match (true) { + null !== $service => new Reference($service), + null !== $expression => class_exists(Expression::class) ? new Expression($expression) : throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'), + null !== $env => "%env($env)%", + null !== $param => "%$param%", + default => $value, + }; + } +} diff --git a/lib/symfony/dependency-injection/Attribute/AutowireCallable.php b/lib/symfony/dependency-injection/Attribute/AutowireCallable.php new file mode 100644 index 000000000..87e119746 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AutowireCallable.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Attribute to tell which callable to give to an argument of type Closure. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireCallable extends Autowire +{ + /** + * @param bool|class-string $lazy Whether to use lazy-loading for this argument + */ + public function __construct( + string|array $callable = null, + string $service = null, + string $method = null, + bool|string $lazy = false, + ) { + if (!(null !== $callable xor null !== $service)) { + throw new LogicException('#[AutowireCallable] attribute must declare exactly one of $callable or $service.'); + } + if (null === $service && null !== $method) { + throw new LogicException('#[AutowireCallable] attribute cannot have a $method without a $service.'); + } + + parent::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy); + } + + public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition + { + return (new Definition($type = \is_string($this->lazy) ? $this->lazy : ($type ?: 'Closure'))) + ->setFactory(['Closure', 'fromCallable']) + ->setArguments([\is_array($value) ? $value + [1 => '__invoke'] : $value]) + ->setLazy($this->lazy || 'Closure' !== $type && 'callable' !== (string) $parameter->getType()); + } +} diff --git a/lib/symfony/dependency-injection/Attribute/AutowireDecorated.php b/lib/symfony/dependency-injection/Attribute/AutowireDecorated.php new file mode 100644 index 000000000..ed8f33e00 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AutowireDecorated.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireDecorated +{ +} diff --git a/lib/symfony/dependency-injection/Attribute/AutowireIterator.php b/lib/symfony/dependency-injection/Attribute/AutowireIterator.php new file mode 100644 index 000000000..b81bd8f92 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AutowireIterator.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; + +/** + * Autowires an iterator of services based on a tag name. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireIterator extends Autowire +{ + /** + * @param string|string[] $exclude A service or a list of services to exclude + */ + public function __construct( + string $tag, + string $indexAttribute = null, + string $defaultIndexMethod = null, + string $defaultPriorityMethod = null, + string|array $exclude = [], + bool $excludeSelf = true, + ) { + parent::__construct(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf)); + } +} diff --git a/lib/symfony/dependency-injection/Attribute/AutowireLocator.php b/lib/symfony/dependency-injection/Attribute/AutowireLocator.php new file mode 100644 index 000000000..a60a76960 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AutowireLocator.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\SubscribedService; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * Autowires a service locator based on a tag name or an explicit list of key => service-type pairs. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireLocator extends Autowire +{ + /** + * @see ServiceSubscriberInterface::getSubscribedServices() + * + * @param string|array $services An explicit list of services or a tag name + * @param string|string[] $exclude A service or a list of services to exclude + */ + public function __construct( + string|array $services, + string $indexAttribute = null, + string $defaultIndexMethod = null, + string $defaultPriorityMethod = null, + string|array $exclude = [], + bool $excludeSelf = true, + ) { + if (\is_string($services)) { + parent::__construct(new ServiceLocatorArgument(new TaggedIteratorArgument($services, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf))); + + return; + } + + $references = []; + + foreach ($services as $key => $type) { + $attributes = []; + + if ($type instanceof Autowire) { + $references[$key] = $type; + continue; + } + + if ($type instanceof SubscribedService) { + $key = $type->key ?? $key; + $attributes = $type->attributes; + $type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s" is used, a type must be set.', SubscribedService::class))); + } + + if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { + throw new InvalidArgumentException(sprintf('"%s" is not a PHP type for key "%s".', \is_string($type) ? $type : get_debug_type($type), $key)); + } + $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('?' === $type[0]) { + $type = substr($type, 1); + $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } + if (\is_int($name = $key)) { + $key = $type; + $name = null; + } + + $references[$key] = new TypedReference($type, $type, $optionalBehavior, $name, $attributes); + } + + parent::__construct(new ServiceLocatorArgument($references)); + } +} diff --git a/lib/symfony/dependency-injection/Attribute/AutowireServiceClosure.php b/lib/symfony/dependency-injection/Attribute/AutowireServiceClosure.php new file mode 100644 index 000000000..a468414a4 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AutowireServiceClosure.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Attribute to wrap a service in a closure that returns it. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireServiceClosure extends Autowire +{ + public function __construct(string $service) + { + parent::__construct(new ServiceClosureArgument(new Reference($service))); + } +} diff --git a/lib/symfony/dependency-injection/Attribute/Exclude.php b/lib/symfony/dependency-injection/Attribute/Exclude.php new file mode 100644 index 000000000..43efdcf1a --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/Exclude.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell the class should not be registered as service. + * + * @author GrĂ©goire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class Exclude +{ +} diff --git a/lib/symfony/dependency-injection/Attribute/MapDecorated.php b/lib/symfony/dependency-injection/Attribute/MapDecorated.php new file mode 100644 index 000000000..d63f0567c --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/MapDecorated.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +trigger_deprecation('symfony/dependency-injection', '6.3', 'The "%s" class is deprecated, use "%s" instead.', MapDecorated::class, AutowireDecorated::class); + +/** + * @deprecated since Symfony 6.3, use AutowireDecorated instead + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapDecorated +{ +} diff --git a/lib/symfony/dependency-injection/Attribute/TaggedIterator.php b/lib/symfony/dependency-injection/Attribute/TaggedIterator.php index d498f4647..dce969bd2 100644 --- a/lib/symfony/dependency-injection/Attribute/TaggedIterator.php +++ b/lib/symfony/dependency-injection/Attribute/TaggedIterator.php @@ -12,13 +12,16 @@ namespace Symfony\Component\DependencyInjection\Attribute; #[\Attribute(\Attribute::TARGET_PARAMETER)] -class TaggedIterator +class TaggedIterator extends AutowireIterator { public function __construct( public string $tag, public ?string $indexAttribute = null, public ?string $defaultIndexMethod = null, public ?string $defaultPriorityMethod = null, + public string|array $exclude = [], + public bool $excludeSelf = true, ) { + parent::__construct($tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf); } } diff --git a/lib/symfony/dependency-injection/Attribute/TaggedLocator.php b/lib/symfony/dependency-injection/Attribute/TaggedLocator.php index 4617e0f51..15fb62d1c 100644 --- a/lib/symfony/dependency-injection/Attribute/TaggedLocator.php +++ b/lib/symfony/dependency-injection/Attribute/TaggedLocator.php @@ -12,13 +12,16 @@ namespace Symfony\Component\DependencyInjection\Attribute; #[\Attribute(\Attribute::TARGET_PARAMETER)] -class TaggedLocator +class TaggedLocator extends AutowireLocator { public function __construct( public string $tag, public ?string $indexAttribute = null, public ?string $defaultIndexMethod = null, public ?string $defaultPriorityMethod = null, + public string|array $exclude = [], + public bool $excludeSelf = true, ) { + parent::__construct($tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf); } } diff --git a/lib/symfony/dependency-injection/Attribute/Target.php b/lib/symfony/dependency-injection/Attribute/Target.php index a7a4d8b5f..c3f22127b 100644 --- a/lib/symfony/dependency-injection/Attribute/Target.php +++ b/lib/symfony/dependency-injection/Attribute/Target.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Attribute; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; /** * An attribute to tell how a dependency is used and hint named autowiring aliases. @@ -21,25 +22,32 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; #[\Attribute(\Attribute::TARGET_PARAMETER)] final class Target { - /** - * @var string - */ - public $name; - - public function __construct(string $name) - { - $this->name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name)))); + public function __construct( + public ?string $name = null, + ) { } - public static function parseName(\ReflectionParameter $parameter): string + public function getParsedName(): string { - if (80000 > \PHP_VERSION_ID || !$target = $parameter->getAttributes(self::class)[0] ?? null) { + if (null === $this->name) { + throw new LogicException(sprintf('Cannot parse the name of a #[Target] attribute that has not been resolved. Did you forget to call "%s::parseName()"?', __CLASS__)); + } + + return lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->name)))); + } + + public static function parseName(\ReflectionParameter $parameter, self &$attribute = null): string + { + $attribute = null; + if (!$target = $parameter->getAttributes(self::class)[0] ?? null) { return $parameter->name; } - $name = $target->newInstance()->name; + $attribute = $target->newInstance(); + $name = $attribute->name ??= $parameter->name; + $parsedName = $attribute->getParsedName(); - if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) { + if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $parsedName)) { if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { $function = $function->class.'::'.$function->name; } else { @@ -49,6 +57,6 @@ final class Target throw new InvalidArgumentException(sprintf('Invalid #[Target] name "%s" on parameter "$%s" of "%s()": the first character must be a letter.', $name, $parameter->name, $function)); } - return $name; + return $parsedName; } } diff --git a/lib/symfony/dependency-injection/Attribute/When.php b/lib/symfony/dependency-injection/Attribute/When.php index 60b7af04b..302b7b050 100644 --- a/lib/symfony/dependency-injection/Attribute/When.php +++ b/lib/symfony/dependency-injection/Attribute/When.php @@ -12,7 +12,7 @@ namespace Symfony\Component\DependencyInjection\Attribute; /** - * An attribute to tell under which environement this class should be registered as a service. + * An attribute to tell under which environment this class should be registered as a service. * * @author Nicolas Grekas */ diff --git a/lib/symfony/dependency-injection/CHANGELOG.md b/lib/symfony/dependency-injection/CHANGELOG.md index 88a6df8df..0f38ac86c 100644 --- a/lib/symfony/dependency-injection/CHANGELOG.md +++ b/lib/symfony/dependency-injection/CHANGELOG.md @@ -1,6 +1,75 @@ CHANGELOG ========= +6.4 +--- + + * Allow using `#[Target]` with no arguments to state that a parameter must match a named autowiring alias + * Deprecate `ContainerAwareInterface` and `ContainerAwareTrait`, use dependency injection instead + * Add `defined` env var processor that returns `true` for defined and neither null nor empty env vars + * Add `#[AutowireLocator]` and `#[AutowireIterator]` attributes + +6.3 +--- + + * Add options `inline_factories` and `inline_class_loader` to `PhpDumper::dump()` + * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter` + * Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation + * Add support for nesting autowiring-related attributes into `#[Autowire(...)]` + * Deprecate undefined and numeric keys with `service_locator` config + * Fail if Target attribute does not exist during compilation + * Enable deprecating parameters with `ContainerBuilder::deprecateParameter()` + * Add `#[AsAlias]` attribute to tell under which alias a service should be registered or to use the implemented interface if no parameter is given + * Allow to trim XML service parameters value by using `trim="true"` attribute + * Allow extending the `Autowire` attribute + * Add `#[Exclude]` to skip autoregistering a class + * Add support for generating lazy closures + * Add support for autowiring services as closures using `#[AutowireCallable]` or `#[AutowireServiceClosure]` + * Add support for `#[Autowire(lazy: true|class-string)]` + * Make it possible to cast callables into single-method interfaces + * Deprecate `#[MapDecorated]`, use `#[AutowireDecorated]` instead + * Deprecate the `@required` annotation, use the `Symfony\Contracts\Service\Attribute\Required` attribute instead + * Add `constructor` option to services declaration and to `#[Autoconfigure]` + +6.2 +--- + + * Use lazy-loading ghost objects and virtual proxies out of the box + * Add arguments `&$asGhostObject` and `$id` to LazyProxy's `DumperInterface` to allow using ghost objects for lazy loading services + * Add `enum` env var processor + * Add `shuffle` env var processor + * Allow #[When] to be extended + * Change the signature of `ContainerAwareInterface::setContainer()` to `setContainer(?ContainerInterface)` + * Deprecate calling `ContainerAwareTrait::setContainer()` without arguments + * Deprecate using numeric parameter names + * Add support for tagged iterators/locators `exclude` option to the xml and yaml loaders/dumpers + * Allow injecting `string $env` into php config closures + * Add `excludeSelf` parameter to `TaggedIteratorArgument` with default value to `true` + to control whether the referencing service should be automatically excluded from the iterator + +6.1 +--- + + * Add `#[MapDecorated]` attribute telling to which parameter the decorated service should be mapped in a decorator + * Add `#[AsDecorator]` attribute to make a service decorates another + * Add `$exclude` to `TaggedIterator` and `TaggedLocator` attributes + * Add `$exclude` to `tagged_iterator` and `tagged_locator` configurator + * Add an `env` function to the expression language provider + * Add an `Autowire` attribute to tell a parameter how to be autowired + * Allow using expressions as service factories + * Add argument type `closure` to help passing closures to services + * Deprecate `ReferenceSetArgumentTrait` + * Add `AbstractExtension` class for DI configuration/definition on a single file + +6.0 +--- + + * Remove `Definition::setPrivate()` and `Alias::setPrivate()`, use `setPublic()` instead + * Remove `inline()` in favor of `inline_service()` and `ref()` in favor of `service()` when using the PHP-DSL + * Remove `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead + * Remove `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead + * Remove the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service + 5.4 --- * Add `$defaultIndexMethod` and `$defaultPriorityMethod` to `TaggedIterator` and `TaggedLocator` attributes @@ -89,7 +158,7 @@ CHANGELOG * added `%env(trim:...)%` processor to trim a string value * added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%` - * added `%env(url:...)%` processor to convert an URL or DNS into an array of components + * added `%env(url:...)%` processor to convert a URL or DNS into an array of components * added `%env(query_string:...)%` processor to convert a query string into an array of key values * added support for deprecating aliases * made `ContainerParametersResource` final and not implement `Serializable` anymore diff --git a/lib/symfony/dependency-injection/ChildDefinition.php b/lib/symfony/dependency-injection/ChildDefinition.php index 5c648ba61..c5905a401 100644 --- a/lib/symfony/dependency-injection/ChildDefinition.php +++ b/lib/symfony/dependency-injection/ChildDefinition.php @@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; */ class ChildDefinition extends Definition { - private $parent; + private string $parent; /** * @param string $parent The id of Definition instance to decorate @@ -33,10 +33,8 @@ class ChildDefinition extends Definition /** * Returns the Definition to inherit from. - * - * @return string */ - public function getParent() + public function getParent(): string { return $this->parent; } @@ -46,7 +44,7 @@ class ChildDefinition extends Definition * * @return $this */ - public function setParent(string $parent) + public function setParent(string $parent): static { $this->parent = $parent; @@ -59,13 +57,9 @@ class ChildDefinition extends Definition * If replaceArgument() has been used to replace an argument, this method * will return the replacement value. * - * @param int|string $index - * - * @return mixed - * * @throws OutOfBoundsException When the argument does not exist */ - public function getArgument($index) + public function getArgument(int|string $index): mixed { if (\array_key_exists('index_'.$index, $this->arguments)) { return $this->arguments['index_'.$index]; @@ -82,14 +76,11 @@ class ChildDefinition extends Definition * certain conventions when you want to overwrite the arguments of the * parent definition, otherwise your arguments will only be appended. * - * @param int|string $index - * @param mixed $value - * * @return $this * * @throws InvalidArgumentException when $index isn't an integer */ - public function replaceArgument($index, $value) + public function replaceArgument(int|string $index, mixed $value): static { if (\is_int($index)) { $this->arguments['index_'.$index] = $value; diff --git a/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php b/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php index 362c5f571..f18baa57c 100644 --- a/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php +++ b/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php @@ -31,13 +31,14 @@ abstract class AbstractRecursivePass implements CompilerPassInterface */ protected $container; protected $currentId; + protected bool $skipScalars = false; - private $processExpressions = false; - private $expressionLanguage; - private $inExpression = false; + private bool $processExpressions = false; + private ExpressionLanguage $expressionLanguage; + private bool $inExpression = false; /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -50,6 +51,9 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } } + /** + * @return void + */ protected function enableExpressionProcessing() { $this->processExpressions = true; @@ -68,15 +72,19 @@ abstract class AbstractRecursivePass implements CompilerPassInterface /** * Processes a value found in a definition tree. * - * @param mixed $value - * * @return mixed */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false) { if (\is_array($value)) { foreach ($value as $k => $v) { + if ((!$v || \is_scalar($v)) && $this->skipScalars) { + continue; + } if ($isRoot) { + if ($v->hasTag('container.excluded')) { + continue; + } $this->currentId = $k; } if ($v !== $processedValue = $this->processValue($v, $isRoot)) { @@ -86,7 +94,7 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } elseif ($value instanceof ArgumentInterface) { $value->setValues($this->processValue($value->getValues())); } elseif ($value instanceof Expression && $this->processExpressions) { - $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']); + $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container', 'args' => 'args']); } elseif ($value instanceof Definition) { $value->setArguments($this->processValue($value->getArguments())); $value->setProperties($this->processValue($value->getProperties())); @@ -94,7 +102,16 @@ abstract class AbstractRecursivePass implements CompilerPassInterface $changes = $value->getChanges(); if (isset($changes['factory'])) { - $value->setFactory($this->processValue($value->getFactory())); + if (\is_string($factory = $value->getFactory()) && str_starts_with($factory, '@=')) { + if (!class_exists(Expression::class)) { + throw new LogicException('Expressions cannot be used in service factories without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + $factory = new Expression(substr($factory, 2)); + } + if (($factory = $this->processValue($factory)) instanceof Expression) { + $factory = '@='.$factory; + } + $value->setFactory($factory); } if (isset($changes['configurator'])) { $value->setConfigurator($this->processValue($value->getConfigurator())); @@ -105,17 +122,19 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } /** - * @return \ReflectionFunctionAbstract|null - * * @throws RuntimeException */ - protected function getConstructor(Definition $definition, bool $required) + protected function getConstructor(Definition $definition, bool $required): ?\ReflectionFunctionAbstract { if ($definition->isSynthetic()) { return null; } if (\is_string($factory = $definition->getFactory())) { + if (str_starts_with($factory, '@=')) { + return new \ReflectionFunction(static function (...$args) {}); + } + if (!\function_exists($factory)) { throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory)); } @@ -141,8 +160,8 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } } elseif ($class instanceof Definition) { $class = $class->getClass(); - } elseif (null === $class) { - $class = $definition->getClass(); + } else { + $class ??= $definition->getClass(); } return $this->getReflectionMethod(new Definition($class), $method); @@ -176,10 +195,8 @@ abstract class AbstractRecursivePass implements CompilerPassInterface /** * @throws RuntimeException - * - * @return \ReflectionFunctionAbstract */ - protected function getReflectionMethod(Definition $definition, string $method) + protected function getReflectionMethod(Definition $definition, string $method): \ReflectionFunctionAbstract { if ('__construct' === $method) { return $this->getConstructor($definition, true); @@ -215,7 +232,7 @@ abstract class AbstractRecursivePass implements CompilerPassInterface private function getExpressionLanguage(): ExpressionLanguage { - if (null === $this->expressionLanguage) { + if (!isset($this->expressionLanguage)) { if (!class_exists(ExpressionLanguage::class)) { throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } diff --git a/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php b/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php index 8d3fefe75..7aa7ec2ad 100644 --- a/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php +++ b/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php @@ -17,43 +17,19 @@ use Symfony\Component\DependencyInjection\Reference; final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass { - private $tagName; + protected bool $skipScalars = true; - private $aliases = []; + private array $aliases = []; - public function __construct(string $tagName = 'container.private') + public function process(ContainerBuilder $container): void { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->tagName = $tagName; - } - - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) - { - if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) { - return new Reference($this->aliases[$id], $value->getInvalidBehavior()); - } - - return parent::processValue($value, $isRoot); - } - - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) { + foreach ($container->findTaggedServiceIds('container.private') as $id => $tags) { if (null === $package = $tags[0]['package'] ?? null) { - throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id)); + throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "container.private" tag on the "%s" service.', $id)); } if (null === $version = $tags[0]['version'] ?? null) { - throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id)); + throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "container.private" tag on the "%s" service.', $id)); } $definition = $container->getDefinition($id); @@ -62,7 +38,7 @@ final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass } $container - ->setAlias($id, $aliasId = '.'.$this->tagName.'.'.$id) + ->setAlias($id, $aliasId = '.container.private.'.$id) ->setPublic(true) ->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.'); @@ -73,4 +49,13 @@ final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass parent::process($container); } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) { + return new Reference($this->aliases[$id], $value->getInvalidBehavior()); + } + + return parent::processValue($value, $isRoot); + } } diff --git a/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php b/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php index b23303581..4fea73217 100644 --- a/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php +++ b/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php @@ -16,7 +16,9 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; /** * Run this pass before passes that need to know more about the relation of @@ -30,15 +32,17 @@ use Symfony\Component\DependencyInjection\Reference; */ class AnalyzeServiceReferencesPass extends AbstractRecursivePass { - private $graph; - private $currentDefinition; - private $onlyConstructorArguments; - private $hasProxyDumper; - private $lazy; - private $byConstructor; - private $byFactory; - private $definitions; - private $aliases; + protected bool $skipScalars = true; + + private ServiceReferenceGraph $graph; + private ?Definition $currentDefinition = null; + private bool $onlyConstructorArguments; + private bool $hasProxyDumper; + private bool $lazy; + private bool $byConstructor; + private bool $byFactory; + private array $definitions; + private array $aliases; /** * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls @@ -52,6 +56,8 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass /** * Processes a ContainerBuilder object to populate the service reference graph. + * + * @return void */ public function process(ContainerBuilder $container) { @@ -76,7 +82,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass } } - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { $lazy = $this->lazy; $inExpression = $this->inExpression(); @@ -98,7 +104,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass $targetId, $targetDefinition, $value, - $this->lazy || ($this->hasProxyDumper && $targetDefinition && $targetDefinition->isLazy()), + $this->lazy || ($this->hasProxyDumper && $targetDefinition?->isLazy()), ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(), $this->byConstructor ); @@ -110,7 +116,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass $targetId, $targetDefinition, $value, - $this->lazy || ($targetDefinition && $targetDefinition->isLazy()), + $this->lazy || $targetDefinition?->isLazy(), true ); } @@ -135,8 +141,16 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass $byFactory = $this->byFactory; $this->byFactory = true; - $this->processValue($value->getFactory()); + if (\is_string($factory = $value->getFactory()) && str_starts_with($factory, '@=')) { + if (!class_exists(Expression::class)) { + throw new LogicException('Expressions cannot be used in service factories without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + + $factory = new Expression(substr($factory, 2)); + } + $this->processValue($factory); $this->byFactory = $byFactory; + $this->processValue($value->getArguments()); $properties = $value->getProperties(); diff --git a/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php b/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php index 4db7185cf..cb428565e 100644 --- a/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php +++ b/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php @@ -22,19 +22,21 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ final class AttributeAutoconfigurationPass extends AbstractRecursivePass { - private $classAttributeConfigurators = []; - private $methodAttributeConfigurators = []; - private $propertyAttributeConfigurators = []; - private $parameterAttributeConfigurators = []; + protected bool $skipScalars = true; + + private array $classAttributeConfigurators = []; + private array $methodAttributeConfigurators = []; + private array $propertyAttributeConfigurators = []; + private array $parameterAttributeConfigurators = []; public function process(ContainerBuilder $container): void { - if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) { + if (!$container->getAutoconfiguredAttributes()) { return; } foreach ($container->getAutoconfiguredAttributes() as $attributeName => $callable) { - $callableReflector = new \ReflectionFunction(\Closure::fromCallable($callable)); + $callableReflector = new \ReflectionFunction($callable(...)); if ($callableReflector->getNumberOfParameters() <= 2) { $this->classAttributeConfigurators[$attributeName] = $callable; continue; @@ -55,7 +57,7 @@ final class AttributeAutoconfigurationPass extends AbstractRecursivePass try { $attributeReflector = new \ReflectionClass($attributeName); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { continue; } @@ -78,7 +80,7 @@ final class AttributeAutoconfigurationPass extends AbstractRecursivePass parent::process($container); } - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof Definition || !$value->isAutoconfigured() @@ -103,7 +105,7 @@ final class AttributeAutoconfigurationPass extends AbstractRecursivePass if ($this->parameterAttributeConfigurators) { try { $constructorReflector = $this->getConstructor($value, false); - } catch (RuntimeException $e) { + } catch (RuntimeException) { $constructorReflector = null; } @@ -120,7 +122,7 @@ final class AttributeAutoconfigurationPass extends AbstractRecursivePass if ($this->methodAttributeConfigurators || $this->parameterAttributeConfigurators) { foreach ($classReflector->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflector) { - if ($methodReflector->isStatic() || $methodReflector->isConstructor() || $methodReflector->isDestructor()) { + if ($methodReflector->isConstructor() || $methodReflector->isDestructor()) { continue; } diff --git a/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php b/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php index b150e70e6..3f070dcc0 100644 --- a/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php +++ b/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php @@ -20,10 +20,8 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; */ class AutoAliasServicePass implements CompilerPassInterface { - private $privateAliases = []; - /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -37,24 +35,8 @@ class AutoAliasServicePass implements CompilerPassInterface if ($container->hasDefinition($aliasId) || $container->hasAlias($aliasId)) { $alias = new Alias($aliasId, $container->getDefinition($serviceId)->isPublic()); $container->setAlias($serviceId, $alias); - - if (!$alias->isPublic()) { - $alias->setPublic(true); - $this->privateAliases[] = $alias; - } } } } } - - /** - * @internal to be removed in Symfony 6.0 - */ - public function getPrivateAliases(): array - { - $privateAliases = $this->privateAliases; - $this->privateAliases = []; - - return $privateAliases; - } } diff --git a/lib/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php b/lib/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php new file mode 100644 index 000000000..1e812c700 --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Reads #[AsDecorator] attributes on definitions that are autowired + * and don't have the "container.ignore_attributes" tag. + */ +final class AutowireAsDecoratorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $definition) { + if ($this->accept($definition) && $reflectionClass = $container->getReflectionClass($definition->getClass(), false)) { + $this->processClass($definition, $reflectionClass); + } + } + } + + private function accept(Definition $definition): bool + { + return !$definition->hasTag('container.ignore_attributes') && $definition->isAutowired(); + } + + private function processClass(Definition $definition, \ReflectionClass $reflectionClass): void + { + foreach ($reflectionClass->getAttributes(AsDecorator::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + $attribute = $attribute->newInstance(); + + $definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid); + } + } +} diff --git a/lib/symfony/dependency-injection/Compiler/AutowirePass.php b/lib/symfony/dependency-injection/Compiler/AutowirePass.php index c2b80770c..ee3ba948b 100644 --- a/lib/symfony/dependency-injection/Compiler/AutowirePass.php +++ b/lib/symfony/dependency-injection/Compiler/AutowirePass.php @@ -12,17 +12,20 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\Config\Resource\ClassExistenceResource; -use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; -use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; -use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; -use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; +use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Symfony\Component\DependencyInjection\Attribute\MapDecorated; use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\VarExporter\ProxyHelper; /** * Inspects existing service definitions and wires the autowired ones using the type hints of their classes. @@ -32,20 +35,21 @@ use Symfony\Component\DependencyInjection\TypedReference; */ class AutowirePass extends AbstractRecursivePass { - private $types; - private $ambiguousServiceTypes; - private $autowiringAliases; - private $lastFailure; - private $throwOnAutowiringException; - private $decoratedClass; - private $decoratedId; - private $methodCalls; - private $defaultArgument; - private $getPreviousValue; - private $decoratedMethodIndex; - private $decoratedMethodArgumentIndex; - private $typesClone; - private $combinedAliases; + protected bool $skipScalars = true; + + private array $types; + private array $ambiguousServiceTypes; + private array $autowiringAliases; + private ?string $lastFailure = null; + private bool $throwOnAutowiringException; + private ?string $decoratedClass = null; + private ?string $decoratedId = null; + private ?array $methodCalls = null; + private object $defaultArgument; + private ?\Closure $getPreviousValue = null; + private ?int $decoratedMethodIndex = null; + private ?int $decoratedMethodArgumentIndex = null; + private ?self $typesClone = null; public function __construct(bool $throwOnAutowireException = true) { @@ -53,15 +57,24 @@ class AutowirePass extends AbstractRecursivePass $this->defaultArgument = new class() { public $value; public $names; + public $bag; + + public function withValue(\ReflectionParameter $parameter): self + { + $clone = clone $this; + $clone->value = $this->bag->escapeValue($parameter->getDefaultValue()); + + return $clone; + } }; } /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { - $this->populateCombinedAliases($container); + $this->defaultArgument->bag = $container->getParameterBag(); try { $this->typesClone = clone $this; @@ -70,20 +83,27 @@ class AutowirePass extends AbstractRecursivePass $this->decoratedClass = null; $this->decoratedId = null; $this->methodCalls = null; + $this->defaultArgument->bag = null; $this->defaultArgument->names = null; $this->getPreviousValue = null; $this->decoratedMethodIndex = null; $this->decoratedMethodArgumentIndex = null; $this->typesClone = null; - $this->combinedAliases = []; } } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { + if ($value instanceof Autowire) { + return $this->processValue($this->container->getParameterBag()->resolveValue($value->value)); + } + + if ($value instanceof AutowireDecorated || $value instanceof MapDecorated) { + $definition = $this->container->getDefinition($this->currentId); + + return new Reference($definition->innerServiceId ?? $this->currentId.'.inner', $definition->decorationOnInvalid ?? ContainerInterface::NULL_ON_INVALID_REFERENCE); + } + try { return $this->doProcessValue($value, $isRoot); } catch (AutowiringFailedException $e) { @@ -97,12 +117,23 @@ class AutowirePass extends AbstractRecursivePass } } - /** - * @return mixed - */ - private function doProcessValue($value, bool $isRoot = false) + private function doProcessValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof TypedReference) { + foreach ($value->getAttributes() as $attribute) { + if ($attribute === $v = $this->processValue($attribute)) { + continue; + } + if (!$attribute instanceof Autowire || !$v instanceof Reference) { + return $v; + } + + $invalidBehavior = ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE !== $v->getInvalidBehavior() ? $v->getInvalidBehavior() : $value->getInvalidBehavior(); + $value = $v instanceof TypedReference + ? new TypedReference($v, $v->getType(), $invalidBehavior, $v->getName() ?? $value->getName(), array_merge($v->getAttributes(), $value->getAttributes())) + : new TypedReference($v, $value->getType(), $invalidBehavior, $value->getName(), $value->getAttributes()); + break; + } if ($ref = $this->getAutowiredReference($value, true)) { return $ref; } @@ -139,7 +170,7 @@ class AutowirePass extends AbstractRecursivePass array_unshift($this->methodCalls, [$constructor, $value->getArguments()]); } - $checkAttributes = 80000 <= \PHP_VERSION_ID && !$value->hasTag('container.ignore_attributes'); + $checkAttributes = !$value->hasTag('container.ignore_attributes'); $this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes); if ($constructor) { @@ -204,13 +235,17 @@ class AutowirePass extends AbstractRecursivePass unset($arguments[$j]); $arguments[$namedArguments[$j]] = $value; } - if ($namedArguments || !$value instanceof $this->defaultArgument) { + if (!$value instanceof $this->defaultArgument) { continue; } - if (\PHP_VERSION_ID >= 80100 && (\is_array($value->value) ? $value->value : \is_object($value->value))) { + if (\is_array($value->value) ? $value->value : \is_object($value->value)) { unset($arguments[$j]); $namedArguments = $value->names; + } + + if ($namedArguments) { + unset($arguments[$j]); } else { $arguments[$j] = $value->value; } @@ -240,29 +275,95 @@ class AutowirePass extends AbstractRecursivePass foreach ($parameters as $index => $parameter) { $this->defaultArgument->names[$index] = $parameter->name; + if (\array_key_exists($parameter->name, $arguments)) { + $arguments[$index] = $arguments[$parameter->name]; + unset($arguments[$parameter->name]); + } if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) { continue; } - $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true); + $type = ProxyHelper::exportType($parameter, true); + $target = null; + $name = Target::parseName($parameter, $target); + $target = $target ? [$target] : []; - if ($checkAttributes) { - foreach ($parameter->getAttributes() as $attribute) { - if (TaggedIterator::class === $attribute->getName()) { - $attribute = $attribute->newInstance(); - $arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, false, $attribute->defaultPriorityMethod); - break; - } + $getValue = function () use ($type, $parameter, $class, $method, $name, $target) { + if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target), false)) { + $failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); - if (TaggedLocator::class === $attribute->getName()) { - $attribute = $attribute->newInstance(); - $arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, true, $attribute->defaultPriorityMethod)); - break; + if ($parameter->isDefaultValueAvailable()) { + $value = $this->defaultArgument->withValue($parameter); + } elseif (!$parameter->allowsNull()) { + throw new AutowiringFailedException($this->currentId, $failureMessage); } } - if ('' !== ($arguments[$index] ?? '')) { - continue; + return $value; + }; + + if ($checkAttributes) { + foreach ($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + $attribute = $attribute->newInstance(); + $invalidBehavior = $parameter->allowsNull() ? ContainerInterface::NULL_ON_INVALID_REFERENCE : ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE; + + try { + $value = $this->processValue(new TypedReference($type ?: '?', $type ?: 'mixed', $invalidBehavior, $name, [$attribute, ...$target])); + } catch (ParameterNotFoundException $e) { + if (!$parameter->isDefaultValueAvailable()) { + throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); + } + $arguments[$index] = clone $this->defaultArgument; + $arguments[$index]->value = $parameter->getDefaultValue(); + + continue 2; + } + + if ($attribute instanceof AutowireCallable) { + $value = $attribute->buildDefinition($value, $type, $parameter); + } elseif ($lazy = $attribute->lazy) { + $definition = (new Definition($type)) + ->setFactory('current') + ->setArguments([[$value ??= $getValue()]]) + ->setLazy(true); + + if (!\is_array($lazy)) { + if (str_contains($type, '|')) { + throw new AutowiringFailedException($this->currentId, sprintf('Cannot use #[Autowire] with option "lazy: true" on union types for service "%s"; set the option to the interface(s) that should be proxied instead.', $this->currentId)); + } + $lazy = str_contains($type, '&') ? explode('&', $type) : []; + } + + if ($lazy) { + if (!class_exists($type) && !interface_exists($type, false)) { + $definition->setClass('object'); + } + foreach ($lazy as $v) { + $definition->addTag('proxy', ['interface' => $v]); + } + } + + if ($definition->getClass() !== (string) $value || $definition->getTag('proxy')) { + $value .= '.'.$this->container->hash([$definition->getClass(), $definition->getTag('proxy')]); + } + $this->container->setDefinition($value = '.lazy.'.$value, $definition); + $value = new Reference($value); + } + $arguments[$index] = $value; + + continue 2; + } + + foreach ($parameter->getAttributes(AutowireDecorated::class) as $attribute) { + $arguments[$index] = $this->processValue($attribute->newInstance()); + + continue 2; + } + + foreach ($parameter->getAttributes(MapDecorated::class) as $attribute) { + $arguments[$index] = $this->processValue($attribute->newInstance()); + + continue 2; } } @@ -280,35 +381,19 @@ class AutowirePass extends AbstractRecursivePass --$index; break; } - $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false); - $type = $type ? sprintf('is type-hinted "%s"', ltrim($type, '\\')) : 'has no type-hint'; + $type = ProxyHelper::exportType($parameter); + $type = $type ? sprintf('is type-hinted "%s"', preg_replace('/(^|[(|&])\\\\|^\?\\\\?/', '\1', $type)) : 'has no type-hint'; throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type)); } // specifically pass the default value - $arguments[$index] = clone $this->defaultArgument; - $arguments[$index]->value = $parameter->getDefaultValue(); + $arguments[$index] = $this->defaultArgument->withValue($parameter); continue; } - $getValue = function () use ($type, $parameter, $class, $method) { - if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), true)) { - $failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); - - if ($parameter->isDefaultValueAvailable()) { - $value = clone $this->defaultArgument; - $value->value = $parameter->getDefaultValue(); - } elseif (!$parameter->allowsNull()) { - throw new AutowiringFailedException($this->currentId, $failureMessage); - } - } - - return $value; - }; - - if ($this->decoratedClass && $isDecorated = is_a($this->decoratedClass, $type, true)) { + if ($this->decoratedClass && is_a($this->decoratedClass, $type, true)) { if ($this->getPreviousValue) { // The inner service is injected only if there is only 1 argument matching the type of the decorated class // across all arguments of all autowired methods. @@ -341,7 +426,7 @@ class AutowirePass extends AbstractRecursivePass // it's possible index 1 was set, then index 0, then 2, etc // make sure that we re-order so they're injected as expected - ksort($arguments); + ksort($arguments, \SORT_NATURAL); return $arguments; } @@ -366,29 +451,37 @@ class AutowirePass extends AbstractRecursivePass $type = implode($m[0], $types); } - if (null !== $name = $reference->getName()) { - if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) { + $name = $target = (array_filter($reference->getAttributes(), static fn ($a) => $a instanceof Target)[0] ?? null)?->name; + + if (null !== $name ??= $reference->getName()) { + $parsedName = (new Target($name))->getParsedName(); + + if ($this->container->has($alias = $type.' $'.$parsedName) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } - if (null !== ($alias = $this->combinedAliases[$alias] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { + if (null !== ($alias = $this->getCombinedAlias($type, $parsedName) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) { - foreach ($this->container->getAliases() + $this->combinedAliases as $id => $alias) { + foreach ($this->container->getAliases() as $id => $alias) { if ($name === (string) $alias && str_starts_with($id, $type.' $')) { return new TypedReference($name, $type, $reference->getInvalidBehavior()); } } } + + if (null !== $target) { + return null; + } } if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) { return new TypedReference($type, $type, $reference->getInvalidBehavior()); } - if (null !== ($alias = $this->combinedAliases[$type] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { + if (null !== ($alias = $this->getCombinedAlias($type) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } @@ -398,7 +491,7 @@ class AutowirePass extends AbstractRecursivePass /** * Populates the list of available types. */ - private function populateAvailableTypes(ContainerBuilder $container) + private function populateAvailableTypes(ContainerBuilder $container): void { $this->types = []; $this->ambiguousServiceTypes = []; @@ -408,15 +501,17 @@ class AutowirePass extends AbstractRecursivePass $this->populateAvailableType($container, $id, $definition); } + $prev = null; foreach ($container->getAliases() as $id => $alias) { - $this->populateAutowiringAlias($id); + $this->populateAutowiringAlias($id, $prev); + $prev = $id; } } /** * Populates the list of available types for a given definition. */ - private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition) + private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition): void { // Never use abstract services if ($definition->isAbstract()) { @@ -441,7 +536,7 @@ class AutowirePass extends AbstractRecursivePass /** * Associates a type and a service id if applicable. */ - private function set(string $type, string $id) + private function set(string $type, string $id): void { // is this already a type/class that is known to match multiple services? if (isset($this->ambiguousServiceTypes[$type])) { @@ -467,7 +562,7 @@ class AutowirePass extends AbstractRecursivePass private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label): \Closure { - if (null === $this->typesClone->container) { + if (!isset($this->typesClone->container)) { $this->typesClone->container = new ContainerBuilder($this->container->getParameterBag()); $this->typesClone->container->setAliases($this->container->getAliases()); $this->typesClone->container->setDefinitions($this->container->getDefinitions()); @@ -475,29 +570,49 @@ class AutowirePass extends AbstractRecursivePass } $currentId = $this->currentId; - return (function () use ($reference, $label, $currentId) { - return $this->createTypeNotFoundMessage($reference, $label, $currentId); - })->bindTo($this->typesClone); + return (fn () => $this->createTypeNotFoundMessage($reference, $label, $currentId))->bindTo($this->typesClone); } private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId): string { - if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) { + $type = $reference->getType(); + + $i = null; + $namespace = $type; + do { + $namespace = substr($namespace, 0, $i); + + if ($this->container->hasDefinition($namespace) && $tag = $this->container->getDefinition($namespace)->getTag('container.excluded')) { + return sprintf('Cannot autowire service "%s": %s needs an instance of "%s" but this type has been excluded %s.', $currentId, $label, $type, $tag[0]['source'] ?? 'from autowiring'); + } + } while (false !== $i = strrpos($namespace, '\\')); + + if (!$r = $this->container->getReflectionClass($type, false)) { // either $type does not exist or a parent class does not exist try { - $resource = new ClassExistenceResource($type, false); - // isFresh() will explode ONLY if a parent class/trait does not exist - $resource->isFresh(0); - $parentMsg = false; + if (class_exists(ClassExistenceResource::class)) { + $resource = new ClassExistenceResource($type, false); + // isFresh() will explode ONLY if a parent class/trait does not exist + $resource->isFresh(0); + $parentMsg = false; + } else { + $parentMsg = "couldn't be loaded. Either it was not found or it is missing a parent class or a trait"; + } } catch (\ReflectionException $e) { - $parentMsg = $e->getMessage(); + $parentMsg = sprintf('is missing a parent class (%s)', $e->getMessage()); } - $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); + $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ?: 'was not found'); } else { $alternatives = $this->createTypeAlternatives($this->container, $reference); - $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; - $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); + + if (null !== $target = (array_filter($reference->getAttributes(), static fn ($a) => $a instanceof Target)[0] ?? null)) { + $target = null !== $target->name ? "('{$target->name}')" : ''; + $message = sprintf('has "#[Target%s]" but no such target exists.%s', $target, $alternatives); + } else { + $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; + $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); + } if ($r->isInterface() && !$alternatives) { $message .= ' Did you create a class that implements this interface?'; @@ -520,13 +635,16 @@ class AutowirePass extends AbstractRecursivePass if ($message = $this->getAliasesSuggestionForType($container, $type = $reference->getType())) { return ' '.$message; } - if (null === $this->ambiguousServiceTypes) { + if (!isset($this->ambiguousServiceTypes)) { $this->populateAvailableTypes($container); } $servicesAndAliases = $container->getServiceIds(); - if (null !== ($autowiringAliases = $this->autowiringAliases[$type] ?? null) && !isset($autowiringAliases[''])) { - return sprintf(' Available autowiring aliases for this %s are: "$%s".', class_exists($type, false) ? 'class' : 'interface', implode('", "$', $autowiringAliases)); + $autowiringAliases = $this->autowiringAliases[$type] ?? []; + unset($autowiringAliases['']); + + if ($autowiringAliases) { + return sprintf(' Did you mean to target%s "%s" instead?', 1 < \count($autowiringAliases) ? ' one of' : '', implode('", "', $autowiringAliases)); } if (!$container->has($type) && false !== $key = array_search(strtolower($type), array_map('strtolower', $servicesAndAliases))) { @@ -568,7 +686,7 @@ class AutowirePass extends AbstractRecursivePass return null; } - private function populateAutowiringAlias(string $id): void + private function populateAutowiringAlias(string $id, string $target = null): void { if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) { return; @@ -578,48 +696,41 @@ class AutowirePass extends AbstractRecursivePass $name = $m[3] ?? ''; if (class_exists($type, false) || interface_exists($type, false)) { + if (null !== $target && str_starts_with($target, '.'.$type.' $') + && (new Target($target = substr($target, \strlen($type) + 3)))->getParsedName() === $name + ) { + $name = $target; + } + $this->autowiringAliases[$type][$name] = $name; } } - private function populateCombinedAliases(ContainerBuilder $container): void + private function getCombinedAlias(string $type, string $name = null): ?string { - $this->combinedAliases = []; - $reverseAliases = []; - - foreach ($container->getAliases() as $id => $alias) { - if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) { - continue; - } - - $type = $m[2]; - $name = $m[3] ?? ''; - $reverseAliases[(string) $alias][$name][] = $type; + if (str_contains($type, '&')) { + $types = explode('&', $type); + } elseif (str_contains($type, '|')) { + $types = explode('|', $type); + } else { + return null; } - foreach ($reverseAliases as $alias => $names) { - foreach ($names as $name => $types) { - if (2 > $count = \count($types)) { - continue; - } - sort($types); - $i = 1 << $count; + $alias = null; + $suffix = $name ? ' $'.$name : ''; - // compute the powerset of the list of types - while ($i--) { - $set = []; - for ($j = 0; $j < $count; ++$j) { - if ($i & (1 << $j)) { - $set[] = $types[$j]; - } - } + foreach ($types as $type) { + if (!$this->container->hasAlias($type.$suffix)) { + return null; + } - if (2 <= \count($set)) { - $this->combinedAliases[implode('&', $set).('' === $name ? '' : ' $'.$name)] = $alias; - $this->combinedAliases[implode('|', $set).('' === $name ? '' : ' $'.$name)] = $alias; - } - } + if (null === $alias) { + $alias = (string) $this->container->getAlias($type.$suffix); + } elseif ((string) $this->container->getAlias($type.$suffix) !== $alias) { + return null; } } + + return $alias; } } diff --git a/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php b/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php index 5c255cfb6..dcc04eabd 100644 --- a/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php +++ b/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php @@ -15,16 +15,15 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Contracts\Service\Attribute\Required; /** - * Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters. + * Looks for definitions with autowiring enabled and registers their corresponding "#[Required]" methods as setters. * * @author Nicolas Grekas */ class AutowireRequiredMethodsPass extends AbstractRecursivePass { - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { $value = parent::processValue($value, $isRoot); @@ -50,7 +49,7 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass } while (true) { - if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) { + if ($r->getAttributes(Required::class)) { if ($this->isWither($r, $r->getDocComment() ?: '')) { $withers[] = [$r->name, [], true]; } else { @@ -60,6 +59,8 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass } if (false !== $doc = $r->getDocComment()) { if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Relying on the "@required" annotation on method "%s::%s()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.', $reflectionMethod->class, $reflectionMethod->name); + if ($this->isWither($reflectionMethod, $doc)) { $withers[] = [$reflectionMethod->name, [], true]; } else { @@ -73,7 +74,7 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass } try { $r = $r->getPrototype(); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { break; // method has no prototype } } diff --git a/lib/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php b/lib/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php index 52024b807..568211008 100644 --- a/lib/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php +++ b/lib/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php @@ -17,21 +17,17 @@ use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Contracts\Service\Attribute\Required; /** - * Looks for definitions with autowiring enabled and registers their corresponding "@required" properties. + * Looks for definitions with autowiring enabled and registers their corresponding "#[Required]" properties. * * @author Sebastien Morel (Plopix) * @author Nicolas Grekas */ class AutowireRequiredPropertiesPass extends AbstractRecursivePass { - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { - if (\PHP_VERSION_ID < 70400) { - return $value; - } $value = parent::processValue($value, $isRoot); if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { @@ -46,11 +42,15 @@ class AutowireRequiredPropertiesPass extends AbstractRecursivePass if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) { continue; } - if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class)) + $doc = false; + if (!$reflectionProperty->getAttributes(Required::class) && ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) ) { continue; } + if ($doc) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Using the "@required" annotation on property "%s::$%s" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.', $reflectionProperty->class, $reflectionProperty->name); + } if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) { continue; } diff --git a/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php index 93808b201..8cbd72292 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php @@ -22,17 +22,16 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ class CheckArgumentsValidityPass extends AbstractRecursivePass { - private $throwExceptions; + protected bool $skipScalars = true; + + private bool $throwExceptions; public function __construct(bool $throwExceptions = true) { $this->throwExceptions = $throwExceptions; } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); @@ -41,7 +40,7 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass $i = 0; $hasNamedArgs = false; foreach ($value->getArguments() as $k => $v) { - if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { + if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { $hasNamedArgs = true; continue; } @@ -79,7 +78,7 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass $i = 0; $hasNamedArgs = false; foreach ($methodCall[1] as $k => $v) { - if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { + if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { $hasNamedArgs = true; continue; } diff --git a/lib/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php b/lib/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php index 55d911c4f..1fb8935c3 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php @@ -26,11 +26,13 @@ use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceExce */ class CheckCircularReferencesPass implements CompilerPassInterface { - private $currentPath; - private $checkedNodes; + private array $currentPath; + private array $checkedNodes; /** * Checks the ContainerBuilder object for circular references. + * + * @return void */ public function process(ContainerBuilder $container) { @@ -51,7 +53,7 @@ class CheckCircularReferencesPass implements CompilerPassInterface * * @throws ServiceCircularReferenceException when a circular reference is found */ - private function checkOutEdges(array $edges) + private function checkOutEdges(array $edges): void { foreach ($edges as $edge) { $node = $edge->getDestNode(); diff --git a/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php index 68c42ae48..c62345f26 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php @@ -33,6 +33,8 @@ class CheckDefinitionValidityPass implements CompilerPassInterface /** * Processes the ContainerBuilder to validate the Definition. * + * @return void + * * @throws RuntimeException When the Definition is invalid */ public function process(ContainerBuilder $container) @@ -62,11 +64,7 @@ class CheckDefinitionValidityPass implements CompilerPassInterface // tag attribute values must be scalars foreach ($definition->getTags() as $name => $tags) { foreach ($tags as $attributes) { - foreach ($attributes as $attribute => $value) { - if (!\is_scalar($value) && null !== $value) { - throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute)); - } - } + $this->validateAttributes($id, $name, $attributes); } } @@ -87,4 +85,16 @@ class CheckDefinitionValidityPass implements CompilerPassInterface } } } + + private function validateAttributes(string $id, string $tag, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($id, $tag, $value, [...$path, $name]); + } elseif (!\is_scalar($value) && null !== $value) { + $name = implode('.', [...$path, $name]); + throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $tag, $name)); + } + } + } } diff --git a/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php index fd3173831..7a6dd7687 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -23,10 +23,12 @@ use Symfony\Component\DependencyInjection\Reference; */ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass { - private $serviceLocatorContextIds = []; + protected bool $skipScalars = true; + + private array $serviceLocatorContextIds = []; /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -37,13 +39,13 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass } try { - return parent::process($container); + parent::process($container); } finally { $this->serviceLocatorContextIds = []; } } - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); @@ -90,12 +92,12 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass { $alternatives = []; foreach ($this->container->getServiceIds() as $knownId) { - if ('' === $knownId || '.' === $knownId[0]) { + if ('' === $knownId || '.' === $knownId[0] || $knownId === $this->currentId) { continue; } $lev = levenshtein($id, $knownId); - if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) { + if ($lev <= \strlen($id) / 3 || str_contains($knownId, $id)) { $alternatives[] = $knownId; } } diff --git a/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php index 0349ef761..5c54a6577 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php @@ -25,7 +25,9 @@ use Symfony\Component\DependencyInjection\Reference; */ class CheckReferenceValidityPass extends AbstractRecursivePass { - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) { return $value; diff --git a/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php b/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php index b7ec85cef..4830bad1a 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php @@ -41,6 +41,8 @@ use Symfony\Component\ExpressionLanguage\Expression; */ final class CheckTypeDeclarationsPass extends AbstractRecursivePass { + protected bool $skipScalars = true; + private const SCALAR_TYPES = [ 'int' => true, 'float' => true, @@ -59,10 +61,10 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass 'string' => true, ]; - private $autoload; - private $skippedIds; + private bool $autoload; + private array $skippedIds; - private $expressionLanguage; + private ExpressionLanguage $expressionLanguage; /** * @param bool $autoload Whether services who's class in not loaded should be checked or not. @@ -75,10 +77,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass $this->skippedIds = $skippedIds; } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (isset($this->skippedIds[$this->currentId])) { return $value; @@ -139,11 +138,17 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass $envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null; for ($i = 0; $i < $checksCount; ++$i) { - if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) { + $p = $reflectionParameters[$i]; + if (!$p->hasType() || $p->isVariadic()) { + continue; + } + if (\array_key_exists($p->name, $values)) { + $i = $p->name; + } elseif (!\array_key_exists($i, $values)) { continue; } - $this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i], $envPlaceholderUniquePrefix); + $this->checkType($checkedDefinition, $values[$i], $p, $envPlaceholderUniquePrefix); } if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) { @@ -158,9 +163,9 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass /** * @throws InvalidParameterTypeException When a parameter is not compatible with the declared type */ - private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, \ReflectionType $reflectionType = null): void + private function checkType(Definition $checkedDefinition, mixed $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, \ReflectionType $reflectionType = null): void { - $reflectionType = $reflectionType ?? $parameter->getType(); + $reflectionType ??= $parameter->getType(); if ($reflectionType instanceof \ReflectionUnionType) { foreach ($reflectionType->getTypes() as $t) { @@ -210,7 +215,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass $class = null; if ($value instanceof Definition) { - if ($value->getFactory()) { + if ($value->hasErrors() || $value->getFactory()) { return; } @@ -226,7 +231,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass } elseif ($value instanceof Expression) { try { $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]); - } catch (\Exception $e) { + } catch (\Exception) { // If a service from the expression cannot be fetched from the container, we skip the validation. return; } @@ -241,7 +246,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) { try { $value = $this->container->resolveEnvPlaceholders($value, true); - } catch (\Exception $e) { + } catch (\Exception) { // If an env placeholder cannot be resolved, we skip the validation. return; } @@ -261,7 +266,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass } elseif ($value instanceof ServiceLocatorArgument) { $class = ServiceLocator::class; } elseif (\is_object($value)) { - $class = \get_class($value); + $class = $value::class; } else { $class = \gettype($value); $class = ['integer' => 'int', 'double' => 'float', 'boolean' => 'bool'][$class] ?? $class; @@ -308,6 +313,10 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass if (false === $value) { return; } + } elseif ('true' === $type) { + if (true === $value) { + return; + } } elseif ($reflectionType->isBuiltin()) { $checkFunction = sprintf('is_%s', $type); if ($checkFunction($value)) { @@ -320,10 +329,6 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass private function getExpressionLanguage(): ExpressionLanguage { - if (null === $this->expressionLanguage) { - $this->expressionLanguage = new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders()); - } - - return $this->expressionLanguage; + return $this->expressionLanguage ??= new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders()); } } diff --git a/lib/symfony/dependency-injection/Compiler/Compiler.php b/lib/symfony/dependency-injection/Compiler/Compiler.php index 4c5d003f0..c8cbccb4b 100644 --- a/lib/symfony/dependency-injection/Compiler/Compiler.php +++ b/lib/symfony/dependency-injection/Compiler/Compiler.php @@ -21,9 +21,9 @@ use Symfony\Component\DependencyInjection\Exception\EnvParameterException; */ class Compiler { - private $passConfig; - private $log = []; - private $serviceReferenceGraph; + private PassConfig $passConfig; + private array $log = []; + private ServiceReferenceGraph $serviceReferenceGraph; public function __construct() { @@ -31,22 +31,19 @@ class Compiler $this->serviceReferenceGraph = new ServiceReferenceGraph(); } - /** - * @return PassConfig - */ - public function getPassConfig() + public function getPassConfig(): PassConfig { return $this->passConfig; } - /** - * @return ServiceReferenceGraph - */ - public function getServiceReferenceGraph() + public function getServiceReferenceGraph(): ServiceReferenceGraph { return $this->serviceReferenceGraph; } + /** + * @return void + */ public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { $this->passConfig->addPass($pass, $type, $priority); @@ -54,26 +51,27 @@ class Compiler /** * @final + * + * @return void */ public function log(CompilerPassInterface $pass, string $message) { if (str_contains($message, "\n")) { - $message = str_replace("\n", "\n".\get_class($pass).': ', trim($message)); + $message = str_replace("\n", "\n".$pass::class.': ', trim($message)); } - $this->log[] = \get_class($pass).': '.$message; + $this->log[] = $pass::class.': '.$message; } - /** - * @return array - */ - public function getLog() + public function getLog(): array { return $this->log; } /** * Run the Compiler and process all Passes. + * + * @return void */ public function compile(ContainerBuilder $container) { @@ -90,7 +88,6 @@ class Compiler if ($msg !== $resolvedMsg = $container->resolveEnvPlaceholders($msg, null, $usedEnvs)) { $r = new \ReflectionProperty($prev, 'message'); - $r->setAccessible(true); $r->setValue($prev, $resolvedMsg); } } while ($prev = $prev->getPrevious()); diff --git a/lib/symfony/dependency-injection/Compiler/CompilerPassInterface.php b/lib/symfony/dependency-injection/Compiler/CompilerPassInterface.php index 308500605..2ad4a048b 100644 --- a/lib/symfony/dependency-injection/Compiler/CompilerPassInterface.php +++ b/lib/symfony/dependency-injection/Compiler/CompilerPassInterface.php @@ -22,6 +22,8 @@ interface CompilerPassInterface { /** * You can modify the container here before it is dumped to PHP code. + * + * @return void */ public function process(ContainerBuilder $container); } diff --git a/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php b/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php index 917024917..92e1e2de4 100644 --- a/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php +++ b/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php @@ -27,17 +27,11 @@ use Symfony\Component\DependencyInjection\Reference; */ class DecoratorServicePass extends AbstractRecursivePass { - private $innerId = '.inner'; - - public function __construct(?string $innerId = '.inner') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->innerId = $innerId; - } + protected bool $skipScalars = true; + /** + * @return void + */ public function process(ContainerBuilder $container) { $definitions = new \SplPriorityQueue(); @@ -50,10 +44,11 @@ class DecoratorServicePass extends AbstractRecursivePass $definitions->insert([$id, $definition], [$decorated[2], --$order]); } $decoratingDefinitions = []; + $decoratedIds = []; $tagsToKeep = $container->hasParameter('container.behavior_describing_tags') ? $container->getParameter('container.behavior_describing_tags') - : ['container.do_not_inline', 'container.service_locator', 'container.service_subscriber']; + : ['proxy', 'container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator']; foreach ($definitions as [$id, $definition]) { $decoratedService = $definition->getDecoratedService(); @@ -66,6 +61,7 @@ class DecoratorServicePass extends AbstractRecursivePass $renamedId = $id.'.inner'; } + $decoratedIds[$inner] ??= $renamedId; $this->currentId = $renamedId; $this->processValue($definition); @@ -95,7 +91,7 @@ class DecoratorServicePass extends AbstractRecursivePass throw new ServiceNotFoundException($inner, $id); } - if ($decoratedDefinition && $decoratedDefinition->isSynthetic()) { + if ($decoratedDefinition?->isSynthetic()) { throw new InvalidArgumentException(sprintf('A synthetic service cannot be decorated: service "%s" cannot decorate "%s".', $id, $inner)); } @@ -120,11 +116,15 @@ class DecoratorServicePass extends AbstractRecursivePass $container->setAlias($inner, $id)->setPublic($public); } + + foreach ($decoratingDefinitions as $inner => $definition) { + $definition->addTag('container.decorator', ['id' => $inner, 'inner' => $decoratedIds[$inner]]); + } } - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { - if ($value instanceof Reference && $this->innerId === (string) $value) { + if ($value instanceof Reference && '.inner' === (string) $value) { return new Reference($this->currentId, $value->getInvalidBehavior()); } diff --git a/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php b/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php index 5e7ba3173..b6a2cf907 100644 --- a/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php +++ b/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php @@ -11,6 +11,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -23,34 +25,86 @@ use Symfony\Component\DependencyInjection\Reference; */ class DefinitionErrorExceptionPass extends AbstractRecursivePass { + protected bool $skipScalars = true; + + private array $erroredDefinitions = []; + private array $sourceReferences = []; + /** - * {@inheritdoc} + * @return void */ - protected function processValue($value, bool $isRoot = false) + public function process(ContainerBuilder $container) { - if (!$value instanceof Definition || !$value->hasErrors()) { + try { + parent::process($container); + + $visitedIds = []; + + foreach ($this->erroredDefinitions as $id => $definition) { + if ($this->isErrorForRuntime($id, $visitedIds)) { + continue; + } + + // only show the first error so the user can focus on it + $errors = $definition->getErrors(); + + throw new RuntimeException(reset($errors)); + } + } finally { + $this->erroredDefinitions = []; + $this->sourceReferences = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof ArgumentInterface) { + parent::processValue($value->getValues()); + + return $value; + } + + if ($value instanceof Reference && $this->currentId !== $targetId = (string) $value) { + if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $this->sourceReferences[$targetId][$this->currentId] ??= true; + } else { + $this->sourceReferences[$targetId][$this->currentId] = false; + } + + return $value; + } + + if (!$value instanceof Definition || !$value->hasErrors() || $value->hasTag('container.error')) { return parent::processValue($value, $isRoot); } - if ($isRoot && !$value->isPublic()) { - $graph = $this->container->getCompiler()->getServiceReferenceGraph(); - $runtimeException = false; - foreach ($graph->getNode($this->currentId)->getInEdges() as $edge) { - if (!$edge->getValue() instanceof Reference || ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE !== $edge->getValue()->getInvalidBehavior()) { - $runtimeException = false; - break; - } - $runtimeException = true; + $this->erroredDefinitions[$this->currentId] = $value; + + return parent::processValue($value); + } + + private function isErrorForRuntime(string $id, array &$visitedIds): bool + { + if (!isset($this->sourceReferences[$id])) { + return false; + } + + if (isset($visitedIds[$id])) { + return $visitedIds[$id]; + } + + $visitedIds[$id] = true; + + foreach ($this->sourceReferences[$id] as $sourceId => $isRuntime) { + if ($visitedIds[$sourceId] ?? $visitedIds[$sourceId] = $this->isErrorForRuntime($sourceId, $visitedIds)) { + continue; } - if ($runtimeException) { - return parent::processValue($value, $isRoot); + + if (!$isRuntime) { + return false; } } - // only show the first error so the user can focus on it - $errors = $value->getErrors(); - $message = reset($errors); - - throw new RuntimeException($message); + return true; } } diff --git a/lib/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php b/lib/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php index 27e504824..953b7f942 100644 --- a/lib/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php +++ b/lib/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php @@ -22,7 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; class ExtensionCompilerPass implements CompilerPassInterface { /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { diff --git a/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php b/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php index 2285f8ea5..57e14b77b 100644 --- a/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php +++ b/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php @@ -24,19 +24,24 @@ use Symfony\Component\DependencyInjection\Reference; */ class InlineServiceDefinitionsPass extends AbstractRecursivePass { - private $analyzingPass; - private $cloningIds = []; - private $connectedIds = []; - private $notInlinedIds = []; - private $inlinedIds = []; - private $notInlinableIds = []; - private $graph; + protected bool $skipScalars = true; + + private ?AnalyzeServiceReferencesPass $analyzingPass; + private array $cloningIds = []; + private array $connectedIds = []; + private array $notInlinedIds = []; + private array $inlinedIds = []; + private array $notInlinableIds = []; + private ?ServiceReferenceGraph $graph = null; public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null) { $this->analyzingPass = $analyzingPass; } + /** + * @return void + */ public function process(ContainerBuilder $container) { $this->container = $container; @@ -51,6 +56,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass $analyzedContainer = $container; } try { + $notInlinableIds = []; $remainingInlinedIds = []; $this->connectedIds = $this->notInlinedIds = $container->getDefinitions(); do { @@ -60,7 +66,8 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass } $this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph(); $notInlinedIds = $this->notInlinedIds; - $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = []; + $notInlinableIds += $this->notInlinableIds; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = $this->notInlinableIds = []; foreach ($analyzedContainer->getDefinitions() as $id => $definition) { if (!$this->graph->hasNode($id)) { @@ -86,7 +93,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass } while ($this->inlinedIds && $this->analyzingPass); foreach ($remainingInlinedIds as $id) { - if (isset($this->notInlinableIds[$id])) { + if (isset($notInlinableIds[$id])) { continue; } @@ -104,10 +111,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass } } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof ArgumentInterface) { // References found in ArgumentInterface::getValues() are not inlineable @@ -129,8 +133,10 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass $definition = $this->container->getDefinition($id); - if (!$this->isInlineableDefinition($id, $definition)) { - $this->notInlinableIds[$id] = true; + if (isset($this->notInlinableIds[$id]) || !$this->isInlineableDefinition($id, $definition)) { + if ($this->currentId !== $id) { + $this->notInlinableIds[$id] = true; + } return $value; } @@ -191,7 +197,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass return true; } - if ($this->currentId == $id) { + if ($this->currentId === $id) { return false; } $this->connectedIds[$id] = true; diff --git a/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php b/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php index 9dc39314c..cd8ebfe0f 100644 --- a/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php +++ b/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php @@ -30,7 +30,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; class MergeExtensionConfigurationPass implements CompilerPassInterface { /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -101,7 +101,7 @@ class MergeExtensionConfigurationPass implements CompilerPassInterface */ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag { - private $processedEnvPlaceholders; + private array $processedEnvPlaceholders; public function __construct(parent $parameterBag) { @@ -109,7 +109,7 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag $this->mergeEnvPlaceholders($parameterBag); } - public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container) + public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container): void { if (!$config = $extension->getProcessedConfigs()) { // Extension::processConfiguration() wasn't called, we cannot know how configs were merged @@ -120,9 +120,15 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag // serialize config and container to catch env vars nested in object graphs $config = serialize($config).serialize($container->getDefinitions()).serialize($container->getAliases()).serialize($container->getParameterBag()->all()); + if (false === stripos($config, 'env_')) { + return; + } + + preg_match_all('/env_[a-f0-9]{16}_\w+_[a-f0-9]{32}/Ui', $config, $matches); + $usedPlaceholders = array_flip($matches[0]); foreach (parent::getEnvPlaceholders() as $env => $placeholders) { foreach ($placeholders as $placeholder) { - if (false !== stripos($config, $placeholder)) { + if (isset($usedPlaceholders[$placeholder])) { $this->processedEnvPlaceholders[$env] = $placeholders; break; } @@ -130,9 +136,6 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag } } - /** - * {@inheritdoc} - */ public function getEnvPlaceholders(): array { return $this->processedEnvPlaceholders ?? parent::getEnvPlaceholders(); @@ -140,7 +143,7 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag public function getUnusedEnvPlaceholders(): array { - return null === $this->processedEnvPlaceholders ? [] : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders); + return !isset($this->processedEnvPlaceholders) ? [] : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders); } } @@ -151,43 +154,31 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag */ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder { - private $extensionClass; + private string $extensionClass; public function __construct(ExtensionInterface $extension, ParameterBagInterface $parameterBag = null) { parent::__construct($parameterBag); - $this->extensionClass = \get_class($extension); + $this->extensionClass = $extension::class; } - /** - * {@inheritdoc} - */ - public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): self + public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): static { throw new LogicException(sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', get_debug_type($pass), $this->extensionClass)); } - /** - * {@inheritdoc} - */ public function registerExtension(ExtensionInterface $extension) { throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', get_debug_type($extension), $this->extensionClass)); } - /** - * {@inheritdoc} - */ public function compile(bool $resolveEnvPlaceholders = false) { throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass)); } - /** - * {@inheritdoc} - */ - public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null) + public function resolveEnvPlaceholders(mixed $value, string|bool $format = null, array &$usedEnvs = null): mixed { if (true !== $format || !\is_string($value)) { return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); diff --git a/lib/symfony/dependency-injection/Compiler/PassConfig.php b/lib/symfony/dependency-injection/Compiler/PassConfig.php index 9f9a56edd..16b24cc71 100644 --- a/lib/symfony/dependency-injection/Compiler/PassConfig.php +++ b/lib/symfony/dependency-injection/Compiler/PassConfig.php @@ -28,12 +28,12 @@ class PassConfig public const TYPE_OPTIMIZE = 'optimization'; public const TYPE_REMOVE = 'removing'; - private $mergePass; - private $afterRemovingPasses = []; - private $beforeOptimizationPasses = []; - private $beforeRemovingPasses = []; - private $optimizationPasses; - private $removingPasses; + private MergeExtensionConfigurationPass $mergePass; + private array $afterRemovingPasses; + private array $beforeOptimizationPasses; + private array $beforeRemovingPasses = []; + private array $optimizationPasses; + private array $removingPasses; public function __construct() { @@ -43,6 +43,7 @@ class PassConfig 100 => [ new ResolveClassPass(), new RegisterAutoconfigureAttributesPass(), + new AutowireAsDecoratorPass(), new AttributeAutoconfigurationPass(), new ResolveInstanceofConditionalsPass(), new RegisterEnvVarProcessorsPass(), @@ -51,7 +52,7 @@ class PassConfig ]; $this->optimizationPasses = [[ - $autoAliasServicePass = new AutoAliasServicePass(), + new AutoAliasServicePass(), new ValidateEnvPlaceholdersPass(), new ResolveDecoratorStackPass(), new ResolveChildDefinitionsPass(), @@ -79,7 +80,7 @@ class PassConfig $this->removingPasses = [[ new RemovePrivateAliasesPass(), - (new ReplaceAliasByActualDefinitionPass())->setAutoAliasServicePass($autoAliasServicePass), + new ReplaceAliasByActualDefinitionPass(), new RemoveAbstractDefinitionsPass(), new RemoveUnusedDefinitionsPass(), new AnalyzeServiceReferencesPass(), @@ -89,11 +90,15 @@ class PassConfig new DefinitionErrorExceptionPass(), ]]; - $this->afterRemovingPasses = [[ - new ResolveHotPathPass(), - new ResolveNoPreloadPass(), - new AliasDeprecatedPublicServicesPass(), - ]]; + $this->afterRemovingPasses = [ + 0 => [ + new ResolveHotPathPass(), + new ResolveNoPreloadPass(), + new AliasDeprecatedPublicServicesPass(), + ], + // Let build parameters be available as late as possible + -2048 => [new RemoveBuildParametersPass()], + ]; } /** @@ -101,7 +106,7 @@ class PassConfig * * @return CompilerPassInterface[] */ - public function getPasses() + public function getPasses(): array { return array_merge( [$this->mergePass], @@ -116,6 +121,8 @@ class PassConfig /** * Adds a pass. * + * @return void + * * @throws InvalidArgumentException when a pass type doesn't exist */ public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) @@ -138,7 +145,7 @@ class PassConfig * * @return CompilerPassInterface[] */ - public function getAfterRemovingPasses() + public function getAfterRemovingPasses(): array { return $this->sortPasses($this->afterRemovingPasses); } @@ -148,7 +155,7 @@ class PassConfig * * @return CompilerPassInterface[] */ - public function getBeforeOptimizationPasses() + public function getBeforeOptimizationPasses(): array { return $this->sortPasses($this->beforeOptimizationPasses); } @@ -158,7 +165,7 @@ class PassConfig * * @return CompilerPassInterface[] */ - public function getBeforeRemovingPasses() + public function getBeforeRemovingPasses(): array { return $this->sortPasses($this->beforeRemovingPasses); } @@ -168,7 +175,7 @@ class PassConfig * * @return CompilerPassInterface[] */ - public function getOptimizationPasses() + public function getOptimizationPasses(): array { return $this->sortPasses($this->optimizationPasses); } @@ -178,21 +185,22 @@ class PassConfig * * @return CompilerPassInterface[] */ - public function getRemovingPasses() + public function getRemovingPasses(): array { return $this->sortPasses($this->removingPasses); } /** * Gets the Merge pass. - * - * @return CompilerPassInterface */ - public function getMergePass() + public function getMergePass(): CompilerPassInterface { return $this->mergePass; } + /** + * @return void + */ public function setMergePass(CompilerPassInterface $pass) { $this->mergePass = $pass; @@ -202,6 +210,8 @@ class PassConfig * Sets the AfterRemoving passes. * * @param CompilerPassInterface[] $passes + * + * @return void */ public function setAfterRemovingPasses(array $passes) { @@ -212,6 +222,8 @@ class PassConfig * Sets the BeforeOptimization passes. * * @param CompilerPassInterface[] $passes + * + * @return void */ public function setBeforeOptimizationPasses(array $passes) { @@ -222,6 +234,8 @@ class PassConfig * Sets the BeforeRemoving passes. * * @param CompilerPassInterface[] $passes + * + * @return void */ public function setBeforeRemovingPasses(array $passes) { @@ -232,6 +246,8 @@ class PassConfig * Sets the Optimization passes. * * @param CompilerPassInterface[] $passes + * + * @return void */ public function setOptimizationPasses(array $passes) { @@ -242,6 +258,8 @@ class PassConfig * Sets the Removing passes. * * @param CompilerPassInterface[] $passes + * + * @return void */ public function setRemovingPasses(array $passes) { diff --git a/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php b/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php index 8c4d841f5..701786245 100644 --- a/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php +++ b/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php @@ -35,11 +35,9 @@ trait PriorityTaggedServiceTrait * @see https://bugs.php.net/53710 * @see https://bugs.php.net/60926 * - * @param string|TaggedIteratorArgument $tagName - * * @return Reference[] */ - private function findAndSortTaggedServices($tagName, ContainerBuilder $container): array + private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container, array $exclude = []): array { $indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null; @@ -48,6 +46,7 @@ trait PriorityTaggedServiceTrait $defaultIndexMethod = $tagName->getDefaultIndexMethod(); $needsIndexes = $tagName->needsIndexes(); $defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority'; + $exclude = array_merge($exclude, $tagName->getExclude()); $tagName = $tagName->getTag(); } @@ -55,12 +54,16 @@ trait PriorityTaggedServiceTrait $services = []; foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { + if (\in_array($serviceId, $exclude, true)) { + continue; + } + $defaultPriority = null; $defaultIndex = null; $definition = $container->getDefinition($serviceId); $class = $definition->getClass(); $class = $container->getParameterBag()->resolveValue($class) ?: null; - $checkTaggedItem = !$definition->hasTag(80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName); + $checkTaggedItem = !$definition->hasTag($definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName); foreach ($attributes as $attribute) { $index = $priority = null; @@ -70,7 +73,7 @@ trait PriorityTaggedServiceTrait } elseif (null === $defaultPriority && $defaultPriorityMethod && $class) { $defaultPriority = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem); } - $priority = $priority ?? $defaultPriority ?? $defaultPriority = 0; + $priority ??= $defaultPriority ??= 0; if (null === $indexAttribute && !$defaultIndexMethod && !$needsIndexes) { $services[] = [$priority, ++$i, null, $serviceId, null]; @@ -82,13 +85,13 @@ trait PriorityTaggedServiceTrait } elseif (null === $defaultIndex && $defaultPriorityMethod && $class) { $defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem); } - $index = $index ?? $defaultIndex ?? $defaultIndex = $serviceId; + $index ??= $defaultIndex ??= $serviceId; $services[] = [$priority, ++$i, $index, $serviceId, $class]; } } - uasort($services, static function ($a, $b) { return $b[0] <=> $a[0] ?: $a[1] <=> $b[1]; }); + uasort($services, static fn ($a, $b) => $b[0] <=> $a[0] ?: $a[1] <=> $b[1]); $refs = []; foreach ($services as [, , $index, $serviceId, $class]) { @@ -116,10 +119,7 @@ trait PriorityTaggedServiceTrait */ class PriorityTaggedServiceUtil { - /** - * @return string|int|null - */ - public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem) + public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem): string|int|null { if (!($r = $container->getReflectionClass($class)) || (!$checkTaggedItem && !$r->hasMethod($defaultMethod))) { return null; diff --git a/lib/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php b/lib/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php index cc3b117a4..d479743ec 100644 --- a/lib/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php +++ b/lib/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php @@ -24,17 +24,10 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; */ final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface { - private static $registerForAutoconfiguration; + private static \Closure $registerForAutoconfiguration; - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { - if (80000 > \PHP_VERSION_ID) { - return; - } - foreach ($container->getDefinitions() as $id => $definition) { if ($this->accept($definition) && $class = $container->getReflectionClass($definition->getClass(), false)) { $this->processClass($container, $class); @@ -44,24 +37,25 @@ final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface public function accept(Definition $definition): bool { - return 80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes'); + return $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes'); } - public function processClass(ContainerBuilder $container, \ReflectionClass $class) + public function processClass(ContainerBuilder $container, \ReflectionClass $class): void { foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { self::registerForAutoconfiguration($container, $class, $attribute); } } - private static function registerForAutoconfiguration(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) + private static function registerForAutoconfiguration(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute): void { - if (self::$registerForAutoconfiguration) { - return (self::$registerForAutoconfiguration)($container, $class, $attribute); + if (isset(self::$registerForAutoconfiguration)) { + (self::$registerForAutoconfiguration)($container, $class, $attribute); + + return; } $parseDefinitions = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinitions'); - $parseDefinitions->setAccessible(true); $yamlLoader = $parseDefinitions->getDeclaringClass()->newInstanceWithoutConstructor(); self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) { @@ -87,6 +81,6 @@ final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface ); }; - return (self::$registerForAutoconfiguration)($container, $class, $attribute); + (self::$registerForAutoconfiguration)($container, $class, $attribute); } } diff --git a/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php b/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php index 251889ebe..2a706bfe5 100644 --- a/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php +++ b/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php @@ -25,8 +25,11 @@ use Symfony\Component\DependencyInjection\Reference; */ class RegisterEnvVarProcessorsPass implements CompilerPassInterface { - private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string']; + private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string', \BackedEnum::class]; + /** + * @return void + */ public function process(ContainerBuilder $container) { $bag = $container->getParameterBag(); diff --git a/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php b/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php index c5eb9bf08..aa4cca357 100644 --- a/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php +++ b/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php @@ -22,38 +22,33 @@ use Symfony\Component\DependencyInjection\Reference; */ class RegisterReverseContainerPass implements CompilerPassInterface { - private $beforeRemoving; - private $serviceId; - private $tagName; + private bool $beforeRemoving; - public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible') + public function __construct(bool $beforeRemoving) { - if (1 < \func_num_args()) { - trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - $this->beforeRemoving = $beforeRemoving; - $this->serviceId = $serviceId; - $this->tagName = $tagName; } + /** + * @return void + */ public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->serviceId)) { + if (!$container->hasDefinition('reverse_container')) { return; } $refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; $services = []; - foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) { + foreach ($container->findTaggedServiceIds('container.reversible') as $id => $tags) { $services[$id] = new Reference($id, $refType); } if ($this->beforeRemoving) { // prevent inlining of the reverse container - $services[$this->serviceId] = new Reference($this->serviceId, $refType); + $services['reverse_container'] = new Reference('reverse_container', $refType); } - $locator = $container->getDefinition($this->serviceId)->getArgument(1); + $locator = $container->getDefinition('reverse_container')->getArgument(1); if ($locator instanceof Reference) { $locator = $container->getDefinition((string) $locator); diff --git a/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php b/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php index 2a458ad12..dab84cd37 100644 --- a/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php +++ b/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php @@ -14,12 +14,14 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Psr\Container\ContainerInterface as PsrContainerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Contracts\Service\Attribute\SubscribedService; use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -30,7 +32,9 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; */ class RegisterServiceSubscribersPass extends AbstractRecursivePass { - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) { return parent::processValue($value, $isRoot); @@ -68,14 +72,29 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class)); } $class = $r->name; + // to remove when symfony/dependency-injection will stop being compatible with symfony/framework-bundle<6.0 $replaceDeprecatedSession = $this->container->has('.session.deprecated') && $r->isSubclassOf(AbstractController::class); $subscriberMap = []; foreach ($class::getSubscribedServices() as $key => $type) { + $attributes = []; + + if (!isset($serviceMap[$key]) && $type instanceof Autowire) { + $subscriberMap[$key] = $type; + continue; + } + + if ($type instanceof SubscribedService) { + $key = $type->key ?? $key; + $attributes = $type->attributes; + $type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s::getSubscribedServices()" returns "%s", a type must be set.', $class, SubscribedService::class))); + } + if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type))); } - if ($optionalBehavior = '?' === $type[0]) { + $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('?' === $type[0]) { $type = substr($type, 1); $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } @@ -89,7 +108,7 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass } if ($replaceDeprecatedSession && SessionInterface::class === $type) { // This prevents triggering the deprecation when building the container - // Should be removed in Symfony 6.0 + // to remove when symfony/dependency-injection will stop being compatible with symfony/framework-bundle<6.0 $type = '.session.deprecated'; } $serviceMap[$key] = new Reference($type); @@ -108,7 +127,7 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass $name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name; } - $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name); + $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes); unset($serviceMap[$key]); } diff --git a/lib/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php b/lib/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php index 04b6852fa..d0ebfcc50 100644 --- a/lib/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php +++ b/lib/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php @@ -20,6 +20,8 @@ class RemoveAbstractDefinitionsPass implements CompilerPassInterface { /** * Removes abstract definitions from the ContainerBuilder. + * + * @return void */ public function process(ContainerBuilder $container) { diff --git a/lib/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php b/lib/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php new file mode 100644 index 000000000..75e714475 --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class RemoveBuildParametersPass implements CompilerPassInterface +{ + /** + * @var array + */ + private array $removedParameters = []; + + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + $parameterBag = $container->getParameterBag(); + $this->removedParameters = []; + + foreach ($parameterBag->all() as $name => $value) { + if ('.' === ($name[0] ?? '')) { + $this->removedParameters[$name] = $value; + + $parameterBag->remove($name); + $container->log($this, sprintf('Removing build parameter "%s".', $name)); + } + } + } + + /** + * @return array + */ + public function getRemovedParameters(): array + { + return $this->removedParameters; + } +} diff --git a/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php b/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php index 75b36d227..93c3fd267 100644 --- a/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php +++ b/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php @@ -24,6 +24,8 @@ class RemovePrivateAliasesPass implements CompilerPassInterface { /** * Removes private aliases from the ContainerBuilder. + * + * @return void */ public function process(ContainerBuilder $container) { diff --git a/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php b/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php index cf1a3ddc7..d6ee5ea56 100644 --- a/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php +++ b/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php @@ -22,10 +22,14 @@ use Symfony\Component\DependencyInjection\Reference; */ class RemoveUnusedDefinitionsPass extends AbstractRecursivePass { - private $connectedIds = []; + protected bool $skipScalars = true; + + private array $connectedIds = []; /** * Processes the ContainerBuilder to remove unused definitions. + * + * @return void */ public function process(ContainerBuilder $container) { @@ -72,10 +76,7 @@ class RemoveUnusedDefinitionsPass extends AbstractRecursivePass } } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); diff --git a/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php b/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php index bb2ba0d54..46d615f83 100644 --- a/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php +++ b/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -24,24 +24,15 @@ use Symfony\Component\DependencyInjection\Reference; */ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass { - private $replacements; - private $autoAliasServicePass; + protected bool $skipScalars = true; - /** - * @internal to be removed in Symfony 6.0 - * - * @return $this - */ - public function setAutoAliasServicePass(AutoAliasServicePass $autoAliasServicePass): self - { - $this->autoAliasServicePass = $autoAliasServicePass; - - return $this; - } + private array $replacements; /** * Process the Container to replace aliases with service definitions. * + * @return void + * * @throws InvalidArgumentException if the service definition does not exist */ public function process(ContainerBuilder $container) @@ -50,11 +41,6 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass $seenAliasTargets = []; $replacements = []; - $privateAliases = $this->autoAliasServicePass ? $this->autoAliasServicePass->getPrivateAliases() : []; - foreach ($privateAliases as $target) { - $target->setDeprecated('symfony/dependency-injection', '5.4', 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.'); - } - foreach ($container->getAliases() as $definitionId => $target) { $targetId = (string) $target; // Special case: leave this target alone @@ -103,10 +89,7 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass $this->replacements = []; } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) { // Perform the replacement diff --git a/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php index 5bc379153..68835d52a 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php @@ -19,21 +19,23 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\VarExporter\ProxyHelper; /** * @author Guilhem Niot */ class ResolveBindingsPass extends AbstractRecursivePass { - private $usedBindings = []; - private $unusedBindings = []; - private $errorMessages = []; + protected bool $skipScalars = true; + + private array $usedBindings = []; + private array $unusedBindings = []; + private array $errorMessages = []; /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -90,10 +92,7 @@ class ResolveBindingsPass extends AbstractRecursivePass } } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof TypedReference && $value->getType() === (string) $value) { // Already checked @@ -177,15 +176,23 @@ class ResolveBindingsPass extends AbstractRecursivePass } } + $names = []; + foreach ($reflectionMethod->getParameters() as $key => $parameter) { + $names[$key] = $parameter->name; + if (\array_key_exists($key, $arguments) && '' !== $arguments[$key]) { continue; } + if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name]) { + continue; + } + + $typeHint = ltrim(ProxyHelper::exportType($parameter) ?? '', '?'); - $typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter); $name = Target::parseName($parameter); - if ($typeHint && \array_key_exists($k = ltrim($typeHint, '\\').' $'.$name, $bindings)) { + if ($typeHint && \array_key_exists($k = preg_replace('/(^|[(|&])\\\\/', '\1', $typeHint).' $'.$name, $bindings)) { $arguments[$key] = $this->getBindingValue($bindings[$k]); continue; @@ -210,8 +217,15 @@ class ResolveBindingsPass extends AbstractRecursivePass } } + foreach ($names as $key => $name) { + if (\array_key_exists($name, $arguments) && (0 === $key || \array_key_exists($key - 1, $arguments))) { + $arguments[$key] = $arguments[$name]; + unset($arguments[$name]); + } + } + if ($arguments !== $call[1]) { - ksort($arguments); + ksort($arguments, \SORT_NATURAL); $calls[$i][1] = $arguments; } } @@ -231,10 +245,7 @@ class ResolveBindingsPass extends AbstractRecursivePass return parent::processValue($value, $isRoot); } - /** - * @return mixed - */ - private function getBindingValue(BoundArgument $binding) + private function getBindingValue(BoundArgument $binding): mixed { [$bindingValue, $bindingId] = $binding->getValues(); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php index aefd2294a..bc1eeebe2 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php @@ -27,9 +27,11 @@ use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceExce */ class ResolveChildDefinitionsPass extends AbstractRecursivePass { - private $currentPath; + protected bool $skipScalars = true; - protected function processValue($value, bool $isRoot = false) + private array $currentPath; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); @@ -63,7 +65,6 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass throw $e; } catch (ExceptionInterface $e) { $r = new \ReflectionProperty($e, 'message'); - $r->setAccessible(true); $r->setValue($e, sprintf('Service "%s": %s', $this->currentId, $e->getMessage())); throw $e; @@ -139,13 +140,9 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass if (isset($changes['lazy'])) { $def->setLazy($definition->isLazy()); } - if (isset($changes['deprecated'])) { - if ($definition->isDeprecated()) { - $deprecation = $definition->getDeprecation('%service_id%'); - $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); - } else { - $def->setDeprecated(false); - } + if (isset($changes['deprecated']) && $definition->isDeprecated()) { + $deprecation = $definition->getDeprecation('%service_id%'); + $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); } if (isset($changes['autowired'])) { $def->setAutowired($definition->isAutowired()); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php b/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php index e67a2a8ed..468837672 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php @@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; class ResolveClassPass implements CompilerPassInterface { /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { diff --git a/lib/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php b/lib/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php index 4914b3ac9..da02622b2 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php @@ -24,26 +24,18 @@ use Symfony\Component\DependencyInjection\Reference; */ class ResolveDecoratorStackPass implements CompilerPassInterface { - private $tag; - - public function __construct(string $tag = 'container.stack') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->tag = $tag; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { $stacks = []; - foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) { + foreach ($container->findTaggedServiceIds('container.stack') as $id => $tags) { $definition = $container->getDefinition($id); if (!$definition instanceof ChildDefinition) { - throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag)); + throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "container.stack" tag.', $id)); } if (!$stack = $definition->getArguments()) { diff --git a/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php index ea52b1459..ea077cba9 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php @@ -18,7 +18,9 @@ use Symfony\Component\DependencyInjection\Definition; */ class ResolveEnvPlaceholdersPass extends AbstractRecursivePass { - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = false; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (\is_string($value)) { return $this->container->resolveEnvPlaceholders($value, true); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php b/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php index 23f535b71..2beaa01b6 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php @@ -19,10 +19,9 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ class ResolveFactoryClassPass extends AbstractRecursivePass { - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof Definition && \is_array($factory = $value->getFactory()) && null === $factory[0]) { if (null === $class = $value->getClass()) { diff --git a/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php b/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php index dee2dc6be..705bb837b 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php @@ -23,35 +23,24 @@ use Symfony\Component\DependencyInjection\Reference; */ class ResolveHotPathPass extends AbstractRecursivePass { - private $tagName; - private $resolvedIds = []; + protected bool $skipScalars = true; - public function __construct(string $tagName = 'container.hot_path') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->tagName = $tagName; - } + private array $resolvedIds = []; /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { try { parent::process($container); - $container->getDefinition('service_container')->clearTag($this->tagName); + $container->getDefinition('service_container')->clearTag('container.hot_path'); } finally { $this->resolvedIds = []; } } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof ArgumentInterface) { return $value; @@ -59,12 +48,12 @@ class ResolveHotPathPass extends AbstractRecursivePass if ($value instanceof Definition && $isRoot) { if ($value->isDeprecated()) { - return $value->clearTag($this->tagName); + return $value->clearTag('container.hot_path'); } $this->resolvedIds[$this->currentId] = true; - if (!$value->hasTag($this->tagName)) { + if (!$value->hasTag('container.hot_path')) { return $value; } } @@ -72,11 +61,11 @@ class ResolveHotPathPass extends AbstractRecursivePass if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { $definition = $this->container->getDefinition($id); - if ($definition->isDeprecated() || $definition->hasTag($this->tagName)) { + if ($definition->isDeprecated() || $definition->hasTag('container.hot_path')) { return $value; } - $definition->addTag($this->tagName); + $definition->addTag('container.hot_path'); if (isset($this->resolvedIds[$id])) { parent::processValue($definition, false); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php index b211b84e1..fbce044e3 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php @@ -25,7 +25,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; class ResolveInstanceofConditionalsPass implements CompilerPassInterface { /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -73,7 +73,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface $parent = $definition instanceof ChildDefinition ? $definition->getParent() : null; foreach ($conditionals as $interface => $instanceofDefs) { - if ($interface !== $class && !($reflectionClass ?? $reflectionClass = $container->getReflectionClass($class, false) ?: false)) { + if ($interface !== $class && !($reflectionClass ??= $container->getReflectionClass($class, false) ?: false)) { continue; } @@ -87,7 +87,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface $instanceofDef->setAbstract(true)->setParent($parent ?: '.abstract.instanceof.'.$id); $parent = '.instanceof.'.$interface.'.'.$key.'.'.$id; $container->setDefinition($parent, $instanceofDef); - $instanceofTags[] = $instanceofDef->getTags(); + $instanceofTags[] = [$interface, $instanceofDef->getTags()]; $instanceofBindings = $instanceofDef->getBindings() + $instanceofBindings; foreach ($instanceofDef->getMethodCalls() as $methodCall) { @@ -110,7 +110,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface $definition->setBindings([]); $definition = serialize($definition); - if (Definition::class === \get_class($abstract)) { + if (Definition::class === $abstract::class) { // cast Definition to ChildDefinition $definition = substr_replace($definition, '53', 2, 2); $definition = substr_replace($definition, 'Child', 44, 0); @@ -126,8 +126,9 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface // Don't add tags to service decorators $i = \count($instanceofTags); while (0 <= --$i) { - foreach ($instanceofTags[$i] as $k => $v) { - if (null === $definition->getDecoratedService() || \in_array($k, $tagsToKeep, true)) { + [$interface, $tags] = $instanceofTags[$i]; + foreach ($tags as $k => $v) { + if (null === $definition->getDecoratedService() || $interface === $definition->getClass() || \in_array($k, $tagsToKeep, true)) { foreach ($v as $v) { if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) { continue; @@ -157,7 +158,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container): array { // make each value an array of ChildDefinition - $conditionals = array_map(function ($childDef) { return [$childDef]; }, $autoconfiguredInstanceof); + $conditionals = array_map(fn ($childDef) => [$childDef], $autoconfiguredInstanceof); foreach ($instanceofConditionals as $interface => $instanceofDef) { // make sure the interface/class exists (but don't validate automaticInstanceofConditionals) diff --git a/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php b/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php index 948de421f..7a2a69aa6 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php @@ -29,12 +29,14 @@ use Symfony\Component\DependencyInjection\TypedReference; */ class ResolveInvalidReferencesPass implements CompilerPassInterface { - private $container; - private $signalingException; - private $currentId; + private ContainerBuilder $container; + private RuntimeException $signalingException; + private string $currentId; /** * Process the ContainerBuilder to resolve invalid references. + * + * @return void */ public function process(ContainerBuilder $container) { @@ -46,18 +48,16 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface $this->processValue($definition); } } finally { - $this->container = $this->signalingException = null; + unset($this->container, $this->signalingException); } } /** * Processes arguments to determine invalid references. * - * @return mixed - * * @throws RuntimeException When an invalid reference is found */ - private function processValue($value, int $rootLevel = 0, int $level = 0) + private function processValue(mixed $value, int $rootLevel = 0, int $level = 0): mixed { if ($value instanceof ServiceClosureArgument) { $value->setValues($this->processValue($value->getValues(), 1, 1)); @@ -97,7 +97,7 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface $value = array_values($value); } } elseif ($value instanceof Reference) { - if ($this->container->has($id = (string) $value)) { + if ($this->container->hasDefinition($id = (string) $value) ? !$this->container->getDefinition($id)->hasTag('container.excluded') : $this->container->hasAlias($id)) { return $value; } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php index c1c5748e8..24fac737c 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php @@ -14,8 +14,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\VarExporter\ProxyHelper; /** * Resolves named arguments to their corresponding numeric index. @@ -24,10 +24,9 @@ use Symfony\Component\DependencyInjection\Reference; */ class ResolveNamedArgumentsPass extends AbstractRecursivePass { - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof AbstractArgument && $value->getText().'.' === $value->getTextWithContext()) { $value->setContext(sprintf('A value found in service "%s"', $this->currentId)); @@ -43,6 +42,7 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass foreach ($calls as $i => $call) { [$method, $arguments] = $call; $parameters = null; + $resolvedKeys = []; $resolvedArguments = []; foreach ($arguments as $key => $argument) { @@ -51,6 +51,7 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass } if (\is_int($key)) { + $resolvedKeys[$key] = $key; $resolvedArguments[$key] = $argument; continue; } @@ -71,9 +72,11 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass if ($key === '$'.$p->name) { if ($p->isVariadic() && \is_array($argument)) { foreach ($argument as $variadicArgument) { + $resolvedKeys[$j] = $j; $resolvedArguments[$j++] = $variadicArgument; } } else { + $resolvedKeys[$j] = $p->name; $resolvedArguments[$j] = $argument; } @@ -90,7 +93,8 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass $typeFound = false; foreach ($parameters as $j => $p) { - if (!\array_key_exists($j, $resolvedArguments) && ProxyHelper::getTypeHint($r, $p, true) === $key) { + if (!\array_key_exists($j, $resolvedArguments) && ProxyHelper::exportType($p, true) === $key) { + $resolvedKeys[$j] = $p->name; $resolvedArguments[$j] = $argument; $typeFound = true; } @@ -103,6 +107,12 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass if ($resolvedArguments !== $call[1]) { ksort($resolvedArguments); + + if (!$value->isAutowired() && !array_is_list($resolvedArguments)) { + ksort($resolvedKeys); + $resolvedArguments = array_combine($resolvedKeys, $resolvedArguments); + } + $calls[$i][1] = $resolvedArguments; } } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php b/lib/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php index 016be55b3..fb7991229 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php @@ -24,20 +24,12 @@ class ResolveNoPreloadPass extends AbstractRecursivePass { private const DO_PRELOAD_TAG = '.container.do_preload'; - private $tagName; - private $resolvedIds = []; + protected bool $skipScalars = true; - public function __construct(string $tagName = 'container.no_preload') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->tagName = $tagName; - } + private array $resolvedIds = []; /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -66,15 +58,12 @@ class ResolveNoPreloadPass extends AbstractRecursivePass if ($definition->hasTag(self::DO_PRELOAD_TAG)) { $definition->clearTag(self::DO_PRELOAD_TAG); } elseif (!$definition->isDeprecated() && !$definition->hasErrors()) { - $definition->addTag($this->tagName); + $definition->addTag('container.no_preload'); } } } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { $definition = $this->container->getDefinition($id); @@ -91,7 +80,7 @@ class ResolveNoPreloadPass extends AbstractRecursivePass return parent::processValue($value, $isRoot); } - if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) { + if ($value->hasTag('container.no_preload') || $value->isDeprecated() || $value->hasErrors()) { return $value; } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php index 0099a3bbc..a78a6e508 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php @@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; /** * Resolves all parameter placeholders "%somevalue%" to their real values. @@ -22,18 +23,18 @@ use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; */ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass { - private $bag; - private $resolveArrays; - private $throwOnResolveException; + protected bool $skipScalars = false; - public function __construct($resolveArrays = true, $throwOnResolveException = true) - { - $this->resolveArrays = $resolveArrays; - $this->throwOnResolveException = $throwOnResolveException; + private ParameterBagInterface $bag; + + public function __construct( + private bool $resolveArrays = true, + private bool $throwOnResolveException = true, + ) { } /** - * {@inheritdoc} + * @return void * * @throws ParameterNotFoundException */ @@ -57,10 +58,10 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass } $this->bag->resolve(); - $this->bag = null; + unset($this->bag); } - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (\is_string($value)) { try { diff --git a/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php b/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php deleted file mode 100644 index b63e3f5c2..000000000 --- a/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s" class is deprecated.', ResolvePrivatesPass::class); - -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * @author Nicolas Grekas - * - * @deprecated since Symfony 5.2 - */ -class ResolvePrivatesPass implements CompilerPassInterface -{ - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isPrivate()) { - $definition->setPublic(false); - $definition->setPrivate(true); - } - } - - foreach ($container->getAliases() as $id => $alias) { - if ($alias->isPrivate()) { - $alias->setPublic(false); - $alias->setPrivate(true); - } - } - } -} diff --git a/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php b/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php index e59893ff7..16d0e9fcb 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php @@ -22,8 +22,10 @@ use Symfony\Component\DependencyInjection\Reference; */ class ResolveReferencesToAliasesPass extends AbstractRecursivePass { + protected bool $skipScalars = true; + /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -39,10 +41,7 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass } } - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php index 518c03d7e..91714120e 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php @@ -23,9 +23,11 @@ use Symfony\Contracts\Service\ServiceProviderInterface; */ class ResolveServiceSubscribersPass extends AbstractRecursivePass { - private $serviceLocator; + protected bool $skipScalars = true; - protected function processValue($value, bool $isRoot = false) + private ?string $serviceLocator = null; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], true)) { return new Reference($this->serviceLocator); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php b/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php index 48a034a84..c01e90b4f 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php @@ -22,16 +22,20 @@ class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass { use PriorityTaggedServiceTrait; - /** - * {@inheritdoc} - */ - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if (!$value instanceof TaggedIteratorArgument) { return parent::processValue($value, $isRoot); } - $value->setValues($this->findAndSortTaggedServices($value, $this->container)); + $exclude = $value->getExclude(); + if ($value->excludeSelf()) { + $exclude[] = $this->currentId; + } + + $value->setValues($this->findAndSortTaggedServices($value, $this->container, $exclude)); return $value; } diff --git a/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php b/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php index faa7b57e4..fb0fc2682 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php @@ -30,7 +30,9 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass { use PriorityTaggedServiceTrait; - protected function processValue($value, bool $isRoot = false) + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed { if ($value instanceof ServiceLocatorArgument) { if ($value->getTaggedIteratorArgument()) { @@ -40,6 +42,10 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass return self::register($this->container, $value->getValues()); } + if ($value instanceof Definition) { + $value->setBindings(parent::processValue($value->getBindings())); + } + if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) { return parent::processValue($value, $isRoot); } @@ -64,18 +70,17 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass if ($v instanceof ServiceClosureArgument) { continue; } - if (!$v instanceof Reference) { - throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, get_debug_type($v), $k)); - } if ($i === $k) { - unset($services[$k]); - - $k = (string) $v; + if ($v instanceof Reference) { + unset($services[$k]); + $k = (string) $v; + } ++$i; } elseif (\is_int($k)) { $i = null; } + $services[$k] = new ServiceClosureArgument($v); } ksort($services); @@ -97,20 +102,14 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass return new Reference($id); } - /** - * @param Reference[] $refMap - */ - public static function register(ContainerBuilder $container, array $refMap, string $callerId = null): Reference + public static function register(ContainerBuilder $container, array $map, string $callerId = null): Reference { - foreach ($refMap as $id => $ref) { - if (!$ref instanceof Reference) { - throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', get_debug_type($ref), $id)); - } - $refMap[$id] = new ServiceClosureArgument($ref); + foreach ($map as $k => $v) { + $map[$k] = new ServiceClosureArgument($v); } $locator = (new Definition(ServiceLocator::class)) - ->addArgument($refMap) + ->addArgument($map) ->addTag('container.service_locator'); if (null !== $callerId && $container->hasDefinition($callerId)) { diff --git a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php index 1225514c2..c90fc7ac5 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php @@ -29,7 +29,7 @@ class ServiceReferenceGraph /** * @var ServiceReferenceGraphNode[] */ - private $nodes = []; + private array $nodes = []; public function hasNode(string $id): bool { @@ -63,7 +63,7 @@ class ServiceReferenceGraph /** * Clears all nodes. */ - public function clear() + public function clear(): void { foreach ($this->nodes as $node) { $node->clear(); @@ -74,7 +74,7 @@ class ServiceReferenceGraph /** * Connects 2 nodes together in the Graph. */ - public function connect(?string $sourceId, $sourceValue, ?string $destId, $destValue = null, Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false) + public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void { if (null === $sourceId || null === $destId) { return; @@ -88,7 +88,7 @@ class ServiceReferenceGraph $destNode->addInEdge($edge); } - private function createNode(string $id, $value): ServiceReferenceGraphNode + private function createNode(string $id, mixed $value): ServiceReferenceGraphNode { if (isset($this->nodes[$id]) && $this->nodes[$id]->getValue() === $value) { return $this->nodes[$id]; diff --git a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php index 986145606..b607164a6 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php @@ -20,14 +20,14 @@ namespace Symfony\Component\DependencyInjection\Compiler; */ class ServiceReferenceGraphEdge { - private $sourceNode; - private $destNode; - private $value; - private $lazy; - private $weak; - private $byConstructor; + private ServiceReferenceGraphNode $sourceNode; + private ServiceReferenceGraphNode $destNode; + private mixed $value; + private bool $lazy; + private bool $weak; + private bool $byConstructor; - public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false) + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, mixed $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false) { $this->sourceNode = $sourceNode; $this->destNode = $destNode; @@ -39,60 +39,48 @@ class ServiceReferenceGraphEdge /** * Returns the value of the edge. - * - * @return mixed */ - public function getValue() + public function getValue(): mixed { return $this->value; } /** * Returns the source node. - * - * @return ServiceReferenceGraphNode */ - public function getSourceNode() + public function getSourceNode(): ServiceReferenceGraphNode { return $this->sourceNode; } /** * Returns the destination node. - * - * @return ServiceReferenceGraphNode */ - public function getDestNode() + public function getDestNode(): ServiceReferenceGraphNode { return $this->destNode; } /** * Returns true if the edge is lazy, meaning it's a dependency not requiring direct instantiation. - * - * @return bool */ - public function isLazy() + public function isLazy(): bool { return $this->lazy; } /** * Returns true if the edge is weak, meaning it shouldn't prevent removing the target service. - * - * @return bool */ - public function isWeak() + public function isWeak(): bool { return $this->weak; } /** * Returns true if the edge links with a constructor argument. - * - * @return bool */ - public function isReferencedByConstructor() + public function isReferencedByConstructor(): bool { return $this->byConstructor; } diff --git a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php index ba96da233..e7f42f87d 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php @@ -23,26 +23,28 @@ use Symfony\Component\DependencyInjection\Definition; */ class ServiceReferenceGraphNode { - private $id; - private $inEdges = []; - private $outEdges = []; - private $value; + private string $id; + private array $inEdges = []; + private array $outEdges = []; + private mixed $value; - /** - * @param string $id The node identifier - * @param mixed $value The node value - */ - public function __construct(string $id, $value) + public function __construct(string $id, mixed $value) { $this->id = $id; $this->value = $value; } + /** + * @return void + */ public function addInEdge(ServiceReferenceGraphEdge $edge) { $this->inEdges[] = $edge; } + /** + * @return void + */ public function addOutEdge(ServiceReferenceGraphEdge $edge) { $this->outEdges[] = $edge; @@ -50,30 +52,24 @@ class ServiceReferenceGraphNode /** * Checks if the value of this node is an Alias. - * - * @return bool */ - public function isAlias() + public function isAlias(): bool { return $this->value instanceof Alias; } /** * Checks if the value of this node is a Definition. - * - * @return bool */ - public function isDefinition() + public function isDefinition(): bool { return $this->value instanceof Definition; } /** * Returns the identifier. - * - * @return string */ - public function getId() + public function getId(): string { return $this->id; } @@ -83,7 +79,7 @@ class ServiceReferenceGraphNode * * @return ServiceReferenceGraphEdge[] */ - public function getInEdges() + public function getInEdges(): array { return $this->inEdges; } @@ -93,23 +89,23 @@ class ServiceReferenceGraphNode * * @return ServiceReferenceGraphEdge[] */ - public function getOutEdges() + public function getOutEdges(): array { return $this->outEdges; } /** * Returns the value of this Node. - * - * @return mixed */ - public function getValue() + public function getValue(): mixed { return $this->value; } /** * Clears all edges. + * + * @return void */ public function clear() { diff --git a/lib/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php b/lib/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php index 23bfe59b0..2d6542660 100644 --- a/lib/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php +++ b/lib/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php @@ -28,10 +28,10 @@ class ValidateEnvPlaceholdersPass implements CompilerPassInterface { private const TYPE_FIXTURES = ['array' => [], 'bool' => false, 'float' => 0.0, 'int' => 0, 'string' => '']; - private $extensionConfig = []; + private array $extensionConfig = []; /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { diff --git a/lib/symfony/dependency-injection/Config/ContainerParametersResource.php b/lib/symfony/dependency-injection/Config/ContainerParametersResource.php index c10398bcb..b066b5ffc 100644 --- a/lib/symfony/dependency-injection/Config/ContainerParametersResource.php +++ b/lib/symfony/dependency-injection/Config/ContainerParametersResource.php @@ -22,7 +22,7 @@ use Symfony\Component\Config\Resource\ResourceInterface; */ class ContainerParametersResource implements ResourceInterface { - private $parameters; + private array $parameters; /** * @param array $parameters The container parameters to track @@ -34,7 +34,7 @@ class ContainerParametersResource implements ResourceInterface public function __toString(): string { - return 'container_parameters_'.md5(serialize($this->parameters)); + return 'container_parameters_'.hash('xxh128', serialize($this->parameters)); } public function getParameters(): array diff --git a/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php b/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php index 2f2affaa5..619c5e197 100644 --- a/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php +++ b/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php @@ -20,26 +20,19 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class ContainerParametersResourceChecker implements ResourceCheckerInterface { - /** @var ContainerInterface */ - private $container; + private ContainerInterface $container; public function __construct(ContainerInterface $container) { $this->container = $container; } - /** - * {@inheritdoc} - */ - public function supports(ResourceInterface $metadata) + public function supports(ResourceInterface $metadata): bool { return $metadata instanceof ContainerParametersResource; } - /** - * {@inheritdoc} - */ - public function isFresh(ResourceInterface $resource, int $timestamp) + public function isFresh(ResourceInterface $resource, int $timestamp): bool { foreach ($resource->getParameters() as $key => $value) { if (!$this->container->hasParameter($key) || $this->container->getParameter($key) !== $value) { diff --git a/lib/symfony/dependency-injection/Container.php b/lib/symfony/dependency-injection/Container.php index 6f61eb869..0777a8755 100644 --- a/lib/symfony/dependency-injection/Container.php +++ b/lib/symfony/dependency-injection/Container.php @@ -16,11 +16,13 @@ use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentSer use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Contracts\Service\ResetInterface; @@ -36,11 +38,12 @@ class_exists(ArgumentServiceLocator::class); * The container can have four possible behaviors when a service * does not exist (or is not initialized for the last case): * - * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default) + * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at compilation time (the default) * * NULL_ON_INVALID_REFERENCE: Returns null * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference * (for instance, ignore a setter if the service does not exist) * * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references + * * RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at runtime * * @author Fabien Potencier * @author Johannes M. Schmitt @@ -58,9 +61,11 @@ class Container implements ContainerInterface, ResetInterface protected $resolving = []; protected $syntheticIds = []; - private $envCache = []; - private $compiled = false; - private $getEnv; + private array $envCache = []; + private bool $compiled = false; + private \Closure $getEnv; + + private static \Closure $make; public function __construct(ParameterBagInterface $parameterBag = null) { @@ -74,32 +79,33 @@ class Container implements ContainerInterface, ResetInterface * * * Parameter values are resolved; * * The parameter bag is frozen. + * + * @return void */ public function compile() { $this->parameterBag->resolve(); - $this->parameterBag = new FrozenParameterBag($this->parameterBag->all()); + $this->parameterBag = new FrozenParameterBag( + $this->parameterBag->all(), + $this->parameterBag instanceof ParameterBag ? $this->parameterBag->allDeprecated() : [] + ); $this->compiled = true; } /** * Returns true if the container is compiled. - * - * @return bool */ - public function isCompiled() + public function isCompiled(): bool { return $this->compiled; } /** * Gets the service container parameter bag. - * - * @return ParameterBagInterface */ - public function getParameterBag() + public function getParameterBag(): ParameterBagInterface { return $this->parameterBag; } @@ -109,28 +115,22 @@ class Container implements ContainerInterface, ResetInterface * * @return array|bool|string|int|float|\UnitEnum|null * - * @throws InvalidArgumentException if the parameter is not defined + * @throws ParameterNotFoundException if the parameter is not defined */ public function getParameter(string $name) { return $this->parameterBag->get($name); } - /** - * @return bool - */ - public function hasParameter(string $name) + public function hasParameter(string $name): bool { return $this->parameterBag->has($name); } /** - * Sets a parameter. - * - * @param string $name The parameter name - * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value + * @return void */ - public function setParameter(string $name, $value) + public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value) { $this->parameterBag->set($name, $value); } @@ -140,6 +140,8 @@ class Container implements ContainerInterface, ResetInterface * * Setting a synthetic service to null resets it: has() returns false and get() * behaves in the same way as if the service was never created. + * + * @return void */ public function set(string $id, ?object $service) { @@ -147,7 +149,7 @@ class Container implements ContainerInterface, ResetInterface if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) { $initialize = $this->privates['service_container']; unset($this->privates['service_container']); - $initialize(); + $initialize($this); } if ('service_container' === $id) { @@ -179,14 +181,7 @@ class Container implements ContainerInterface, ResetInterface $this->services[$id] = $service; } - /** - * Returns true if the given service is defined. - * - * @param string $id The service identifier - * - * @return bool - */ - public function has(string $id) + public function has(string $id): bool { if (isset($this->aliases[$id])) { $id = $this->aliases[$id]; @@ -204,19 +199,16 @@ class Container implements ContainerInterface, ResetInterface /** * Gets a service. * - * @return object|null - * * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined - * @throws \Exception if an exception has been thrown when the service has been resolved * * @see Reference */ - public function get(string $id, int $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1) + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object { return $this->services[$id] ?? $this->services[$id = $this->aliases[$id] ?? $id] - ?? ('service_container' === $id ? $this : ($this->factories[$id] ?? [$this, 'make'])($id, $invalidBehavior)); + ?? ('service_container' === $id ? $this : ($this->factories[$id] ?? self::$make ??= self::make(...))($this, $id, $invalidBehavior)); } /** @@ -224,41 +216,41 @@ class Container implements ContainerInterface, ResetInterface * * As a separate method to allow "get()" to use the really fast `??` operator. */ - private function make(string $id, int $invalidBehavior) + private static function make(self $container, string $id, int $invalidBehavior): ?object { - if (isset($this->loading[$id])) { - throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), [$id])); + if (isset($container->loading[$id])) { + throw new ServiceCircularReferenceException($id, array_merge(array_keys($container->loading), [$id])); } - $this->loading[$id] = true; + $container->loading[$id] = true; try { - if (isset($this->fileMap[$id])) { - return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]); - } elseif (isset($this->methodMap[$id])) { - return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); + if (isset($container->fileMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->load($container->fileMap[$id]); + } elseif (isset($container->methodMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->{$container->methodMap[$id]}($container); } } catch (\Exception $e) { - unset($this->services[$id]); + unset($container->services[$id]); throw $e; } finally { - unset($this->loading[$id]); + unset($container->loading[$id]); } - if (/* self::EXCEPTION_ON_INVALID_REFERENCE */ 1 === $invalidBehavior) { + if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { if (!$id) { throw new ServiceNotFoundException($id); } - if (isset($this->syntheticIds[$id])) { + if (isset($container->syntheticIds[$id])) { throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is synthetic, it needs to be set at boot time before it can be used.', $id)); } - if (isset($this->getRemovedIds()[$id])) { + if (isset($container->getRemovedIds()[$id])) { throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.', $id)); } $alternatives = []; - foreach ($this->getServiceIds() as $knownId) { + foreach ($container->getServiceIds() as $knownId) { if ('' === $knownId || '.' === $knownId[0]) { continue; } @@ -276,10 +268,8 @@ class Container implements ContainerInterface, ResetInterface /** * Returns true if the given service has actually been initialized. - * - * @return bool */ - public function initialized(string $id) + public function initialized(string $id): bool { if (isset($this->aliases[$id])) { $id = $this->aliases[$id]; @@ -293,7 +283,7 @@ class Container implements ContainerInterface, ResetInterface } /** - * {@inheritdoc} + * @return void */ public function reset() { @@ -305,7 +295,7 @@ class Container implements ContainerInterface, ResetInterface if ($service instanceof ResetInterface) { $service->reset(); } - } catch (\Throwable $e) { + } catch (\Throwable) { continue; } } @@ -316,43 +306,39 @@ class Container implements ContainerInterface, ResetInterface * * @return string[] */ - public function getServiceIds() + public function getServiceIds(): array { return array_map('strval', array_unique(array_merge(['service_container'], array_keys($this->fileMap), array_keys($this->methodMap), array_keys($this->aliases), array_keys($this->services)))); } /** * Gets service ids that existed at compile time. - * - * @return array */ - public function getRemovedIds() + public function getRemovedIds(): array { return []; } /** * Camelizes a string. - * - * @return string */ - public static function camelize(string $id) + public static function camelize(string $id): string { return strtr(ucwords(strtr($id, ['_' => ' ', '.' => '_ ', '\\' => '_ '])), [' ' => '']); } /** * A string to underscore. - * - * @return string */ - public static function underscore(string $id) + public static function underscore(string $id): string { return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], str_replace('_', '.', $id))); } /** * Creates a service by requiring its factory file. + * + * @return mixed */ protected function load(string $file) { @@ -362,11 +348,9 @@ class Container implements ContainerInterface, ResetInterface /** * Fetches a variable from the environment. * - * @return mixed - * * @throws EnvNotFoundException When the environment variable is not found and has no default value */ - protected function getEnv(string $name) + protected function getEnv(string $name): mixed { if (isset($this->resolving[$envName = "env($name)"])) { throw new ParameterCircularReferenceException(array_keys($this->resolving)); @@ -377,9 +361,7 @@ class Container implements ContainerInterface, ResetInterface if (!$this->has($id = 'container.env_var_processors_locator')) { $this->set($id, new ServiceLocator([])); } - if (!$this->getEnv) { - $this->getEnv = \Closure::fromCallable([$this, 'getEnv']); - } + $this->getEnv ??= $this->getEnv(...); $processors = $this->get($id); if (false !== $i = strpos($name, ':')) { @@ -389,7 +371,11 @@ class Container implements ContainerInterface, ResetInterface $prefix = 'string'; $localName = $name; } + $processor = $processors->has($prefix) ? $processors->get($prefix) : new EnvVarProcessor($this); + if (false === $i) { + $prefix = ''; + } $this->resolving[$envName] = true; try { @@ -400,14 +386,9 @@ class Container implements ContainerInterface, ResetInterface } /** - * @param string|false $registry - * @param string|bool $load - * - * @return mixed - * * @internal */ - final protected function getService($registry, string $id, ?string $method, $load) + final protected function getService(string|false $registry, string $id, ?string $method, string|bool $load): mixed { if ('service_container' === $id) { return $this; @@ -419,13 +400,13 @@ class Container implements ContainerInterface, ResetInterface return false !== $registry ? $this->{$registry}[$id] ?? null : null; } if (false !== $registry) { - return $this->{$registry}[$id] ?? $this->{$registry}[$id] = $load ? $this->load($method) : $this->{$method}(); + return $this->{$registry}[$id] ??= $load ? $this->load($method) : $this->{$method}($this); } if (!$load) { - return $this->{$method}(); + return $this->{$method}($this); } - return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method); + return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory($this) : $this->load($method); } private function __clone() diff --git a/lib/symfony/dependency-injection/ContainerAwareInterface.php b/lib/symfony/dependency-injection/ContainerAwareInterface.php index e7b9d575e..9b3709c96 100644 --- a/lib/symfony/dependency-injection/ContainerAwareInterface.php +++ b/lib/symfony/dependency-injection/ContainerAwareInterface.php @@ -15,11 +15,15 @@ namespace Symfony\Component\DependencyInjection; * ContainerAwareInterface should be implemented by classes that depends on a Container. * * @author Fabien Potencier + * + * @deprecated since Symfony 6.4, use dependency injection instead */ interface ContainerAwareInterface { /** * Sets the container. + * + * @return void */ - public function setContainer(ContainerInterface $container = null); + public function setContainer(?ContainerInterface $container); } diff --git a/lib/symfony/dependency-injection/ContainerAwareTrait.php b/lib/symfony/dependency-injection/ContainerAwareTrait.php index ee1ea2cb3..be6b225a3 100644 --- a/lib/symfony/dependency-injection/ContainerAwareTrait.php +++ b/lib/symfony/dependency-injection/ContainerAwareTrait.php @@ -11,20 +11,31 @@ namespace Symfony\Component\DependencyInjection; +trigger_deprecation('symfony/dependency-injection', '6.4', '"%s" is deprecated, use dependency injection instead.', ContainerAwareTrait::class); + /** * ContainerAware trait. * * @author Fabien Potencier + * + * @deprecated since Symfony 6.4, use dependency injection instead */ trait ContainerAwareTrait { /** - * @var ContainerInterface + * @var ContainerInterface|null */ protected $container; + /** + * @return void + */ public function setContainer(ContainerInterface $container = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/dependency-injection', '6.2', 'Calling "%s::%s()" without any arguments is deprecated, pass null explicitly instead.', __CLASS__, __FUNCTION__); + } + $this->container = $container; } } diff --git a/lib/symfony/dependency-injection/ContainerBuilder.php b/lib/symfony/dependency-injection/ContainerBuilder.php index 485630a74..f56072a35 100644 --- a/lib/symfony/dependency-injection/ContainerBuilder.php +++ b/lib/symfony/dependency-injection/ContainerBuilder.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection; use Composer\InstalledVersions; -use Psr\Container\ContainerInterface as PsrContainerInterface; use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Config\Resource\ComposerResource; use Symfony\Component\Config\Resource\DirectoryResource; @@ -23,6 +22,7 @@ use Symfony\Component\Config\Resource\ReflectionClassResource; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\LazyClosure; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocator; @@ -35,11 +35,13 @@ use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass; use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\LazyServiceInstantiator; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -57,97 +59,87 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * @var array */ - private $extensions = []; + private array $extensions = []; /** * @var array */ - private $extensionsByNs = []; + private array $extensionsByNs = []; /** * @var array */ - private $definitions = []; + private array $definitions = []; /** * @var array */ - private $aliasDefinitions = []; + private array $aliasDefinitions = []; /** * @var array */ - private $resources = []; + private array $resources = []; /** * @var array>> */ - private $extensionConfigs = []; + private array $extensionConfigs = []; - /** - * @var Compiler - */ - private $compiler; - - /** - * @var bool - */ - private $trackResources; - - /** - * @var InstantiatorInterface|null - */ - private $proxyInstantiator; - - /** - * @var ExpressionLanguage|null - */ - private $expressionLanguage; + private Compiler $compiler; + private bool $trackResources; + private InstantiatorInterface $proxyInstantiator; + private ExpressionLanguage $expressionLanguage; /** * @var ExpressionFunctionProviderInterface[] */ - private $expressionLanguageProviders = []; + private array $expressionLanguageProviders = []; /** * @var string[] with tag names used by findTaggedServiceIds */ - private $usedTags = []; + private array $usedTags = []; /** * @var string[][] a map of env var names to their placeholders */ - private $envPlaceholders = []; + private array $envPlaceholders = []; /** * @var int[] a map of env vars to their resolution counter */ - private $envCounters = []; + private array $envCounters = []; /** * @var string[] the list of vendor directories */ - private $vendors; + private array $vendors; + + /** + * @var string[] the list of paths in vendor directories + */ + private array $pathsInVendor = []; /** * @var array */ - private $autoconfiguredInstanceof = []; + private array $autoconfiguredInstanceof = []; /** * @var array */ - private $autoconfiguredAttributes = []; + private array $autoconfiguredAttributes = []; /** * @var array */ - private $removedIds = []; + private array $removedIds = []; /** * @var array */ - private $removedBindingIds = []; + private array $removedBindingIds = []; private const INTERNAL_TYPES = [ 'int' => true, @@ -169,20 +161,20 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $this->trackResources = interface_exists(ResourceInterface::class); $this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true)->setPublic(true)); - $this->setAlias(PsrContainerInterface::class, new Alias('service_container', false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage = 'The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it.'); - $this->setAlias(ContainerInterface::class, new Alias('service_container', false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage); } /** * @var array */ - private $classReflectors; + private array $classReflectors; /** * Sets the track resources flag. * * If you are not using the loaders and therefore don't want * to depend on the Config component, set this flag to false. + * + * @return void */ public function setResourceTracking(bool $track) { @@ -191,22 +183,25 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Checks if resources are tracked. - * - * @return bool */ - public function isTrackingResources() + public function isTrackingResources(): bool { return $this->trackResources; } /** * Sets the instantiator to be used when fetching proxies. + * + * @return void */ public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator) { $this->proxyInstantiator = $proxyInstantiator; } + /** + * @return void + */ public function registerExtension(ExtensionInterface $extension) { $this->extensions[$extension->getAlias()] = $extension; @@ -219,11 +214,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns an extension by alias or namespace. * - * @return ExtensionInterface - * * @throws LogicException if the extension is not registered */ - public function getExtension(string $name) + public function getExtension(string $name): ExtensionInterface { if (isset($this->extensions[$name])) { return $this->extensions[$name]; @@ -241,17 +234,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return array */ - public function getExtensions() + public function getExtensions(): array { return $this->extensions; } /** * Checks if we have an extension. - * - * @return bool */ - public function hasExtension(string $name) + public function hasExtension(string $name): bool { return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]); } @@ -261,7 +252,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return ResourceInterface[] */ - public function getResources() + public function getResources(): array { return array_values($this->resources); } @@ -269,7 +260,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * @return $this */ - public function addResource(ResourceInterface $resource) + public function addResource(ResourceInterface $resource): static { if (!$this->trackResources) { return $this; @@ -291,7 +282,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return $this */ - public function setResources(array $resources) + public function setResources(array $resources): static { if (!$this->trackResources) { return $this; @@ -309,11 +300,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return $this */ - public function addObjectResource($object) + public function addObjectResource(object|string $object): static { if ($this->trackResources) { if (\is_object($object)) { - $object = \get_class($object); + $object = $object::class; } if (!isset($this->classReflectors[$object])) { $this->classReflectors[$object] = new \ReflectionClass($object); @@ -402,7 +393,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @final */ - public function fileExists(string $path, $trackContents = true): bool + public function fileExists(string $path, bool|string $trackContents = true): bool { $exists = file_exists($path); @@ -440,7 +431,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @throws BadMethodCallException When this ContainerBuilder is compiled * @throws \LogicException if the extension is not registered */ - public function loadFromExtension(string $extension, array $values = null) + public function loadFromExtension(string $extension, array $values = null): static { if ($this->isCompiled()) { throw new BadMethodCallException('Cannot load from an extension on a compiled container.'); @@ -461,7 +452,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return $this */ - public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) + public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): static { $this->getCompiler()->addPass($pass, $type, $priority); @@ -472,31 +463,25 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns the compiler pass config which can then be modified. - * - * @return PassConfig */ - public function getCompilerPassConfig() + public function getCompilerPassConfig(): PassConfig { return $this->getCompiler()->getPassConfig(); } /** * Returns the compiler. - * - * @return Compiler */ - public function getCompiler() + public function getCompiler(): Compiler { - if (null === $this->compiler) { - $this->compiler = new Compiler(); - } - - return $this->compiler; + return $this->compiler ??= new Compiler(); } /** * Sets a service. * + * @return void + * * @throws BadMethodCallException When this ContainerBuilder is compiled */ public function set(string $id, ?object $service) @@ -513,6 +498,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Removes a service definition. + * + * @return void */ public function removeDefinition(string $id) { @@ -522,21 +509,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } - /** - * Returns true if the given service is defined. - * - * @param string $id The service identifier - * - * @return bool - */ - public function has(string $id) + public function has(string $id): bool { return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id); } /** - * @return object|null - * * @throws InvalidArgumentException when no definitions are available * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined @@ -544,16 +522,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @see Reference */ - public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): ?object { - if ($this->isCompiled() && isset($this->removedIds[$id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior) { - return parent::get($id); + if ($this->isCompiled() && isset($this->removedIds[$id])) { + return ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior ? parent::get($id) : null; } return $this->doGet($id, $invalidBehavior); } - private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, bool $isConstructorArgument = false) + private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, bool $isConstructorArgument = false): mixed { if (isset($inlineServices[$id])) { return $inlineServices[$id]; @@ -564,9 +542,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } try { if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { - return parent::get($id, $invalidBehavior); + return $this->privates[$id] ?? parent::get($id, $invalidBehavior); } - if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { + if (null !== $service = $this->privates[$id] ?? parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { return $service; } } catch (ServiceCircularReferenceException $e) { @@ -631,6 +609,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * parameter, the value will still be 'bar' as defined in the ContainerBuilder * constructor. * + * @return void + * * @throws BadMethodCallException When this ContainerBuilder is compiled */ public function merge(self $container) @@ -639,9 +619,21 @@ class ContainerBuilder extends Container implements TaggedContainerInterface throw new BadMethodCallException('Cannot merge on a compiled container.'); } - $this->addDefinitions($container->getDefinitions()); + foreach ($container->getDefinitions() as $id => $definition) { + if (!$definition->hasTag('container.excluded') || !$this->has($id)) { + $this->setDefinition($id, $definition); + } + } $this->addAliases($container->getAliases()); - $this->getParameterBag()->add($container->getParameterBag()->all()); + $parameterBag = $this->getParameterBag(); + $otherBag = $container->getParameterBag(); + $parameterBag->add($otherBag->all()); + + if ($parameterBag instanceof ParameterBag && $otherBag instanceof ParameterBag) { + foreach ($otherBag->allDeprecated() as $name => $deprecated) { + $parameterBag->deprecate($name, ...$deprecated); + } + } if ($this->trackResources) { foreach ($container->getResources() as $resource) { @@ -657,9 +649,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name)); } - if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) { - $envPlaceholders = $container->getParameterBag()->getEnvPlaceholders(); - $this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag()); + if ($parameterBag instanceof EnvPlaceholderParameterBag && $otherBag instanceof EnvPlaceholderParameterBag) { + $envPlaceholders = $otherBag->getEnvPlaceholders(); + $parameterBag->mergeEnvPlaceholders($otherBag); } else { $envPlaceholders = []; } @@ -697,7 +689,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return array> */ - public function getExtensionConfig(string $name) + public function getExtensionConfig(string $name): array { if (!isset($this->extensionConfigs[$name])) { $this->extensionConfigs[$name] = []; @@ -710,6 +702,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * Prepends a config array to the configs of the given extension. * * @param array $config + * + * @return void */ public function prependExtensionConfig(string $name, array $config) { @@ -720,6 +714,20 @@ class ContainerBuilder extends Container implements TaggedContainerInterface array_unshift($this->extensionConfigs[$name], $config); } + /** + * Deprecates a service container parameter. + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function deprecateParameter(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): void + { + if (!$this->parameterBag instanceof ParameterBag) { + throw new BadMethodCallException(sprintf('The parameter bag must be an instance of "%s" to call "%s".', ParameterBag::class, __METHOD__)); + } + + $this->parameterBag->deprecate($name, $package, $version, $message); + } + /** * Compiles the container. * @@ -738,6 +746,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * env vars or be replaced by uniquely identifiable placeholders. * Set to "true" when you want to use the current ContainerBuilder * directly, keep to "false" when the container is dumped instead. + * + * @return void */ public function compile(bool $resolveEnvPlaceholders = false) { @@ -781,10 +791,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } - /** - * {@inheritdoc} - */ - public function getServiceIds() + public function getServiceIds(): array { return array_map('strval', array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds()))); } @@ -794,7 +801,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return array */ - public function getRemovedIds() + public function getRemovedIds(): array { return $this->removedIds; } @@ -803,6 +810,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * Adds the service aliases. * * @param array $aliases + * + * @return void */ public function addAliases(array $aliases) { @@ -815,6 +824,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * Sets the service aliases. * * @param array $aliases + * + * @return void */ public function setAliases(array $aliases) { @@ -825,15 +836,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Sets an alias for an existing service. * - * @param string $alias The alias to create - * @param string|Alias $id The service to alias - * - * @return Alias - * * @throws InvalidArgumentException if the id is not a string or an Alias * @throws InvalidArgumentException if the alias is for itself */ - public function setAlias(string $alias, $id) + public function setAlias(string $alias, string|Alias $id): Alias { if ('' === $alias || '\\' === $alias[-1] || \strlen($alias) !== strcspn($alias, "\0\r\n'")) { throw new InvalidArgumentException(sprintf('Invalid alias id: "%s".', $alias)); @@ -841,8 +847,6 @@ class ContainerBuilder extends Container implements TaggedContainerInterface if (\is_string($id)) { $id = new Alias($id); - } elseif (!$id instanceof Alias) { - throw new InvalidArgumentException('$id must be a string, or an Alias object.'); } if ($alias === (string) $id) { @@ -854,6 +858,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->aliasDefinitions[$alias] = $id; } + /** + * @return void + */ public function removeAlias(string $alias) { if (isset($this->aliasDefinitions[$alias])) { @@ -862,10 +869,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } - /** - * @return bool - */ - public function hasAlias(string $id) + public function hasAlias(string $id): bool { return isset($this->aliasDefinitions[$id]); } @@ -873,17 +877,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * @return array */ - public function getAliases() + public function getAliases(): array { return $this->aliasDefinitions; } /** - * @return Alias - * * @throws InvalidArgumentException if the alias does not exist */ - public function getAlias(string $id) + public function getAlias(string $id): Alias { if (!isset($this->aliasDefinitions[$id])) { throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id)); @@ -897,10 +899,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * This methods allows for simple registration of service definition * with a fluid interface. - * - * @return Definition */ - public function register(string $id, string $class = null) + public function register(string $id, string $class = null): Definition { return $this->setDefinition($id, new Definition($class)); } @@ -910,10 +910,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * This method implements a shortcut for using setDefinition() with * an autowired definition. - * - * @return Definition */ - public function autowire(string $id, string $class = null) + public function autowire(string $id, string $class = null): Definition { return $this->setDefinition($id, (new Definition($class))->setAutowired(true)); } @@ -922,6 +920,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * Adds the service definitions. * * @param array $definitions + * + * @return void */ public function addDefinitions(array $definitions) { @@ -934,6 +934,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * Sets the service definitions. * * @param array $definitions + * + * @return void */ public function setDefinitions(array $definitions) { @@ -946,7 +948,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return array */ - public function getDefinitions() + public function getDefinitions(): array { return $this->definitions; } @@ -954,11 +956,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Sets a service definition. * - * @return Definition - * * @throws BadMethodCallException When this ContainerBuilder is compiled */ - public function setDefinition(string $id, Definition $definition) + public function setDefinition(string $id, Definition $definition): Definition { if ($this->isCompiled()) { throw new BadMethodCallException('Adding definition to a compiled container is not allowed.'); @@ -975,10 +975,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns true if a service definition exists under the given identifier. - * - * @return bool */ - public function hasDefinition(string $id) + public function hasDefinition(string $id): bool { return isset($this->definitions[$id]); } @@ -986,11 +984,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Gets a service definition. * - * @return Definition - * * @throws ServiceNotFoundException if the service definition does not exist */ - public function getDefinition(string $id) + public function getDefinition(string $id): Definition { if (!isset($this->definitions[$id])) { throw new ServiceNotFoundException($id); @@ -1004,11 +1000,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * The method "unaliases" recursively to return a Definition instance. * - * @return Definition - * * @throws ServiceNotFoundException if the service definition does not exist */ - public function findDefinition(string $id) + public function findDefinition(string $id): Definition { $seen = []; while (isset($this->aliasDefinitions[$id])) { @@ -1031,13 +1025,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Creates a service for a service definition. * - * @return mixed - * * @throws RuntimeException When the factory definition is incomplete * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool $tryProxy = true) + private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool|object $tryProxy = true): mixed { if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) { return $inlineServices[$h]; @@ -1056,12 +1048,41 @@ class ContainerBuilder extends Container implements TaggedContainerInterface trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } - if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) { + $parameterBag = $this->getParameterBag(); + $class = $parameterBag->resolveValue($definition->getClass()) ?: (['Closure', 'fromCallable'] === $definition->getFactory() ? 'Closure' : null); + + if (['Closure', 'fromCallable'] === $definition->getFactory() && ('Closure' !== $class || $definition->isLazy())) { + $callable = $parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArgument(0))); + + if ($callable instanceof Reference || $callable instanceof Definition) { + $callable = [$callable, '__invoke']; + } + + if (\is_array($callable) && ( + $callable[0] instanceof Reference + || $callable[0] instanceof Definition && !isset($inlineServices[spl_object_hash($callable[0])]) + )) { + $initializer = function () use ($callable, &$inlineServices) { + return $this->doResolveServices($callable[0], $inlineServices); + }; + + $proxy = eval('return '.LazyClosure::getCode('$initializer', $callable, $definition, $this, $id).';'); + $this->shareService($definition, $proxy, $id, $inlineServices); + + return $proxy; + } + } + + if (true === $tryProxy && $definition->isLazy() && ['Closure', 'fromCallable'] !== $definition->getFactory() + && !$tryProxy = !($proxy = $this->proxyInstantiator ??= new LazyServiceInstantiator()) || $proxy instanceof RealServiceInstantiator + ) { $proxy = $proxy->instantiateProxy( $this, - $definition, - $id, function () use ($definition, &$inlineServices, $id) { - return $this->createService($definition, $inlineServices, true, $id, false); + (clone $definition) + ->setClass($class) + ->setTags(($definition->hasTag('proxy') ? ['proxy' => $parameterBag->resolveValue($definition->getTag('proxy'))] : []) + $definition->getTags()), + $id, function ($proxy = false) use ($definition, &$inlineServices, $id) { + return $this->createService($definition, $inlineServices, true, $id, $proxy); } ); $this->shareService($definition, $proxy, $id, $inlineServices); @@ -1069,24 +1090,31 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $proxy; } - $parameterBag = $this->getParameterBag(); - if (null !== $definition->getFile()) { require_once $parameterBag->resolveValue($definition->getFile()); } - $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices, $isConstructorArgument); + $arguments = $definition->getArguments(); if (null !== $factory = $definition->getFactory()) { if (\is_array($factory)) { $factory = [$this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices, $isConstructorArgument), $factory[1]]; } elseif (!\is_string($factory)) { throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory.', $id)); + } elseif (str_starts_with($factory, '@=')) { + $factory = fn (ServiceLocator $arguments) => $this->getExpressionLanguage()->evaluate(substr($factory, 2), ['container' => $this, 'args' => $arguments]); + $arguments = [new ServiceLocatorArgument($arguments)]; } } - if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) { - return $this->services[$id]; + $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($arguments)), $inlineServices, $isConstructorArgument); + + if (null !== $id && $definition->isShared() && (isset($this->services[$id]) || isset($this->privates[$id])) && (true === $tryProxy || !$definition->isLazy())) { + return $this->services[$id] ?? $this->privates[$id]; + } + + if (!array_is_list($arguments)) { + $arguments = array_combine(array_map(fn ($k) => preg_replace('/^.*\\$/', '', $k), array_keys($arguments)), $arguments); } if (null !== $factory) { @@ -1100,9 +1128,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } } else { - $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); + $r = new \ReflectionClass($class); - $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs(array_values($arguments)); + if (\is_object($tryProxy)) { + if ($r->getConstructor()) { + $tryProxy->__construct(...$arguments); + } + + $service = $tryProxy; + } else { + $service = $r->getConstructor() ? $r->newInstanceArgs($arguments) : $r->newInstance(); + } if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) { trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name); @@ -1116,7 +1152,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } - if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) { + if (null === $lastWitherIndex && (true === $tryProxy || !$definition->isLazy())) { // share only if proxying failed, or if not a proxy, and if no withers are found $this->shareService($definition, $service, $id, $inlineServices); } @@ -1129,7 +1165,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface foreach ($definition->getMethodCalls() as $k => $call) { $service = $this->callMethod($service, $call, $inlineServices); - if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) { + if ($lastWitherIndex === $k && (true === $tryProxy || !$definition->isLazy())) { // share only if proxying failed, or if not a proxy, and this is the last wither $this->shareService($definition, $service, $id, $inlineServices); } @@ -1159,17 +1195,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Replaces service references by the real service instance and evaluates expressions. * - * @param mixed $value - * * @return mixed The same value with all service references replaced by * the real service instances and all expressions evaluated */ - public function resolveServices($value) + public function resolveServices(mixed $value): mixed { return $this->doResolveServices($value); } - private function doResolveServices($value, array &$inlineServices = [], bool $isConstructorArgument = false) + private function doResolveServices(mixed $value, array &$inlineServices = [], bool $isConstructorArgument = false): mixed { if (\is_array($value)) { foreach ($value as $k => $v) { @@ -1177,9 +1211,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } elseif ($value instanceof ServiceClosureArgument) { $reference = $value->getValues()[0]; - $value = function () use ($reference) { - return $this->resolveServices($reference); - }; + $value = fn () => $this->resolveServices($reference); } elseif ($value instanceof IteratorArgument) { $value = new RewindableGenerator(function () use ($value, &$inlineServices) { foreach ($value->getValues() as $k => $v) { @@ -1218,12 +1250,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } elseif ($value instanceof ServiceLocatorArgument) { $refs = $types = []; foreach ($value->getValues() as $k => $v) { - if ($v) { - $refs[$k] = [$v]; - $types[$k] = $v instanceof TypedReference ? $v->getType() : '?'; - } + $refs[$k] = [$v, null]; + $types[$k] = $v instanceof TypedReference ? $v->getType() : '?'; } - $value = new ServiceLocator(\Closure::fromCallable([$this, 'resolveServices']), $refs, $types); + $value = new ServiceLocator($this->resolveServices(...), $refs, $types); } elseif ($value instanceof Reference) { $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument); } elseif ($value instanceof Definition) { @@ -1255,12 +1285,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return array An array of tags with the tagged service as key, holding a list of attribute arrays */ - public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false) + public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false): array { $this->usedTags[] = $name; $tags = []; foreach ($this->getDefinitions() as $id => $definition) { - if ($definition->hasTag($name)) { + if ($definition->hasTag($name) && !$definition->hasTag('container.excluded')) { if ($throwOnAbstract && $definition->isAbstract()) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name)); } @@ -1276,7 +1306,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return string[] */ - public function findTags() + public function findTags(): array { $tags = []; foreach ($this->getDefinitions() as $id => $definition) { @@ -1291,11 +1321,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return string[] */ - public function findUnusedTags() + public function findUnusedTags(): array { return array_values(array_diff($this->findTags(), $this->usedTags)); } + /** + * @return void + */ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) { $this->expressionLanguageProviders[] = $provider; @@ -1304,17 +1337,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * @return ExpressionFunctionProviderInterface[] */ - public function getExpressionLanguageProviders() + public function getExpressionLanguageProviders(): array { return $this->expressionLanguageProviders; } /** * Returns a ChildDefinition that will be used for autoconfiguring the interface/class. - * - * @return ChildDefinition */ - public function registerForAutoconfiguration(string $interface) + public function registerForAutoconfiguration(string $interface): ChildDefinition { if (!isset($this->autoconfiguredInstanceof[$interface])) { $this->autoconfiguredInstanceof[$interface] = new ChildDefinition(''); @@ -1351,13 +1382,21 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ public function registerAliasForArgument(string $id, string $type, string $name = null): Alias { - $name = (new Target($name ?? $id))->name; + $parsedName = (new Target($name ??= $id))->getParsedName(); - if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) { - throw new InvalidArgumentException(sprintf('Invalid argument name "%s" for service "%s": the first character must be a letter.', $name, $id)); + if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $parsedName)) { + if ($id !== $name) { + $id = sprintf(' for service "%s"', $id); + } + + throw new InvalidArgumentException(sprintf('Invalid argument name "%s"'.$id.': the first character must be a letter.', $name)); } - return $this->setAlias($type.' $'.$name, $id); + if ($parsedName !== $name) { + $this->setAlias('.'.$type.' $'.$name, $type.' $'.$parsedName); + } + + return $this->setAlias($type.' $'.$parsedName, $id); } /** @@ -1365,7 +1404,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return array */ - public function getAutoconfiguredInstanceof() + public function getAutoconfiguredInstanceof(): array { return $this->autoconfiguredInstanceof; } @@ -1381,7 +1420,6 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Resolves env parameter placeholders in a string or an array. * - * @param mixed $value The value to resolve * @param string|true|null $format A sprintf() format returning the replacement for each env var name or * null to resolve back to the original "%env(VAR)%" format or * true to resolve to the actual values of the referenced env vars @@ -1389,14 +1427,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return mixed The value with env parameters resolved if a string or an array is passed */ - public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null) + public function resolveEnvPlaceholders(mixed $value, string|bool $format = null, array &$usedEnvs = null): mixed { - if (null === $format) { - $format = '%%env(%s)%%'; - } - $bag = $this->getParameterBag(); - if (true === $format) { + if (true === $format ??= '%%env(%s)%%') { $value = $bag->resolveValue($value); } @@ -1413,15 +1447,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $result; } - if (!\is_string($value) || 38 > \strlen($value) || !preg_match('/env[_(]/i', $value)) { + if (!\is_string($value) || 38 > \strlen($value) || false === stripos($value, 'env_')) { return $value; } $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; $completed = false; + preg_match_all('/env_[a-f0-9]{16}_\w+_[a-f0-9]{32}/Ui', $value, $matches); + $usedPlaceholders = array_flip($matches[0]); foreach ($envPlaceholders as $env => $placeholders) { foreach ($placeholders as $placeholder) { - if (false !== stripos($value, $placeholder)) { + if (isset($usedPlaceholders[$placeholder])) { if (true === $format) { $resolved = $bag->escapeValue($this->getEnv($env)); } else { @@ -1454,7 +1490,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @return int[] The number of time each env vars has been resolved */ - public function getEnvCounters() + public function getEnvCounters(): array { $bag = $this->getParameterBag(); $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; @@ -1471,7 +1507,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * @final */ - public function log(CompilerPassInterface $pass, string $message) + public function log(CompilerPassInterface $pass, string $message): void { $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message)); } @@ -1481,21 +1517,20 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * When parent packages are provided and if any of them is in dev-only mode, * the class will be considered available even if it is also in dev-only mode. + * + * @throws \LogicException If dependencies have been installed with Composer 1 */ final public static function willBeAvailable(string $package, string $class, array $parentPackages): bool { - $skipDeprecation = 3 < \func_num_args() && func_get_arg(3); - $hasRuntimeApi = class_exists(InstalledVersions::class); - - if (!$hasRuntimeApi && !$skipDeprecation) { - trigger_deprecation('symfony/dependency-injection', '5.4', 'Calling "%s" when dependencies have been installed with Composer 1 is deprecated. Consider upgrading to Composer 2.', __METHOD__); + if (!class_exists(InstalledVersions::class)) { + throw new \LogicException(sprintf('Calling "%s" when dependencies have been installed with Composer 1 is not supported. Consider upgrading to Composer 2.', __METHOD__)); } if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { return false; } - if (!$hasRuntimeApi || !InstalledVersions::isInstalled($package) || InstalledVersions::isInstalled($package, false)) { + if (!InstalledVersions::isInstalled($package) || InstalledVersions::isInstalled($package, false)) { return true; } @@ -1533,7 +1568,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @internal */ - public function removeBindings(string $id) + public function removeBindings(string $id): void { if ($this->hasDefinition($id)) { foreach ($this->getDefinition($id)->getBindings() as $key => $binding) { @@ -1544,15 +1579,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } /** - * Returns the Service Conditionals. - * - * @param mixed $value An array of conditionals to return - * * @return string[] * * @internal */ - public static function getServiceConditionals($value): array + public static function getServiceConditionals(mixed $value): array { $services = []; @@ -1568,15 +1599,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } /** - * Returns the initialized conditionals. - * - * @param mixed $value An array of conditionals to return - * * @return string[] * * @internal */ - public static function getInitializedConditionals($value): array + public static function getInitializedConditionals(mixed $value): array { $services = []; @@ -1592,23 +1619,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } /** - * Computes a reasonably unique hash of a value. - * - * @param mixed $value A serializable value - * - * @return string + * Computes a reasonably unique hash of a serializable value. */ - public static function hash($value) + public static function hash(mixed $value): string { $hash = substr(base64_encode(hash('sha256', serialize($value), true)), 0, 7); return str_replace(['/', '+'], ['.', '_'], $hash); } - /** - * {@inheritdoc} - */ - protected function getEnv(string $name) + protected function getEnv(string $name): mixed { $value = parent::getEnv($name); $bag = $this->getParameterBag(); @@ -1637,7 +1657,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } - private function callMethod(object $service, array $call, array &$inlineServices) + private function callMethod(object $service, array $call, array &$inlineServices): mixed { foreach (self::getServiceConditionals($call[1]) as $s) { if (!$this->has($s)) { @@ -1655,28 +1675,27 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return empty($call[2]) ? $service : $result; } - /** - * Shares a given service in the container. - * - * @param mixed $service - */ - private function shareService(Definition $definition, $service, ?string $id, array &$inlineServices) + private function shareService(Definition $definition, mixed $service, ?string $id, array &$inlineServices): void { $inlineServices[$id ?? spl_object_hash($definition)] = $service; if (null !== $id && $definition->isShared()) { - $this->services[$id] = $service; + if ($definition->isPrivate() && $this->isCompiled()) { + $this->privates[$id] = $service; + } else { + $this->services[$id] = $service; + } unset($this->loading[$id]); } } private function getExpressionLanguage(): ExpressionLanguage { - if (null === $this->expressionLanguage) { - if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { - throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + if (!isset($this->expressionLanguage)) { + if (!class_exists(Expression::class)) { + throw new LogicException('Expressions cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); } - $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders, null, $this->getEnv(...)); } return $this->expressionLanguage; @@ -1684,19 +1703,27 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private function inVendors(string $path): bool { - if (null === $this->vendors) { - $this->vendors = (new ComposerResource())->getVendors(); + $path = is_file($path) ? \dirname($path) : $path; + + if (isset($this->pathsInVendor[$path])) { + return $this->pathsInVendor[$path]; } + + $this->vendors ??= (new ComposerResource())->getVendors(); $path = realpath($path) ?: $path; + if (isset($this->pathsInVendor[$path])) { + return $this->pathsInVendor[$path]; + } + foreach ($this->vendors as $vendor) { if (str_starts_with($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { $this->addResource(new FileResource($vendor.'/composer/installed.json')); - return true; + return $this->pathsInVendor[$path] = true; } } - return false; + return $this->pathsInVendor[$path] = false; } } diff --git a/lib/symfony/dependency-injection/ContainerInterface.php b/lib/symfony/dependency-injection/ContainerInterface.php index 3a44b9967..f70a8a9a6 100644 --- a/lib/symfony/dependency-injection/ContainerInterface.php +++ b/lib/symfony/dependency-injection/ContainerInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\DependencyInjection; use Psr\Container\ContainerInterface as PsrContainerInterface; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; @@ -31,54 +31,42 @@ interface ContainerInterface extends PsrContainerInterface public const IGNORE_ON_UNINITIALIZED_REFERENCE = 4; /** - * Sets a service. + * @return void */ public function set(string $id, ?object $service); /** - * Gets a service. + * @template B of self::*_REFERENCE * - * @param string $id The service identifier - * @param int $invalidBehavior The behavior when the service does not exist + * @param B $invalidBehavior * - * @return object|null + * @psalm-return (B is self::EXCEPTION_ON_INVALID_REFERENCE|self::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE ? object : object|null) * * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined * * @see Reference */ - public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object; - /** - * @return bool - */ - public function has(string $id); + public function has(string $id): bool; /** * Check for whether or not a service has been initialized. - * - * @return bool */ - public function initialized(string $id); + public function initialized(string $id): bool; /** * @return array|bool|string|int|float|\UnitEnum|null * - * @throws InvalidArgumentException if the parameter is not defined + * @throws ParameterNotFoundException if the parameter is not defined */ public function getParameter(string $name); - /** - * @return bool - */ - public function hasParameter(string $name); + public function hasParameter(string $name): bool; /** - * Sets a parameter. - * - * @param string $name The parameter name - * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value + * @return void */ - public function setParameter(string $name, $value); + public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value); } diff --git a/lib/symfony/dependency-injection/Definition.php b/lib/symfony/dependency-injection/Definition.php index 7fc675255..71e6258a8 100644 --- a/lib/symfony/dependency-injection/Definition.php +++ b/lib/symfony/dependency-injection/Definition.php @@ -24,26 +24,26 @@ class Definition { private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%service_id%" service is deprecated. You should stop using it, as it will be removed in the future.'; - private $class; - private $file; - private $factory; - private $shared = true; - private $deprecation = []; - private $properties = []; - private $calls = []; - private $instanceof = []; - private $autoconfigured = false; - private $configurator; - private $tags = []; - private $public = false; - private $synthetic = false; - private $abstract = false; - private $lazy = false; - private $decoratedService; - private $autowired = false; - private $changes = []; - private $bindings = []; - private $errors = []; + private ?string $class = null; + private ?string $file = null; + private string|array|null $factory = null; + private bool $shared = true; + private array $deprecation = []; + private array $properties = []; + private array $calls = []; + private array $instanceof = []; + private bool $autoconfigured = false; + private string|array|null $configurator = null; + private array $tags = []; + private bool $public = false; + private bool $synthetic = false; + private bool $abstract = false; + private bool $lazy = false; + private ?array $decoratedService = null; + private bool $autowired = false; + private array $changes = []; + private array $bindings = []; + private array $errors = []; protected $arguments = []; @@ -52,14 +52,14 @@ class Definition * * Used to store the name of the inner id when using service decoration together with autowiring */ - public $innerServiceId; + public ?string $innerServiceId = null; /** * @internal * * Used to store the behavior to follow when using service decoration and the decorated service is invalid */ - public $decorationOnInvalid; + public ?int $decorationOnInvalid = null; public function __construct(string $class = null, array $arguments = []) { @@ -71,10 +71,8 @@ class Definition /** * Returns all changes tracked for the Definition object. - * - * @return array */ - public function getChanges() + public function getChanges(): array { return $this->changes; } @@ -86,7 +84,7 @@ class Definition * * @return $this */ - public function setChanges(array $changes) + public function setChanges(array $changes): static { $this->changes = $changes; @@ -100,7 +98,7 @@ class Definition * * @return $this */ - public function setFactory($factory) + public function setFactory(string|array|Reference|null $factory): static { $this->changes['factory'] = true; @@ -120,7 +118,7 @@ class Definition * * @return string|array|null The PHP function or an array containing a class/Reference and a method to call */ - public function getFactory() + public function getFactory(): string|array|null { return $this->factory; } @@ -135,7 +133,7 @@ class Definition * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ - public function setDecoratedService(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + public function setDecoratedService(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static { if ($renamedId && $id === $renamedId) { throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); @@ -161,7 +159,7 @@ class Definition * * @return array|null An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated */ - public function getDecoratedService() + public function getDecoratedService(): ?array { return $this->decoratedService; } @@ -171,7 +169,7 @@ class Definition * * @return $this */ - public function setClass(?string $class) + public function setClass(?string $class): static { $this->changes['class'] = true; @@ -183,9 +181,9 @@ class Definition /** * Gets the service class. * - * @return string|null + * @return class-string|null */ - public function getClass() + public function getClass(): ?string { return $this->class; } @@ -195,7 +193,7 @@ class Definition * * @return $this */ - public function setArguments(array $arguments) + public function setArguments(array $arguments): static { $this->arguments = $arguments; @@ -207,7 +205,7 @@ class Definition * * @return $this */ - public function setProperties(array $properties) + public function setProperties(array $properties): static { $this->properties = $properties; @@ -216,10 +214,8 @@ class Definition /** * Gets the properties to define when creating the service. - * - * @return array */ - public function getProperties() + public function getProperties(): array { return $this->properties; } @@ -227,11 +223,9 @@ class Definition /** * Sets a specific property. * - * @param mixed $value - * * @return $this */ - public function setProperty(string $name, $value) + public function setProperty(string $name, mixed $value): static { $this->properties[$name] = $value; @@ -241,11 +235,9 @@ class Definition /** * Adds an argument to pass to the service constructor/factory method. * - * @param mixed $argument An argument - * * @return $this */ - public function addArgument($argument) + public function addArgument(mixed $argument): static { $this->arguments[] = $argument; @@ -255,14 +247,11 @@ class Definition /** * Replaces a specific argument. * - * @param int|string $index - * @param mixed $argument - * * @return $this * * @throws OutOfBoundsException When the replaced argument does not exist */ - public function replaceArgument($index, $argument) + public function replaceArgument(int|string $index, mixed $argument): static { if (0 === \count($this->arguments)) { throw new OutOfBoundsException(sprintf('Cannot replace arguments for class "%s" if none have been configured yet.', $this->class)); @@ -284,12 +273,9 @@ class Definition /** * Sets a specific argument. * - * @param int|string $key - * @param mixed $value - * * @return $this */ - public function setArgument($key, $value) + public function setArgument(int|string $key, mixed $value): static { $this->arguments[$key] = $value; @@ -298,10 +284,8 @@ class Definition /** * Gets the arguments to pass to the service constructor/factory method. - * - * @return array */ - public function getArguments() + public function getArguments(): array { return $this->arguments; } @@ -309,13 +293,9 @@ class Definition /** * Gets an argument to pass to the service constructor/factory method. * - * @param int|string $index - * - * @return mixed - * * @throws OutOfBoundsException When the argument does not exist */ - public function getArgument($index) + public function getArgument(int|string $index): mixed { if (!\array_key_exists($index, $this->arguments)) { throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); @@ -329,7 +309,7 @@ class Definition * * @return $this */ - public function setMethodCalls(array $calls = []) + public function setMethodCalls(array $calls = []): static { $this->calls = []; foreach ($calls as $call) { @@ -350,7 +330,7 @@ class Definition * * @throws InvalidArgumentException on empty $method param */ - public function addMethodCall(string $method, array $arguments = [], bool $returnsClone = false) + public function addMethodCall(string $method, array $arguments = [], bool $returnsClone = false): static { if (empty($method)) { throw new InvalidArgumentException('Method name cannot be empty.'); @@ -365,7 +345,7 @@ class Definition * * @return $this */ - public function removeMethodCall(string $method) + public function removeMethodCall(string $method): static { foreach ($this->calls as $i => $call) { if ($call[0] === $method) { @@ -378,10 +358,8 @@ class Definition /** * Check if the current definition has a given method to call after service initialization. - * - * @return bool */ - public function hasMethodCall(string $method) + public function hasMethodCall(string $method): bool { foreach ($this->calls as $call) { if ($call[0] === $method) { @@ -394,10 +372,8 @@ class Definition /** * Gets the methods to call after service initialization. - * - * @return array */ - public function getMethodCalls() + public function getMethodCalls(): array { return $this->calls; } @@ -409,7 +385,7 @@ class Definition * * @return $this */ - public function setInstanceofConditionals(array $instanceof) + public function setInstanceofConditionals(array $instanceof): static { $this->instanceof = $instanceof; @@ -421,7 +397,7 @@ class Definition * * @return ChildDefinition[] */ - public function getInstanceofConditionals() + public function getInstanceofConditionals(): array { return $this->instanceof; } @@ -431,7 +407,7 @@ class Definition * * @return $this */ - public function setAutoconfigured(bool $autoconfigured) + public function setAutoconfigured(bool $autoconfigured): static { $this->changes['autoconfigured'] = true; @@ -440,10 +416,7 @@ class Definition return $this; } - /** - * @return bool - */ - public function isAutoconfigured() + public function isAutoconfigured(): bool { return $this->autoconfigured; } @@ -453,7 +426,7 @@ class Definition * * @return $this */ - public function setTags(array $tags) + public function setTags(array $tags): static { $this->tags = $tags; @@ -462,20 +435,16 @@ class Definition /** * Returns all tags. - * - * @return array */ - public function getTags() + public function getTags(): array { return $this->tags; } /** * Gets a tag by name. - * - * @return array */ - public function getTag(string $name) + public function getTag(string $name): array { return $this->tags[$name] ?? []; } @@ -485,7 +454,7 @@ class Definition * * @return $this */ - public function addTag(string $name, array $attributes = []) + public function addTag(string $name, array $attributes = []): static { $this->tags[$name][] = $attributes; @@ -494,10 +463,8 @@ class Definition /** * Whether this definition has a tag with the given name. - * - * @return bool */ - public function hasTag(string $name) + public function hasTag(string $name): bool { return isset($this->tags[$name]); } @@ -507,7 +474,7 @@ class Definition * * @return $this */ - public function clearTag(string $name) + public function clearTag(string $name): static { unset($this->tags[$name]); @@ -519,7 +486,7 @@ class Definition * * @return $this */ - public function clearTags() + public function clearTags(): static { $this->tags = []; @@ -531,7 +498,7 @@ class Definition * * @return $this */ - public function setFile(?string $file) + public function setFile(?string $file): static { $this->changes['file'] = true; @@ -542,10 +509,8 @@ class Definition /** * Gets the file to require before creating the service. - * - * @return string|null */ - public function getFile() + public function getFile(): ?string { return $this->file; } @@ -555,7 +520,7 @@ class Definition * * @return $this */ - public function setShared(bool $shared) + public function setShared(bool $shared): static { $this->changes['shared'] = true; @@ -566,10 +531,8 @@ class Definition /** * Whether this service is shared. - * - * @return bool */ - public function isShared() + public function isShared(): bool { return $this->shared; } @@ -579,7 +542,7 @@ class Definition * * @return $this */ - public function setPublic(bool $boolean) + public function setPublic(bool $boolean): static { $this->changes['public'] = true; @@ -590,34 +553,16 @@ class Definition /** * Whether this service is public facing. - * - * @return bool */ - public function isPublic() + public function isPublic(): bool { return $this->public; } - /** - * Sets if this service is private. - * - * @return $this - * - * @deprecated since Symfony 5.2, use setPublic() instead - */ - public function setPrivate(bool $boolean) - { - trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__); - - return $this->setPublic(!$boolean); - } - /** * Whether this service is private. - * - * @return bool */ - public function isPrivate() + public function isPrivate(): bool { return !$this->public; } @@ -627,7 +572,7 @@ class Definition * * @return $this */ - public function setLazy(bool $lazy) + public function setLazy(bool $lazy): static { $this->changes['lazy'] = true; @@ -638,10 +583,8 @@ class Definition /** * Whether this service is lazy. - * - * @return bool */ - public function isLazy() + public function isLazy(): bool { return $this->lazy; } @@ -652,7 +595,7 @@ class Definition * * @return $this */ - public function setSynthetic(bool $boolean) + public function setSynthetic(bool $boolean): static { $this->synthetic = $boolean; @@ -666,10 +609,8 @@ class Definition /** * Whether this definition is synthetic, that is not constructed by the * container, but dynamically injected. - * - * @return bool */ - public function isSynthetic() + public function isSynthetic(): bool { return $this->synthetic; } @@ -680,7 +621,7 @@ class Definition * * @return $this */ - public function setAbstract(bool $boolean) + public function setAbstract(bool $boolean): static { $this->abstract = $boolean; @@ -690,10 +631,8 @@ class Definition /** * Whether this definition is abstract, that means it merely serves as a * template for other definitions. - * - * @return bool */ - public function isAbstract() + public function isAbstract(): bool { return $this->abstract; } @@ -710,28 +649,8 @@ class Definition * * @throws InvalidArgumentException when the message template is invalid */ - public function setDeprecated(/* string $package, string $version, string $message */) + public function setDeprecated(string $package, string $version, string $message): static { - $args = \func_get_args(); - - if (\func_num_args() < 3) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); - - $status = $args[0] ?? true; - - if (!$status) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); - } - - $message = (string) ($args[1] ?? null); - $package = $version = ''; - } else { - $status = true; - $package = (string) $args[0]; - $version = (string) $args[1]; - $message = (string) $args[2]; - } - if ('' !== $message) { if (preg_match('#[\r\n]|\*/#', $message)) { throw new InvalidArgumentException('Invalid characters found in deprecation template.'); @@ -743,7 +662,7 @@ class Definition } $this->changes['deprecated'] = true; - $this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE] : []; + $this->deprecation = ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE]; return $this; } @@ -751,30 +670,12 @@ class Definition /** * Whether this definition is deprecated, that means it should not be called * anymore. - * - * @return bool */ - public function isDeprecated() + public function isDeprecated(): bool { return (bool) $this->deprecation; } - /** - * Message to use if this definition is deprecated. - * - * @deprecated since Symfony 5.1, use "getDeprecation()" instead. - * - * @param string $id Service id relying on this definition - * - * @return string - */ - public function getDeprecationMessage(string $id) - { - trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); - - return $this->getDeprecation($id)['message']; - } - /** * @param string $id Service id relying on this definition */ @@ -794,7 +695,7 @@ class Definition * * @return $this */ - public function setConfigurator($configurator) + public function setConfigurator(string|array|Reference|null $configurator): static { $this->changes['configurator'] = true; @@ -811,20 +712,16 @@ class Definition /** * Gets the configurator to call after the service is fully initialized. - * - * @return string|array|null */ - public function getConfigurator() + public function getConfigurator(): string|array|null { return $this->configurator; } /** * Is the definition autowired? - * - * @return bool */ - public function isAutowired() + public function isAutowired(): bool { return $this->autowired; } @@ -834,7 +731,7 @@ class Definition * * @return $this */ - public function setAutowired(bool $autowired) + public function setAutowired(bool $autowired): static { $this->changes['autowired'] = true; @@ -848,7 +745,7 @@ class Definition * * @return BoundArgument[] */ - public function getBindings() + public function getBindings(): array { return $this->bindings; } @@ -862,7 +759,7 @@ class Definition * * @return $this */ - public function setBindings(array $bindings) + public function setBindings(array $bindings): static { foreach ($bindings as $key => $binding) { if (0 < strpos($key, '$') && $key !== $k = preg_replace('/[ \t]*\$/', ' $', $key)) { @@ -882,11 +779,9 @@ class Definition /** * Add an error that occurred when building this Definition. * - * @param string|\Closure|self $error - * * @return $this */ - public function addError($error) + public function addError(string|\Closure|self $error): static { if ($error instanceof self) { $this->errors = array_merge($this->errors, $error->errors); @@ -899,10 +794,8 @@ class Definition /** * Returns any errors that occurred while building this Definition. - * - * @return array */ - public function getErrors() + public function getErrors(): array { foreach ($this->errors as $i => $error) { if ($error instanceof \Closure) { diff --git a/lib/symfony/dependency-injection/Dumper/DumperInterface.php b/lib/symfony/dependency-injection/Dumper/DumperInterface.php index a00f30bf1..616b658e3 100644 --- a/lib/symfony/dependency-injection/Dumper/DumperInterface.php +++ b/lib/symfony/dependency-injection/Dumper/DumperInterface.php @@ -20,8 +20,6 @@ interface DumperInterface { /** * Dumps the service container. - * - * @return string|array */ - public function dump(array $options = []); + public function dump(array $options = []): string|array; } diff --git a/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php b/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php index e8ca83834..11342815f 100644 --- a/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php +++ b/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php @@ -30,10 +30,10 @@ use Symfony\Component\DependencyInjection\Reference; */ class GraphvizDumper extends Dumper { - private $nodes; - private $edges; + private array $nodes; + private array $edges; // All values should be strings - private $options = [ + private array $options = [ 'graph' => ['ratio' => 'compress'], 'node' => ['fontsize' => '11', 'fontname' => 'Arial', 'shape' => 'record'], 'edge' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => '0.5'], @@ -53,10 +53,8 @@ class GraphvizDumper extends Dumper * * node.instance: The default options for services that are defined directly by object instances * * node.definition: The default options for services that are defined via service definition instances * * node.missing: The default options for missing services - * - * @return string */ - public function dump(array $options = []) + public function dump(array $options = []): string { foreach (['graph', 'node', 'edge', 'node.instance', 'node.definition', 'node.missing'] as $key) { if (isset($options[$key])) { @@ -157,13 +155,13 @@ class GraphvizDumper extends Dumper foreach ($container->getDefinitions() as $id => $definition) { $class = $definition->getClass(); - if ('\\' === substr($class, 0, 1)) { + if (str_starts_with($class, '\\')) { $class = substr($class, 1); } try { $class = $this->container->getParameterBag()->resolveValue($class); - } catch (ParameterNotFoundException $e) { + } catch (ParameterNotFoundException) { } $nodes[$id] = ['class' => str_replace('\\', '\\\\', $class), 'attributes' => array_merge($this->options['node.definition'], ['style' => $definition->isShared() ? 'filled' : 'dotted'])]; @@ -176,7 +174,7 @@ class GraphvizDumper extends Dumper } if (!$container->hasDefinition($id)) { - $nodes[$id] = ['class' => str_replace('\\', '\\\\', \get_class($container->get($id))), 'attributes' => $this->options['node.instance']]; + $nodes[$id] = ['class' => str_replace('\\', '\\\\', $container->get($id)::class), 'attributes' => $this->options['node.instance']]; } } diff --git a/lib/symfony/dependency-injection/Dumper/PhpDumper.php b/lib/symfony/dependency-injection/Dumper/PhpDumper.php index bd63d0689..ff11062b9 100644 --- a/lib/symfony/dependency-injection/Dumper/PhpDumper.php +++ b/lib/symfony/dependency-injection/Dumper/PhpDumper.php @@ -12,10 +12,11 @@ namespace Symfony\Component\DependencyInjection\Dumper; use Composer\Autoload\ClassLoader; -use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\LazyClosure; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocator; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; @@ -30,19 +31,19 @@ use Symfony\Component\DependencyInjection\Exception\EnvParameterException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\ExpressionLanguage; -use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; use Symfony\Component\DependencyInjection\Loader\FileLoader; use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\ErrorHandler\DebugClassLoader; use Symfony\Component\ExpressionLanguage\Expression; -use Symfony\Component\HttpKernel\Kernel; /** * PhpDumper dumps a service container as a PHP class. @@ -62,46 +63,39 @@ class PhpDumper extends Dumper */ public const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; - /** - * @var \SplObjectStorage|null - */ - private $definitionVariables; - private $referenceVariables; - private $variableCount; - private $inlinedDefinitions; - private $serviceCalls; - private $reservedVariables = ['instance', 'class', 'this', 'container']; - private $expressionLanguage; - private $targetDirRegex; - private $targetDirMaxMatches; - private $docStar; - private $serviceIdToMethodNameMap; - private $usedMethodNames; - private $namespace; - private $asFiles; - private $hotPathTag; - private $preloadTags; - private $inlineFactories; - private $inlineRequires; - private $inlinedRequires = []; - private $circularReferences = []; - private $singleUsePrivateIds = []; - private $preload = []; - private $addThrow = false; - private $addGetService = false; - private $locatedIds = []; - private $serviceLocatorTag; - private $exportedVariables = []; - private $baseClass; + /** @var \SplObjectStorage|null */ + private ?\SplObjectStorage $definitionVariables = null; + private ?array $referenceVariables = null; + private int $variableCount; + private ?\SplObjectStorage $inlinedDefinitions = null; + private ?array $serviceCalls = null; + private array $reservedVariables = ['instance', 'class', 'this', 'container']; + private ExpressionLanguage $expressionLanguage; + private ?string $targetDirRegex = null; + private int $targetDirMaxMatches; + private string $docStar; + private array $serviceIdToMethodNameMap; + private array $usedMethodNames; + private string $namespace; + private bool $asFiles; + private string $hotPathTag; + private array $preloadTags; + private bool $inlineFactories; + private bool $inlineRequires; + private array $inlinedRequires = []; + private array $circularReferences = []; + private array $singleUsePrivateIds = []; + private array $preload = []; + private bool $addGetService = false; + private array $locatedIds = []; + private string $serviceLocatorTag; + private array $exportedVariables = []; + private array $dynamicParameters = []; + private string $baseClass; + private string $class; + private DumperInterface $proxyDumper; + private bool $hasProxyDumper = true; - /** - * @var ProxyDumper - */ - private $proxyDumper; - - /** - * {@inheritdoc} - */ public function __construct(ContainerBuilder $container) { if (!$container->isCompiled()) { @@ -113,10 +107,13 @@ class PhpDumper extends Dumper /** * Sets the dumper to be used when dumping proxies in the generated container. + * + * @return void */ - public function setProxyDumper(ProxyDumper $proxyDumper) + public function setProxyDumper(DumperInterface $proxyDumper) { $this->proxyDumper = $proxyDumper; + $this->hasProxyDumper = !$proxyDumper instanceof NullDumper; } /** @@ -133,12 +130,13 @@ class PhpDumper extends Dumper * * @throws EnvParameterException When an env var exists but has not been dumped */ - public function dump(array $options = []) + public function dump(array $options = []): string|array { $this->locatedIds = []; $this->targetDirRegex = null; $this->inlinedRequires = []; $this->exportedVariables = []; + $this->dynamicParameters = []; $options = array_merge([ 'class' => 'ProjectServiceContainer', 'base_class' => 'Container', @@ -147,21 +145,44 @@ class PhpDumper extends Dumper 'debug' => true, 'hot_path_tag' => 'container.hot_path', 'preload_tags' => ['container.preload', 'container.no_preload'], - 'inline_factories_parameter' => 'container.dumper.inline_factories', - 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', + 'inline_factories_parameter' => 'container.dumper.inline_factories', // @deprecated since Symfony 6.3 + 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', // @deprecated since Symfony 6.3 + 'inline_factories' => null, + 'inline_class_loader' => null, 'preload_classes' => [], 'service_locator_tag' => 'container.service_locator', 'build_time' => time(), ], $options); - $this->addThrow = $this->addGetService = false; + $this->addGetService = false; $this->namespace = $options['namespace']; $this->asFiles = $options['as_files']; $this->hotPathTag = $options['hot_path_tag']; $this->preloadTags = $options['preload_tags']; - $this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']); - $this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : (\PHP_VERSION_ID < 70400 || $options['debug'])); + + $this->inlineFactories = false; + if (isset($options['inline_factories'])) { + $this->inlineFactories = $this->asFiles && $options['inline_factories']; + } elseif (!$options['inline_factories_parameter']) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_factories_parameter" passed to "%s()" is deprecated, use option "inline_factories" instead.', __METHOD__); + } elseif ($this->container->hasParameter($options['inline_factories_parameter'])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_factories_parameter" passed to "%s()" is deprecated, use option "inline_factories" instead.', __METHOD__); + $this->inlineFactories = $this->asFiles && $this->container->getParameter($options['inline_factories_parameter']); + } + + $this->inlineRequires = $options['debug']; + if (isset($options['inline_class_loader'])) { + $this->inlineRequires = $options['inline_class_loader']; + } elseif (!$options['inline_class_loader_parameter']) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_class_loader_parameter" passed to "%s()" is deprecated, use option "inline_class_loader" instead.', __METHOD__); + $this->inlineRequires = false; + } elseif ($this->container->hasParameter($options['inline_class_loader_parameter'])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_class_loader_parameter" passed to "%s()" is deprecated, use option "inline_class_loader" instead.', __METHOD__); + $this->inlineRequires = $this->container->getParameter($options['inline_class_loader_parameter']); + } + $this->serviceLocatorTag = $options['service_locator_tag']; + $this->class = $options['class']; if (!str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass); @@ -174,17 +195,9 @@ class PhpDumper extends Dumper $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); - if ($this->getProxyDumper() instanceof NullDumper) { + if (!$this->hasProxyDumper) { (new AnalyzeServiceReferencesPass(true, false))->process($this->container); - try { - (new CheckCircularReferencesPass())->process($this->container); - } catch (ServiceCircularReferenceException $e) { - $path = $e->getPath(); - end($path); - $path[key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge'; - - throw new ServiceCircularReferenceException($e->getServiceId(), $path); - } + (new CheckCircularReferencesPass())->process($this->container); } $this->analyzeReferences(); @@ -221,19 +234,20 @@ class PhpDumper extends Dumper $this->preload = array_combine($options['preload_classes'], $options['preload_classes']); } + $code = $this->addDefaultParametersMethod(); $code = $this->startClass($options['class'], $baseClass, $this->inlineFactories && $proxyClasses). $this->addServices($services). $this->addDeprecatedAliases(). - $this->addDefaultParametersMethod() + $code ; - $proxyClasses = $proxyClasses ?? $this->generateProxyClasses(); + $proxyClasses ??= $this->generateProxyClasses(); if ($this->addGetService) { $code = preg_replace( - "/(\r?\n\r?\n public function __construct.+?\\{\r?\n)/s", - "\n protected \$getService;$1 \$this->getService = \\Closure::fromCallable([\$this, 'getService']);\n", + "/\r?\n\r?\n public function __construct.+?\\{\r?\n/s", + "\n protected \Closure \$getService;$0", $code, 1 ); @@ -326,7 +340,7 @@ EOF; use Symfony\Component\DependencyInjection\Dumper\Preloader; -if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) { +if (in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { return; } @@ -341,7 +355,7 @@ EOF; if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) { continue; } - if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || ((new \ReflectionClass($class))->isUserDefined() && !\in_array($class, ['Attribute', 'JsonException', 'ReturnTypeWillChange', 'Stringable', 'UnhandledMatchError', 'ValueError'], true))) { + if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) { $code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class); } } @@ -374,6 +388,7 @@ return new \\Container{$hash}\\{$options['class']}([ 'container.build_hash' => '$hash', 'container.build_id' => '$id', 'container.build_time' => $time, + 'container.runtime_mode' => \\in_array(\\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? 'web=0' : 'web=1', ], __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}'); EOF; @@ -389,6 +404,7 @@ EOF; $this->circularReferences = []; $this->locatedIds = []; $this->exportedVariables = []; + $this->dynamicParameters = []; $this->preload = []; $unusedEnvs = []; @@ -407,18 +423,14 @@ EOF; /** * Retrieves the currently set proxy dumper or instantiates one. */ - private function getProxyDumper(): ProxyDumper + private function getProxyDumper(): DumperInterface { - if (!$this->proxyDumper) { - $this->proxyDumper = new NullDumper(); - } - - return $this->proxyDumper; + return $this->proxyDumper ??= new LazyServiceDumper($this->class); } - private function analyzeReferences() + private function analyzeReferences(): void { - (new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container); + (new AnalyzeServiceReferencesPass(false, $this->hasProxyDumper))->process($this->container); $checkedNodes = []; $this->circularReferences = []; $this->singleUsePrivateIds = []; @@ -445,13 +457,13 @@ EOF; foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); - if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isLazy() || $edge->isWeak()) { + if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) { continue; } if (isset($path[$id])) { $loop = null; - $loopByConstructor = $edge->isReferencedByConstructor(); + $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); $pathInLoop = [$id, []]; foreach ($path as $k => $pathByConstructor) { if (null !== $loop) { @@ -465,7 +477,7 @@ EOF; } $this->addCircularReferences($id, $loop, $loopByConstructor); } elseif (!isset($checkedNodes[$id])) { - $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor()); + $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor() && !$edge->isLazy()); } elseif (isset($loops[$id])) { // we already had detected loops for this edge // let's check if we have a common ancestor in one of the detected loops @@ -486,7 +498,7 @@ EOF; // we can now build the loop $loop = null; - $loopByConstructor = $edge->isReferencedByConstructor(); + $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); foreach ($fillPath as $k => $pathByConstructor) { if (null !== $loop) { $loop[] = $k; @@ -495,7 +507,7 @@ EOF; $loop = []; } } - $this->addCircularReferences($first, $loop, true); + $this->addCircularReferences($first, $loop, $loopByConstructor); break; } } @@ -503,7 +515,7 @@ EOF; unset($path[$sourceId]); } - private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor) + private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor): void { $currentId = $sourceId; $currentPath = array_reverse($currentPath); @@ -517,7 +529,7 @@ EOF; } } - private function collectLineage(string $class, array &$lineage) + private function collectLineage(string $class, array &$lineage): void { if (isset($lineage[$class])) { return; @@ -559,20 +571,35 @@ EOF; $proxyClasses = []; $alreadyGenerated = []; $definitions = $this->container->getDefinitions(); - $strip = '' === $this->docStar && method_exists(Kernel::class, 'stripComments'); + $strip = '' === $this->docStar; $proxyDumper = $this->getProxyDumper(); ksort($definitions); - foreach ($definitions as $definition) { - if (!$proxyDumper->isProxyCandidate($definition)) { + foreach ($definitions as $id => $definition) { + if (!$definition = $this->isProxyCandidate($definition, $asGhostObject, $id)) { continue; } - if (isset($alreadyGenerated[$class = $definition->getClass()])) { + if (isset($alreadyGenerated[$asGhostObject][$class = $definition->getClass()])) { continue; } - $alreadyGenerated[$class] = true; - // register class' reflector for resource tracking - $this->container->getReflectionClass($class); - if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition)) { + $alreadyGenerated[$asGhostObject][$class] = true; + + foreach (array_column($definition->getTag('proxy'), 'interface') ?: [$class] as $r) { + if (!$r = $this->container->getReflectionClass($r)) { + continue; + } + do { + $file = $r->getFileName(); + if (str_ends_with($file, ') : eval()\'d code')) { + $file = substr($file, 0, strrpos($file, '(', -17)); + } + if (is_file($file)) { + $this->container->addResource(new FileResource($file)); + } + $r = $r->getParentClass() ?: null; + } while ($r?->isUserDefined()); + } + + if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition, $id)) { continue; } @@ -593,7 +620,7 @@ EOF; if ($strip) { $proxyCode = "inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1]; @@ -608,11 +635,11 @@ EOF; return $proxyClasses; } - private function addServiceInclude(string $cId, Definition $definition): string + private function addServiceInclude(string $cId, Definition $definition, bool $isProxyCandidate): string { $code = ''; - if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) { + if ($this->inlineRequires && (!$this->isHotPath($definition) || $isProxyCandidate)) { $lineage = []; foreach ($this->inlinedDefinitions as $def) { if (!$def->isDeprecated()) { @@ -666,7 +693,8 @@ EOF; throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); } - $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); + $asGhostObject = false; + $isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id); $instantiation = ''; $lastWitherIndex = null; @@ -677,7 +705,7 @@ EOF; } if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) { - $instantiation = sprintf('$this->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); + $instantiation = sprintf('$container->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); } elseif (!$isSimpleInstance) { $instantiation = '$instance'; } @@ -689,7 +717,7 @@ EOF; $instantiation .= ' = '; } - return $this->addNewInstance($definition, ' '.$return.$instantiation, $id); + return $this->addNewInstance($definition, ' '.$return.$instantiation, $id, $asGhostObject); } private function isTrivialInstance(Definition $definition): bool @@ -752,8 +780,8 @@ EOF; $witherAssignation = ''; if ($call[2] ?? false) { - if (null !== $sharedNonLazyId && $lastWitherIndex === $k) { - $witherAssignation = sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); + if (null !== $sharedNonLazyId && $lastWitherIndex === $k && 'instance' === $variableName) { + $witherAssignation = sprintf('$container->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); } $witherAssignation .= sprintf('$%s = ', $variableName); } @@ -817,7 +845,7 @@ EOF; $return[] = sprintf(str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); } elseif ($definition->getFactory()) { $factory = $definition->getFactory(); - if (\is_string($factory)) { + if (\is_string($factory) && !str_starts_with($factory, '@=')) { $return[] = sprintf('@return object An instance returned by %s()', $factory); } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { $class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0]; @@ -845,7 +873,7 @@ EOF; $methodName = $this->generateMethodName($id); if ($asFile || $definition->isLazy()) { - $lazyInitialization = '$lazyLoad = true'; + $lazyInitialization = ', $lazyLoad = true'; } else { $lazyInitialization = ''; } @@ -860,22 +888,20 @@ EOF; $code = str_replace('*/', ' ', $code).<<hasErrors() && $e = $definition->getErrors()) { - $this->addThrow = true; - - $code .= sprintf(" \$this->throw(%s);\n", $this->export(reset($e))); + $code .= sprintf(" throw new RuntimeException(%s);\n", $this->export(reset($e))); } else { $this->serviceCalls = []; $this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls); @@ -892,31 +918,32 @@ EOF; } if (!$definition->isShared()) { - $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + $factory = sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); } - if ($isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition)) { - if (!$definition->isShared()) { - $code .= sprintf(' %s = %1$s ?? ', $factory); + $asGhostObject = false; + if ($isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id)) { + $definition = $isProxyCandidate; - if ($asFile) { - $code .= "function () {\n"; - $code .= " return self::do(\$container);\n"; - $code .= " };\n\n"; + if (!$definition->isShared()) { + $code .= sprintf(' %s ??= ', $factory); + + if ($definition->isPublic()) { + $code .= sprintf("fn () => self::%s(\$container);\n\n", $asFile ? 'do' : $methodName); } else { - $code .= sprintf("\\Closure::fromCallable([\$this, '%s']);\n\n", $methodName); + $code .= sprintf("self::%s(...);\n\n", $asFile ? 'do' : $methodName); } } + $lazyLoad = $asGhostObject ? '$proxy' : 'false'; - $factoryCode = $asFile ? 'self::do($container, false)' : sprintf('$this->%s(false)', $methodName); - $factoryCode = $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); - $code .= $asFile ? preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $factoryCode) : $factoryCode; + $factoryCode = $asFile ? sprintf('self::do($container, %s)', $lazyLoad) : sprintf('self::%s($container, %s)', $methodName, $lazyLoad); + $code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); } - $c = $this->addServiceInclude($id, $definition); + $c = $this->addServiceInclude($id, $definition, null !== $isProxyCandidate); if ('' !== $c && $isProxyCandidate && !$definition->isShared()) { - $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c))); + $c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); $code .= " static \$include = true;\n\n"; $code .= " if (\$include) {\n"; $code .= $c; @@ -929,20 +956,15 @@ EOF; $c = $this->addInlineService($id, $definition); if (!$isProxyCandidate && !$definition->isShared()) { - $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c))); - $lazyloadInitialization = $definition->isLazy() ? '$lazyLoad = true' : ''; + $c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); + $lazyloadInitialization = $definition->isLazy() ? ', $lazyLoad = true' : ''; - $c = sprintf(" %s = function (%s) {\n%s };\n\n return %1\$s();\n", $factory, $lazyloadInitialization, $c); + $c = sprintf(" %s = function (\$container%s) {\n%s };\n\n return %1\$s(\$container);\n", $factory, $lazyloadInitialization, $c); } $code .= $c; } - if ($asFile) { - $code = str_replace('$this', '$container', $code); - $code = preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $code); - } - $code .= " }\n"; $this->definitionVariables = $this->inlinedDefinitions = null; @@ -988,7 +1010,7 @@ EOF; return ''; } - $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]); + $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]) && !($this->hasProxyDumper && $definition->isLazy()); if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) { $code = $this->addInlineService($id, $definition, $definition); @@ -1012,8 +1034,8 @@ EOF; $code .= sprintf(<<<'EOTXT' - if (isset($this->%s[%s])) { - return $this->%1$s[%2$s]; + if (isset($container->%s[%s])) { + return $container->%1$s[%2$s]; } EOTXT @@ -1031,13 +1053,13 @@ EOTXT if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) { - if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId]) { + if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) { $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); } } } - if (isset($this->definitionVariables[$inlineDef = $inlineDef ?: $definition])) { + if (isset($this->definitionVariables[$inlineDef ??= $definition])) { return $code; } @@ -1051,6 +1073,9 @@ EOTXT return $code; } + $asGhostObject = false; + $isProxyCandidate = $this->isProxyCandidate($inlineDef, $asGhostObject, $id); + if (isset($this->definitionVariables[$inlineDef])) { $isSimpleInstance = false; } else { @@ -1071,15 +1096,15 @@ EOTXT } $code .= $this->addServiceProperties($inlineDef, $name); - $code .= $this->addServiceMethodCalls($inlineDef, $name, !$this->getProxyDumper()->isProxyCandidate($inlineDef) && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null); + $code .= $this->addServiceMethodCalls($inlineDef, $name, !$isProxyCandidate && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null); $code .= $this->addServiceConfigurator($inlineDef, $name); } - if ($isRootInstance && !$isSimpleInstance) { - $code .= "\n return \$instance;\n"; + if (!$isRootInstance || $isSimpleInstance) { + return $code; } - return $code; + return $code."\n return \$instance;\n"; } private function addServices(array &$services = null): string @@ -1124,9 +1149,9 @@ EOTXT } } - private function addNewInstance(Definition $definition, string $return = '', string $id = null): string + private function addNewInstance(Definition $definition, string $return = '', string $id = null, bool $asGhostObject = false): string { - $tail = $return ? ";\n" : ''; + $tail = $return ? str_repeat(')', substr_count($return, '(') - substr_count($return, ')')).";\n" : ''; if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) { $arguments = []; @@ -1145,41 +1170,83 @@ EOTXT if (null !== $definition->getFactory()) { $callable = $definition->getFactory(); - if (\is_array($callable)) { - if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { - throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a')); - } - - if ($callable[0] instanceof Reference - || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { - return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; - } - - $class = $this->dumpValue($callable[0]); - // If the class is a string we can optimize away - if (str_starts_with($class, "'") && !str_contains($class, '$')) { - if ("''" === $class) { - throw new RuntimeException(sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline')); - } - - return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; - } - - if (str_starts_with($class, 'new ')) { - return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; - } - - return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + if ('current' === $callable && [0] === array_keys($definition->getArguments()) && \is_array($value) && [0] === array_keys($value)) { + return $return.$this->dumpValue($value[0]).$tail; } - return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail; + if (['Closure', 'fromCallable'] === $callable) { + $callable = $definition->getArgument(0); + if ($callable instanceof ServiceClosureArgument) { + return $return.$this->dumpValue($callable).$tail; + } + + $arguments = ['...']; + + if ($callable instanceof Reference || $callable instanceof Definition) { + $callable = [$callable, '__invoke']; + } + } + + if (\is_string($callable) && str_starts_with($callable, '@=')) { + return $return.sprintf('(($args = %s) ? (%s) : null)', + $this->dumpValue(new ServiceLocatorArgument($definition->getArguments())), + $this->getExpressionLanguage()->compile(substr($callable, 2), ['container' => 'container', 'args' => 'args']) + ).$tail; + } + + if (!\is_array($callable)) { + return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail; + } + + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a')); + } + + if (['...'] === $arguments && ($definition->isLazy() || 'Closure' !== ($definition->getClass() ?? 'Closure')) && ( + $callable[0] instanceof Reference + || ($callable[0] instanceof Definition && !$this->definitionVariables->contains($callable[0])) + )) { + $initializer = 'fn () => '.$this->dumpValue($callable[0]); + + return $return.LazyClosure::getCode($initializer, $callable, $definition, $this->container, $id).$tail; + } + + if ($callable[0] instanceof Reference + || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) + ) { + return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + } + + $class = $this->dumpValue($callable[0]); + // If the class is a string we can optimize away + if (str_starts_with($class, "'") && !str_contains($class, '$')) { + if ("''" === $class) { + throw new RuntimeException(sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline')); + } + + return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + } + + if (str_starts_with($class, 'new ')) { + return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + } + + return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; } if (null === $class = $definition->getClass()) { throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); } - return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail; + if (!$asGhostObject) { + return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail; + } + + if (!method_exists($this->container->getParameterBag()->resolveValue($class), '__construct')) { + return $return.'$lazyLoad'.$tail; + } + + return $return.sprintf('($lazyLoad->__construct(%s) && false ?: $lazyLoad)', implode(', ', $arguments)).$tail; } private function startClass(string $class, string $baseClass, bool $hasProxyClasses): string @@ -1192,8 +1259,8 @@ $namespaceLine use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; @@ -1203,17 +1270,17 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; */ class $class extends $baseClass { + private const DEPRECATED_PARAMETERS = []; + protected \$parameters = []; public function __construct() { EOF; + $code = str_replace(" private const DEPRECATED_PARAMETERS = [];\n\n", $this->addDeprecatedParameters(), $code); if ($this->asFiles) { - $code = str_replace('$parameters = []', "\$containerDir;\n protected \$parameters = [];\n private \$buildParameters", $code); - $code = str_replace('__construct()', '__construct(array $buildParameters = [], $containerDir = __DIR__)', $code); - $code .= " \$this->buildParameters = \$buildParameters;\n"; - $code .= " \$this->containerDir = \$containerDir;\n"; + $code = str_replace('__construct()', '__construct(private array $buildParameters = [], protected string $containerDir = __DIR__)', $code); if (null !== $this->targetDirRegex) { $code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code); @@ -1262,7 +1329,7 @@ EOF; if ($this->asFiles && !$this->inlineFactories) { $code .= <<<'EOF' - protected function load($file, $lazyLoad = true) + protected function load($file, $lazyLoad = true): mixed { if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { return $class::do($this, $lazyLoad); @@ -1282,9 +1349,8 @@ EOF; EOF; } - $proxyDumper = $this->getProxyDumper(); foreach ($this->container->getDefinitions() as $definition) { - if (!$proxyDumper->isProxyCandidate($definition)) { + if (!$definition->isLazy() || !$this->hasProxyDumper) { continue; } @@ -1359,6 +1425,24 @@ EOF; EOF; } + private function addDeprecatedParameters(): string + { + if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag) { + return ''; + } + + if (!$deprecated = $bag->allDeprecated()) { + return ''; + } + $code = ''; + ksort($deprecated); + foreach ($deprecated as $param => $deprecation) { + $code .= ' '.$this->doExport($param).' => ['.implode(', ', array_map($this->doExport(...), $deprecation))."],\n"; + } + + return " private const DEPRECATED_PARAMETERS = [\n{$code} ];\n\n"; + } + private function addMethodMap(): string { $code = ''; @@ -1441,11 +1525,11 @@ EOF; * * @return object The "$id" service. */ - protected function {$methodNameAlias}() + protected static function {$methodNameAlias}(\$container) { trigger_deprecation($packageExported, $versionExported, $messageExported); - return \$this->get($idExported); + return \$container->get($idExported); } EOF; @@ -1462,7 +1546,7 @@ EOF; foreach ($hotPathServices as $id => $tags) { $definition = $this->container->getDefinition($id); - if ($this->getProxyDumper()->isProxyCandidate($definition)) { + if ($definition->isLazy() && $this->hasProxyDumper) { continue; } @@ -1488,7 +1572,7 @@ EOF; $code .= "\n include_once __DIR__.'/proxy-classes.php';"; } - return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : ''; + return $code ? sprintf("\n \$this->privates['service_container'] = static function (\$container) {%s\n };\n", $code) : ''; } private function addDefaultParametersMethod(): string @@ -1508,8 +1592,9 @@ EOF; $export = $this->exportParameters([$value], '', 12, $hasEnum); $export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2); - if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) { - $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]); + if ($hasEnum || preg_match("/\\\$container->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w*+'\)|targetDir\.'')/", $export[1])) { + $dynamicPhp[$key] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); + $this->dynamicParameters[$key] = true; } else { $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); } @@ -1518,17 +1603,18 @@ EOF; $code = <<<'EOF' - /** - * @return array|bool|float|int|string|\UnitEnum|null - */ - public function getParameter(string $name) + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null { + if (isset(self::DEPRECATED_PARAMETERS[$name])) { + trigger_deprecation(...self::DEPRECATED_PARAMETERS[$name]); + } + if (isset($this->buildParameters[$name])) { return $this->buildParameters[$name]; } if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { - throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); + throw new ParameterNotFoundException($name); } if (isset($this->loadedDynamicParameters[$name])) { return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); @@ -1553,7 +1639,7 @@ EOF; public function getParameterBag(): ParameterBagInterface { - if (null === $this->parameterBag) { + if (!isset($this->parameterBag)) { $parameters = $this->parameters; foreach ($this->loadedDynamicParameters as $name => $loaded) { $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); @@ -1561,24 +1647,31 @@ EOF; foreach ($this->buildParameters as $name => $value) { $parameters[$name] = $value; } - $this->parameterBag = new FrozenParameterBag($parameters); + $this->parameterBag = new FrozenParameterBag($parameters, self::DEPRECATED_PARAMETERS); } return $this->parameterBag; } EOF; + if (!$this->asFiles) { $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code); } + if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag || !$bag->allDeprecated()) { + $code = preg_replace("/\n.*DEPRECATED_PARAMETERS.*\n.*\n.*\n/m", '', $code, 1); + $code = str_replace(', self::DEPRECATED_PARAMETERS', '', $code); + } + if ($dynamicPhp) { $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); $getDynamicParameter = <<<'EOF' - switch ($name) { + $container = $this; + $value = match ($name) { %s - default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%%s" must be defined.', $name)); - } + default => throw new ParameterNotFoundException($name), + }; $this->loadedDynamicParameters[$name] = true; return $this->dynamicParameters[$name] = $value; @@ -1586,7 +1679,7 @@ EOF; $getDynamicParameter = sprintf($getDynamicParameter, implode("\n", $dynamicPhp)); } else { $loadedDynamicParameters = '[]'; - $getDynamicParameter = str_repeat(' ', 8).'throw new InvalidArgumentException(sprintf(\'The dynamic parameter "%s" must be defined.\', $name));'; + $getDynamicParameter = str_repeat(' ', 8).'throw new ParameterNotFoundException($name);'; } $code .= <<name); + $value = sprintf('\%s::%s', $value::class, $value->name); } else { $value = $this->export($value); } @@ -1643,51 +1736,39 @@ EOF; private function endClass(): string { - if ($this->addThrow) { - return <<<'EOF' - - protected function throw($message) - { - throw new RuntimeException($message); - } -} - -EOF; - } - return <<<'EOF' } EOF; } - private function wrapServiceConditionals($value, string $code): string + private function wrapServiceConditionals(mixed $value, string $code): string { if (!$condition = $this->getServiceConditionals($value)) { return $code; } // re-indent the wrapped code - $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); + $code = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $code))); return sprintf(" if (%s) {\n%s }\n", $condition, $code); } - private function getServiceConditionals($value): string + private function getServiceConditionals(mixed $value): string { $conditions = []; foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { if (!$this->container->hasDefinition($service)) { return 'false'; } - $conditions[] = sprintf('isset($this->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); + $conditions[] = sprintf('isset($container->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); } foreach (ContainerBuilder::getServiceConditionals($value) as $service) { if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { continue; } - $conditions[] = sprintf('$this->has(%s)', $this->doExport($service)); + $conditions[] = sprintf('$container->has(%s)', $this->doExport($service)); } if (!$conditions) { @@ -1699,9 +1780,7 @@ EOF; private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = [], bool $byConstructor = null): \SplObjectStorage { - if (null === $definitions) { - $definitions = new \SplObjectStorage(); - } + $definitions ??= new \SplObjectStorage(); foreach ($arguments as $argument) { if (\is_array($argument)) { @@ -1739,15 +1818,16 @@ EOF; /** * @throws RuntimeException */ - private function dumpValue($value, bool $interpolate = true): string + private function dumpValue(mixed $value, bool $interpolate = true): string { if (\is_array($value)) { if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) { return $this->dumpValue("%$param%"); } + $isList = array_is_list($value); $code = []; foreach ($value as $k => $v) { - $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); + $code[] = $isList ? $this->dumpValue($v, $interpolate) : sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); } return sprintf('[%s]', implode(', ', $code)); @@ -1765,37 +1845,40 @@ EOF; $returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', str_replace(['|', '&'], ['|\\', '&\\'], $value->getType())); } - $code = sprintf('return %s;', $code); + $attribute = ''; + if ($value instanceof Reference) { + $attribute = 'name: '.$this->dumpValue((string) $value, $interpolate); - return sprintf("function ()%s {\n %s\n }", $returnedType, $code); + if ($this->container->hasDefinition($value) && ($class = $this->container->findDefinition($value)->getClass()) && $class !== (string) $value) { + $attribute .= ', class: '.$this->dumpValue($class, $interpolate); + } + + $attribute = sprintf('#[\Closure(%s)] ', $attribute); + } + + return sprintf('%sfn ()%s => %s', $attribute, $returnedType, $code); } if ($value instanceof IteratorArgument) { - $operands = [0]; - $code = []; - $code[] = 'new RewindableGenerator(function () {'; - if (!$values = $value->getValues()) { - $code[] = ' return new \EmptyIterator();'; - } else { - $countCode = []; - $countCode[] = 'function () {'; - - foreach ($values as $k => $v) { - ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; - $v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate))); - foreach (explode("\n", $v) as $v) { - if ($v) { - $code[] = ' '.$v; - } - } - } - - $countCode[] = sprintf(' return %s;', implode(' + ', $operands)); - $countCode[] = ' }'; + return 'new RewindableGenerator(fn () => new \EmptyIterator(), 0)'; } - $code[] = sprintf(' }, %s)', \count($operands) > 1 ? implode("\n", $countCode) : $operands[0]); + $code = []; + $code[] = 'new RewindableGenerator(function () use ($container) {'; + + $operands = [0]; + foreach ($values as $k => $v) { + ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; + $v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate))); + foreach (explode("\n", $v) as $v) { + if ($v) { + $code[] = ' '.$v; + } + } + } + + $code[] = sprintf(' }, %s)', \count($operands) > 1 ? 'fn () => '.implode(' + ', $operands) : $operands[0]); return implode("\n", $code); } @@ -1804,7 +1887,9 @@ EOF; $serviceMap = ''; $serviceTypes = ''; foreach ($value->getValues() as $k => $v) { - if (!$v) { + if (!$v instanceof Reference) { + $serviceMap .= sprintf("\n %s => [%s],", $this->export($k), $this->dumpValue($v)); + $serviceTypes .= sprintf("\n %s => '?',", $this->export($k)); continue; } $id = (string) $v; @@ -1825,18 +1910,16 @@ EOF; } $this->addGetService = true; - return sprintf('new \%s($this->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); + return sprintf('new \%s($container->getService ??= $container->getService(...), [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); } } finally { [$this->definitionVariables, $this->referenceVariables] = $scope; } } elseif ($value instanceof Definition) { if ($value->hasErrors() && $e = $value->getErrors()) { - $this->addThrow = true; - - return sprintf('$this->throw(%s)', $this->export(reset($e))); + return sprintf('throw new RuntimeException(%s)', $this->export(reset($e))); } - if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { + if ($this->definitionVariables?->contains($value)) { return $this->dumpValue($this->definitionVariables[$value], $interpolate); } if ($value->getMethodCalls()) { @@ -1865,7 +1948,7 @@ EOF; return $this->getServiceCall($id, $value); } elseif ($value instanceof Expression) { - return $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']); + return $this->getExpressionLanguage()->compile((string) $value, ['container' => 'container']); } elseif ($value instanceof Parameter) { return $this->dumpParameter($value); } elseif (true === $interpolate && \is_string($value)) { @@ -1874,20 +1957,18 @@ EOF; // the preg_replace_callback converts them to strings return $this->dumpParameter($match[1]); } else { - $replaceParameters = function ($match) { - return "'.".$this->dumpParameter($match[2]).".'"; - }; + $replaceParameters = fn ($match) => "'.".$this->dumpParameter($match[2]).".'"; $code = str_replace('%%', '%', preg_replace_callback('/(?export($value))); return $code; } } elseif ($value instanceof \UnitEnum) { - return sprintf('\%s::%s', \get_class($value), $value->name); + return sprintf('\%s::%s', $value::class, $value->name); } elseif ($value instanceof AbstractArgument) { throw new RuntimeException($value->getTextWithContext()); } elseif (\is_object($value) || \is_resource($value)) { - throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + throw new RuntimeException(sprintf('Unable to dump a service container if a parameter is an object or a resource, got "%s".', get_debug_type($value))); } return $this->export($value); @@ -1914,20 +1995,18 @@ EOF; private function dumpParameter(string $name): string { - if ($this->container->hasParameter($name)) { - $value = $this->container->getParameter($name); - $dumpedValue = $this->dumpValue($value, false); - - if (!$value || !\is_array($value)) { - return $dumpedValue; - } - - if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) { - return sprintf('$this->parameters[%s]', $this->doExport($name)); - } + if (!$this->container->hasParameter($name) || ($this->dynamicParameters[$name] ?? false)) { + return sprintf('$container->getParameter(%s)', $this->doExport($name)); } - return sprintf('$this->getParameter(%s)', $this->doExport($name)); + $value = $this->container->getParameter($name); + $dumpedValue = $this->dumpValue($value, false); + + if (!$value || !\is_array($value)) { + return $dumpedValue; + } + + return sprintf('$container->parameters[%s]', $this->doExport($name)); } private function getServiceCall(string $id, Reference $reference = null): string @@ -1937,12 +2016,12 @@ EOF; } if ('service_container' === $id) { - return '$this'; + return '$container'; } if ($this->container->hasDefinition($id) && $definition = $this->container->getDefinition($id)) { if ($definition->isSynthetic()) { - $code = sprintf('$this->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : ''); + $code = sprintf('$container->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : ''); } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { $code = 'null'; if (!$definition->isShared()) { @@ -1950,26 +2029,24 @@ EOF; } } elseif ($this->isTrivialInstance($definition)) { if ($definition->hasErrors() && $e = $definition->getErrors()) { - $this->addThrow = true; - - return sprintf('$this->throw(%s)', $this->export(reset($e))); + return sprintf('throw new RuntimeException(%s)', $this->export(reset($e))); } $code = $this->addNewInstance($definition, '', $id); if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { - $code = sprintf('$this->%s[%s] = %s', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); + return sprintf('($container->%s[%s] ??= %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); } $code = "($code)"; } else { - $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$this->load('%s')" : '$this->%s()'; + $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$container->load('%s')" : 'self::%s($container)'; $code = sprintf($code, $this->generateMethodName($id)); if (!$definition->isShared()) { - $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); - $code = sprintf('(isset(%s) ? %1$s() : %s)', $factory, $code); + $factory = sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + $code = sprintf('(isset(%s) ? %1$s($container) : %s)', $factory, $code); } } if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { - $code = sprintf('($this->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); + $code = sprintf('($container->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); } return $code; @@ -1978,18 +2055,18 @@ EOF; return 'null'; } if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) { - $code = sprintf('$this->get(%s, /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $this->doExport($id), ContainerInterface::NULL_ON_INVALID_REFERENCE); + $code = sprintf('$container->get(%s, ContainerInterface::NULL_ON_INVALID_REFERENCE)', $this->doExport($id)); } else { - $code = sprintf('$this->get(%s)', $this->doExport($id)); + $code = sprintf('$container->get(%s)', $this->doExport($id)); } - return sprintf('($this->services[%s] ?? %s)', $this->doExport($id), $code); + return sprintf('($container->services[%s] ?? %s)', $this->doExport($id), $code); } /** * Initializes the method names map to avoid conflicts with the Container methods. */ - private function initializeMethodNamesMap(string $class) + private function initializeMethodNamesMap(string $class): void { $this->serviceIdToMethodNameMap = []; $this->usedMethodNames = []; @@ -2062,9 +2139,9 @@ EOF; private function getExpressionLanguage(): ExpressionLanguage { - if (null === $this->expressionLanguage) { + if (!isset($this->expressionLanguage)) { if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { - throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } $providers = $this->container->getExpressionLanguageProviders(); $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { @@ -2074,7 +2151,7 @@ EOF; return $this->getServiceCall($id); } - return sprintf('$this->get(%s)', $arg); + return sprintf('$container->get(%s)', $arg); }); if ($this->container->isTrackingResources()) { @@ -2111,10 +2188,7 @@ EOF; return 1 === \count($ids); } - /** - * @return mixed - */ - private function export($value) + private function export(mixed $value): mixed { if (null !== $this->targetDirRegex && \is_string($value) && preg_match($this->targetDirRegex, $value, $matches, \PREG_OFFSET_CAPTURE)) { $suffix = $matches[0][1] + \strlen($matches[0][0]); @@ -2129,13 +2203,13 @@ EOF; $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; } - $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__'; + $dirname = $this->asFiles ? '$container->containerDir' : '__DIR__'; $offset = 2 + $this->targetDirMaxMatches - \count($matches); if (0 < $offset) { $dirname = sprintf('\dirname(__DIR__, %d)', $offset + (int) $this->asFiles); } elseif ($this->asFiles) { - $dirname = "\$this->targetDir.''"; // empty string concatenation on purpose + $dirname = "\$container->targetDir.''"; // empty string concatenation on purpose } if ($prefix || $suffix) { @@ -2148,10 +2222,7 @@ EOF; return $this->doExport($value, true); } - /** - * @return mixed - */ - private function doExport($value, bool $resolveEnv = false) + private function doExport(mixed $value, bool $resolveEnv = false): mixed { $shouldCacheValue = $resolveEnv && \is_string($value); if ($shouldCacheValue && isset($this->exportedVariables[$value])) { @@ -2159,26 +2230,18 @@ EOF; } if (\is_string($value) && str_contains($value, "\n")) { $cleanParts = explode("\n", $value); - $cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts); + $cleanParts = array_map(fn ($part) => var_export($part, true), $cleanParts); $export = implode('."\n".', $cleanParts); } else { $export = var_export($value, true); } - if ($this->asFiles) { - if (false !== strpos($export, '$this')) { - $export = str_replace('$this', "$'.'this", $export); - } - if (false !== strpos($export, 'function () {')) { - $export = str_replace('function () {', "function ('.') {", $export); - } - } - if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) { + if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$container->getEnv('string:%s').'")) { $export = $resolvedExport; if (str_ends_with($export, ".''")) { $export = substr($export, 0, -3); if ("'" === $export[1]) { - $export = substr_replace($export, '', 18, 7); + $export = substr_replace($export, '', 23, 7); } } if ("'" === $export[1]) { @@ -2202,7 +2265,7 @@ EOF; continue; } - if ($autoloader[0] instanceof DebugClassLoader || $autoloader[0] instanceof LegacyDebugClassLoader) { + if ($autoloader[0] instanceof DebugClassLoader) { $autoloader = $autoloader[0]->getClassLoader(); } @@ -2227,6 +2290,7 @@ EOF; private function getClasses(Definition $definition, string $id): array { $classes = []; + $resolve = $this->container->getParameterBag()->resolveValue(...); while ($definition instanceof Definition) { foreach ($definition->getTag($this->preloadTags[0]) as $tag) { @@ -2238,7 +2302,7 @@ EOF; } if ($class = $definition->getClass()) { - $classes[] = trim($class, '\\'); + $classes[] = trim($resolve($class), '\\'); } $factory = $definition->getFactory(); @@ -2247,6 +2311,8 @@ EOF; } if (\is_string($factory[0])) { + $factory[0] = $resolve($factory[0]); + if (false !== $i = strrpos($factory[0], '::')) { $factory[0] = substr($factory[0], 0, $i); } @@ -2258,4 +2324,80 @@ EOF; return $classes; } + + private function isProxyCandidate(Definition $definition, ?bool &$asGhostObject, string $id): ?Definition + { + $asGhostObject = false; + + if (['Closure', 'fromCallable'] === $definition->getFactory()) { + return null; + } + + if (!$definition->isLazy() || !$this->hasProxyDumper) { + return null; + } + + return $this->getProxyDumper()->isProxyCandidate($definition, $asGhostObject, $id) ? $definition : null; + } + + /** + * Removes comments from a PHP source string. + * + * We don't use the PHP php_strip_whitespace() function + * as we want the content to be readable and well-formatted. + */ + private static function stripComments(string $source): string + { + if (!\function_exists('token_get_all')) { + return $source; + } + + $rawChunk = ''; + $output = ''; + $tokens = token_get_all($source); + $ignoreSpace = false; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1]) || 'b"' === $token) { + $rawChunk .= $token; + } elseif (\T_START_HEREDOC === $token[0]) { + $output .= $rawChunk.$token[1]; + do { + $token = $tokens[++$i]; + $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; + } while (\T_END_HEREDOC !== $token[0]); + $rawChunk = ''; + } elseif (\T_WHITESPACE === $token[0]) { + if ($ignoreSpace) { + $ignoreSpace = false; + + continue; + } + + // replace multiple new lines with a single newline + $rawChunk .= preg_replace(['/\n{2,}/S'], "\n", $token[1]); + } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { + if (!\in_array($rawChunk[\strlen($rawChunk) - 1], [' ', "\n", "\r", "\t"], true)) { + $rawChunk .= ' '; + } + $ignoreSpace = true; + } else { + $rawChunk .= $token[1]; + + // The PHP-open tag already has a new-line + if (\T_OPEN_TAG === $token[0]) { + $ignoreSpace = true; + } else { + $ignoreSpace = false; + } + } + } + + $output .= $rawChunk; + + unset($tokens, $rawChunk); + gc_mem_caches(); + + return $output; + } } diff --git a/lib/symfony/dependency-injection/Dumper/Preloader.php b/lib/symfony/dependency-injection/Dumper/Preloader.php index c61b08ebc..8caa1de48 100644 --- a/lib/symfony/dependency-injection/Dumper/Preloader.php +++ b/lib/symfony/dependency-injection/Dumper/Preloader.php @@ -26,7 +26,7 @@ final class Preloader $classes = []; foreach ($list as $item) { - if (0 === strpos($item, $cacheDir)) { + if (str_starts_with($item, $cacheDir)) { file_put_contents($file, sprintf("require_once __DIR__.%s;\n", var_export(strtr(substr($item, \strlen($cacheDir)), \DIRECTORY_SEPARATOR, '/'), true)), \FILE_APPEND); continue; } @@ -90,10 +90,8 @@ final class Preloader $r->getConstants(); $r->getDefaultProperties(); - if (\PHP_VERSION_ID >= 70400) { - foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) { - self::preloadType($p->getType(), $preloaded); - } + foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) { + self::preloadType($p->getType(), $preloaded); } foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) { @@ -111,7 +109,7 @@ final class Preloader self::preloadType($m->getReturnType(), $preloaded); } - } catch (\Throwable $e) { + } catch (\Throwable) { // ignore missing classes } } diff --git a/lib/symfony/dependency-injection/Dumper/XmlDumper.php b/lib/symfony/dependency-injection/Dumper/XmlDumper.php index 4f7b16d5f..05d424d15 100644 --- a/lib/symfony/dependency-injection/Dumper/XmlDumper.php +++ b/lib/symfony/dependency-injection/Dumper/XmlDumper.php @@ -32,17 +32,12 @@ use Symfony\Component\ExpressionLanguage\Expression; */ class XmlDumper extends Dumper { - /** - * @var \DOMDocument - */ - private $document; + private \DOMDocument $document; /** * Dumps the service container as an XML string. - * - * @return string */ - public function dump(array $options = []) + public function dump(array $options = []): string { $this->document = new \DOMDocument('1.0', 'utf-8'); $this->document->formatOutput = true; @@ -56,12 +51,12 @@ class XmlDumper extends Dumper $this->document->appendChild($container); $xml = $this->document->saveXML(); - $this->document = null; + unset($this->document); return $this->container->resolveEnvPlaceholders($xml); } - private function addParameters(\DOMElement $parent) + private function addParameters(\DOMElement $parent): void { $data = $this->container->getParameterBag()->all(); if (!$data) { @@ -77,7 +72,7 @@ class XmlDumper extends Dumper $this->convertParameters($data, 'parameter', $parameters); } - private function addMethodCalls(array $methodcalls, \DOMElement $parent) + private function addMethodCalls(array $methodcalls, \DOMElement $parent): void { foreach ($methodcalls as $methodcall) { $call = $this->document->createElement('call'); @@ -92,14 +87,14 @@ class XmlDumper extends Dumper } } - private function addService(Definition $definition, ?string $id, \DOMElement $parent) + private function addService(Definition $definition, ?string $id, \DOMElement $parent): void { $service = $this->document->createElement('service'); if (null !== $id) { $service->setAttribute('id', $id); } if ($class = $definition->getClass()) { - if ('\\' === substr($class, 0, 1)) { + if (str_starts_with($class, '\\')) { $class = substr($class, 1); } @@ -134,7 +129,9 @@ class XmlDumper extends Dumper } } - foreach ($definition->getTags() as $name => $tags) { + $tags = $definition->getTags(); + $tags['container.error'] = array_map(fn ($e) => ['message' => $e], $definition->getErrors()); + foreach ($tags as $name => $tags) { foreach ($tags as $attributes) { $tag = $this->document->createElement('tag'); if (!\array_key_exists('name', $attributes)) { @@ -142,8 +139,14 @@ class XmlDumper extends Dumper } else { $tag->appendChild($this->document->createTextNode($name)); } - foreach ($attributes as $key => $value) { - $tag->setAttribute($key, $value ?? ''); + + // Check if we have recursive attributes + if (array_filter($attributes, \is_array(...))) { + $this->addTagRecursiveAttributes($tag, $attributes); + } else { + foreach ($attributes as $key => $value) { + $tag->setAttribute($key, $value ?? ''); + } } $service->appendChild($tag); } @@ -166,20 +169,24 @@ class XmlDumper extends Dumper $this->addMethodCalls($definition->getMethodCalls(), $service); if ($callable = $definition->getFactory()) { - $factory = $this->document->createElement('factory'); - - if (\is_array($callable) && $callable[0] instanceof Definition) { - $this->addService($callable[0], null, $factory); - $factory->setAttribute('method', $callable[1]); - } elseif (\is_array($callable)) { - if (null !== $callable[0]) { - $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); - } - $factory->setAttribute('method', $callable[1]); + if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) { + $service->setAttribute('constructor', $callable[1]); } else { - $factory->setAttribute('function', $callable); + $factory = $this->document->createElement('factory'); + + if (\is_array($callable) && $callable[0] instanceof Definition) { + $this->addService($callable[0], null, $factory); + $factory->setAttribute('method', $callable[1]); + } elseif (\is_array($callable)) { + if (null !== $callable[0]) { + $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); + } + $factory->setAttribute('method', $callable[1]); + } else { + $factory->setAttribute('function', $callable); + } + $service->appendChild($factory); } - $service->appendChild($factory); } if ($definition->isDeprecated()) { @@ -222,7 +229,7 @@ class XmlDumper extends Dumper $parent->appendChild($service); } - private function addServiceAlias(string $alias, Alias $id, \DOMElement $parent) + private function addServiceAlias(string $alias, Alias $id, \DOMElement $parent): void { $service = $this->document->createElement('service'); $service->setAttribute('id', $alias); @@ -244,7 +251,7 @@ class XmlDumper extends Dumper $parent->appendChild($service); } - private function addServices(\DOMElement $parent) + private function addServices(\DOMElement $parent): void { $definitions = $this->container->getDefinitions(); if (!$definitions) { @@ -266,7 +273,23 @@ class XmlDumper extends Dumper $parent->appendChild($services); } - private function convertParameters(array $parameters, string $type, \DOMElement $parent, string $keyAttribute = 'key') + private function addTagRecursiveAttributes(\DOMElement $parent, array $attributes): void + { + foreach ($attributes as $name => $value) { + $attribute = $this->document->createElement('attribute'); + $attribute->setAttribute('name', $name); + + if (\is_array($value)) { + $this->addTagRecursiveAttributes($attribute, $value); + } else { + $attribute->appendChild($this->document->createTextNode($value)); + } + + $parent->appendChild($attribute); + } + } + + private function convertParameters(array $parameters, string $type, \DOMElement $parent, string $keyAttribute = 'key'): void { $withKeys = !array_is_list($parameters); foreach ($parameters as $key => $value) { @@ -292,12 +315,27 @@ class XmlDumper extends Dumper $element->setAttribute('default-priority-method', $tag->getDefaultPriorityMethod()); } } + if ($excludes = $tag->getExclude()) { + if (1 === \count($excludes)) { + $element->setAttribute('exclude', $excludes[0]); + } else { + foreach ($excludes as $exclude) { + $element->appendChild($this->document->createElement('exclude', $exclude)); + } + } + } + if (!$tag->excludeSelf()) { + $element->setAttribute('exclude-self', 'false'); + } } elseif ($value instanceof IteratorArgument) { $element->setAttribute('type', 'iterator'); $this->convertParameters($value->getValues(), $type, $element, 'key'); } elseif ($value instanceof ServiceLocatorArgument) { $element->setAttribute('type', 'service_locator'); $this->convertParameters($value->getValues(), $type, $element, 'key'); + } elseif ($value instanceof ServiceClosureArgument && !$value->getValues()[0] instanceof Reference) { + $element->setAttribute('type', 'service_closure'); + $this->convertParameters($value->getValues(), $type, $element, 'key'); } elseif ($value instanceof Reference || $value instanceof ServiceClosureArgument) { $element->setAttribute('type', 'service'); if ($value instanceof ServiceClosureArgument) { @@ -320,7 +358,7 @@ class XmlDumper extends Dumper $element->setAttribute('type', 'expression'); $text = $this->document->createTextNode(self::phpToXml((string) $value)); $element->appendChild($text); - } elseif (\is_string($value) && !preg_match('/^[^\x00-\x08\x0B\x0E-\x1A\x1C-\x1F\x7F]*+$/u', $value)) { + } elseif (\is_string($value) && !preg_match('/^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*+$/u', $value)) { $element->setAttribute('type', 'binary'); $text = $this->document->createTextNode(self::phpToXml(base64_encode($value))); $element->appendChild($text); @@ -369,11 +407,9 @@ class XmlDumper extends Dumper /** * Converts php types to xml types. * - * @param mixed $value Value to convert - * * @throws RuntimeException When trying to dump object or resource */ - public static function phpToXml($value): string + public static function phpToXml(mixed $value): string { switch (true) { case null === $value: @@ -385,9 +421,9 @@ class XmlDumper extends Dumper case $value instanceof Parameter: return '%'.$value.'%'; case $value instanceof \UnitEnum: - return sprintf('%s::%s', \get_class($value), $value->name); + return sprintf('%s::%s', $value::class, $value->name); case \is_object($value) || \is_resource($value): - throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + throw new RuntimeException(sprintf('Unable to dump a service container if a parameter is an object or a resource, got "%s".', get_debug_type($value))); default: return (string) $value; } diff --git a/lib/symfony/dependency-injection/Dumper/YamlDumper.php b/lib/symfony/dependency-injection/Dumper/YamlDumper.php index 823eb97b0..5c96e3b32 100644 --- a/lib/symfony/dependency-injection/Dumper/YamlDumper.php +++ b/lib/symfony/dependency-injection/Dumper/YamlDumper.php @@ -37,22 +37,18 @@ use Symfony\Component\Yaml\Yaml; */ class YamlDumper extends Dumper { - private $dumper; + private YmlDumper $dumper; /** * Dumps the service container as an YAML string. - * - * @return string */ - public function dump(array $options = []) + public function dump(array $options = []): string { - if (!class_exists(\Symfony\Component\Yaml\Dumper::class)) { - throw new LogicException('Unable to dump the container as the Symfony Yaml Component is not installed.'); + if (!class_exists(YmlDumper::class)) { + throw new LogicException('Unable to dump the container as the Symfony Yaml Component is not installed. Try running "composer require symfony/yaml".'); } - if (null === $this->dumper) { - $this->dumper = new YmlDumper(); - } + $this->dumper ??= new YmlDumper(); return $this->container->resolveEnvPlaceholders($this->addParameters()."\n".$this->addServices()); } @@ -61,7 +57,7 @@ class YamlDumper extends Dumper { $code = " $id:\n"; if ($class = $definition->getClass()) { - if ('\\' === substr($class, 0, 1)) { + if (str_starts_with($class, '\\')) { $class = substr($class, 1); } @@ -73,7 +69,9 @@ class YamlDumper extends Dumper } $tagsCode = ''; - foreach ($definition->getTags() as $name => $tags) { + $tags = $definition->getTags(); + $tags['container.error'] = array_map(fn ($e) => ['message' => $e], $definition->getErrors()); + foreach ($tags as $name => $tags) { foreach ($tags as $attributes) { $att = []; foreach ($attributes as $key => $value) { @@ -155,7 +153,11 @@ class YamlDumper extends Dumper } if ($callable = $definition->getFactory()) { - $code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) { + $code .= sprintf(" constructor: %s\n", $callable[1]); + } else { + $code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + } } if ($callable = $definition->getConfigurator()) { @@ -225,12 +227,8 @@ class YamlDumper extends Dumper /** * Dumps callable to YAML format. - * - * @param mixed $callable - * - * @return mixed */ - private function dumpCallable($callable) + private function dumpCallable(mixed $callable): mixed { if (\is_array($callable)) { if ($callable[0] instanceof Reference) { @@ -246,16 +244,14 @@ class YamlDumper extends Dumper /** * Dumps the value to YAML format. * - * @return mixed - * * @throws RuntimeException When trying to dump object or resource */ - private function dumpValue($value) + private function dumpValue(mixed $value): mixed { if ($value instanceof ServiceClosureArgument) { $value = $value->getValues()[0]; - return new TaggedValue('service_closure', $this->getServiceCall((string) $value, $value)); + return new TaggedValue('service_closure', $this->dumpValue($value)); } if ($value instanceof ArgumentInterface) { $tag = $value; @@ -276,6 +272,15 @@ class YamlDumper extends Dumper $content['default_priority_method'] = $tag->getDefaultPriorityMethod(); } } + if ($excludes = $tag->getExclude()) { + if (!\is_array($content)) { + $content = ['tag' => $content]; + } + $content['exclude'] = 1 === \count($excludes) ? $excludes[0] : $excludes; + } + if (!$tag->excludeSelf()) { + $content['exclude_self'] = false; + } return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator', $content); } @@ -307,11 +312,11 @@ class YamlDumper extends Dumper } elseif ($value instanceof Definition) { return new TaggedValue('service', (new Parser())->parse("_:\n".$this->addService('_', $value), Yaml::PARSE_CUSTOM_TAGS)['_']['_']); } elseif ($value instanceof \UnitEnum) { - return new TaggedValue('php/const', sprintf('%s::%s', \get_class($value), $value->name)); + return new TaggedValue('php/const', sprintf('%s::%s', $value::class, $value->name)); } elseif ($value instanceof AbstractArgument) { return new TaggedValue('abstract', $value->getText()); } elseif (\is_object($value) || \is_resource($value)) { - throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + throw new RuntimeException(sprintf('Unable to dump a service container if a parameter is an object or a resource, got "%s".', get_debug_type($value))); } return $value; diff --git a/lib/symfony/dependency-injection/EnvVarProcessor.php b/lib/symfony/dependency-injection/EnvVarProcessor.php index feb51fff3..bae5e289d 100644 --- a/lib/symfony/dependency-injection/EnvVarProcessor.php +++ b/lib/symfony/dependency-injection/EnvVarProcessor.php @@ -20,12 +20,13 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ class EnvVarProcessor implements EnvVarProcessorInterface { - private $container; - private $loaders; - private $loadedVars = []; + private ContainerInterface $container; + /** @var \Traversable */ + private \Traversable $loaders; + private array $loadedVars = []; /** - * @param EnvVarLoaderInterface[] $loaders + * @param \Traversable|null $loaders */ public function __construct(ContainerInterface $container, \Traversable $loaders = null) { @@ -33,10 +34,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface $this->loaders = $loaders ?? new \ArrayIterator(); } - /** - * {@inheritdoc} - */ - public static function getProvidedTypes() + public static function getProvidedTypes(): array { return [ 'base64' => 'string', @@ -56,13 +54,13 @@ class EnvVarProcessor implements EnvVarProcessorInterface 'string' => 'string', 'trim' => 'string', 'require' => 'bool|int|float|string|array', + 'enum' => \BackedEnum::class, + 'shuffle' => 'array', + 'defined' => 'bool', ]; } - /** - * {@inheritdoc} - */ - public function getEnv(string $prefix, string $name, \Closure $getEnv) + public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed { $i = strpos($name, ':'); @@ -86,6 +84,34 @@ class EnvVarProcessor implements EnvVarProcessorInterface return $array[$key]; } + if ('enum' === $prefix) { + if (false === $i) { + throw new RuntimeException(sprintf('Invalid env "enum:%s": a "%s" class-string should be provided.', $name, \BackedEnum::class)); + } + + $next = substr($name, $i + 1); + $backedEnumClassName = substr($name, 0, $i); + $backedEnumValue = $getEnv($next); + + if (!\is_string($backedEnumValue) && !\is_int($backedEnumValue)) { + throw new RuntimeException(sprintf('Resolved value of "%s" did not result in a string or int value.', $next)); + } + + if (!is_subclass_of($backedEnumClassName, \BackedEnum::class)) { + throw new RuntimeException(sprintf('"%s" is not a "%s".', $backedEnumClassName, \BackedEnum::class)); + } + + return $backedEnumClassName::tryFrom($backedEnumValue) ?? throw new RuntimeException(sprintf('Enum value "%s" is not backed by "%s".', $backedEnumValue, $backedEnumClassName)); + } + + if ('defined' === $prefix) { + try { + return '' !== ($getEnv($name) ?? ''); + } catch (EnvNotFoundException) { + return false; + } + } + if ('default' === $prefix) { if (false === $i) { throw new RuntimeException(sprintf('Invalid env "default:%s": a fallback parameter should be provided.', $name)); @@ -104,7 +130,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface if ('' !== $env && null !== $env) { return $env; } - } catch (EnvNotFoundException $e) { + } catch (EnvNotFoundException) { // no-op } @@ -126,20 +152,27 @@ class EnvVarProcessor implements EnvVarProcessorInterface } } + $returnNull = false; + if ('' === $prefix) { + if ('' === $name) { + return null; + } + $returnNull = true; + $prefix = 'string'; + } + if (false !== $i || 'string' !== $prefix) { $env = $getEnv($name); - } elseif (isset($_ENV[$name])) { - $env = $_ENV[$name]; - } elseif (isset($_SERVER[$name]) && !str_starts_with($name, 'HTTP_')) { - $env = $_SERVER[$name]; - } elseif (false === ($env = getenv($name)) || null === $env) { // null is a possible value because of thread safety issues + } elseif ('' === ($env = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null))) + || (false !== $env && false === ($env = $env ?? getenv($name) ?? false)) // null is a possible value because of thread safety issues + ) { foreach ($this->loadedVars as $vars) { - if (false !== $env = ($vars[$name] ?? false)) { + if (false !== ($env = ($vars[$name] ?? $env)) && '' !== $env) { break; } } - if (false === $env || null === $env) { + if (false === $env || '' === $env) { $loaders = $this->loaders; $this->loaders = new \ArrayIterator(); @@ -152,7 +185,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface continue; } $this->loadedVars[] = $vars = $loader->loadEnvVars(); - if (false !== $env = $vars[$name] ?? false) { + if (false !== ($env = ($vars[$name] ?? $env)) && '' !== $env) { $ended = false; break; } @@ -160,14 +193,14 @@ class EnvVarProcessor implements EnvVarProcessorInterface if ($ended || $count === $i) { $loaders = $this->loaders; } - } catch (ParameterCircularReferenceException $e) { + } catch (ParameterCircularReferenceException) { // skip loaders that need an env var that is not defined } finally { $this->loaders = $loaders; } } - if (false === $env || null === $env) { + if (false === $env) { if (!$this->container->hasParameter("env($name)")) { throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name)); } @@ -177,14 +210,26 @@ class EnvVarProcessor implements EnvVarProcessorInterface } if (null === $env) { + if ($returnNull) { + return null; + } + if (!isset($this->getProvidedTypes()[$prefix])) { throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix)); } - return null; + if (!\in_array($prefix, ['string', 'bool', 'not', 'int', 'float'], true)) { + return null; + } } - if (!\is_scalar($env)) { + if ('shuffle' === $prefix) { + \is_array($env) ? shuffle($env) : throw new RuntimeException(sprintf('Env var "%s" cannot be shuffled, expected array, got "%s".', $name, get_debug_type($env))); + + return $env; + } + + if (null !== $env && !\is_scalar($env)) { throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix)); } @@ -193,13 +238,13 @@ class EnvVarProcessor implements EnvVarProcessorInterface } if (\in_array($prefix, ['bool', 'not'], true)) { - $env = (bool) (filter_var($env, \FILTER_VALIDATE_BOOLEAN) ?: filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)); + $env = (bool) (filter_var($env, \FILTER_VALIDATE_BOOL) ?: filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)); - return 'not' === $prefix ? !$env : $env; + return 'not' === $prefix xor $env; } if ('int' === $prefix) { - if (false === $env = filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)) { + if (null !== $env && false === $env = filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)) { throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to int.', $name)); } @@ -207,7 +252,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface } if ('float' === $prefix) { - if (false === $env = filter_var($env, \FILTER_VALIDATE_FLOAT)) { + if (null !== $env && false === $env = filter_var($env, \FILTER_VALIDATE_FLOAT)) { throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to float.', $name)); } @@ -292,7 +337,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface } if ('csv' === $prefix) { - return str_getcsv($env, ',', '"', \PHP_VERSION_ID >= 70400 ? '' : '\\'); + return '' === $env ? [] : str_getcsv($env, ',', '"', ''); } if ('trim' === $prefix) { diff --git a/lib/symfony/dependency-injection/EnvVarProcessorInterface.php b/lib/symfony/dependency-injection/EnvVarProcessorInterface.php index d3275fe29..fecd47407 100644 --- a/lib/symfony/dependency-injection/EnvVarProcessorInterface.php +++ b/lib/symfony/dependency-injection/EnvVarProcessorInterface.php @@ -23,18 +23,17 @@ interface EnvVarProcessorInterface /** * Returns the value of the given variable as managed by the current instance. * - * @param string $prefix The namespace of the variable - * @param string $name The name of the variable within the namespace - * @param \Closure $getEnv A closure that allows fetching more env vars - * - * @return mixed + * @param string $prefix The namespace of the variable + * @param string $prefix The namespace of the variable; when the empty string is passed, null values should be kept as is + * @param string $name The name of the variable within the namespace + * @param \Closure(string): mixed $getEnv A closure that allows fetching more env vars * * @throws RuntimeException on error */ - public function getEnv(string $prefix, string $name, \Closure $getEnv); + public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed; /** - * @return string[] The PHP-types managed by getEnv(), keyed by prefixes + * @return array The PHP-types managed by getEnv(), keyed by prefixes */ - public static function getProvidedTypes(); + public static function getProvidedTypes(): array; } diff --git a/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php b/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php index 0006f5621..5f22fa53b 100644 --- a/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php +++ b/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php @@ -16,10 +16,10 @@ namespace Symfony\Component\DependencyInjection\Exception; */ class AutowiringFailedException extends RuntimeException { - private $serviceId; - private $messageCallback; + private string $serviceId; + private ?\Closure $messageCallback = null; - public function __construct(string $serviceId, $message = '', int $code = 0, \Throwable $previous = null) + public function __construct(string $serviceId, string|\Closure $message = '', int $code = 0, \Throwable $previous = null) { $this->serviceId = $serviceId; @@ -39,8 +39,8 @@ class AutowiringFailedException extends RuntimeException parent::__construct('', $code, $previous); $this->message = new class($this->message, $this->messageCallback) { - private $message; - private $messageCallback; + private string|self $message; + private ?\Closure $messageCallback; public function __construct(&$message, &$messageCallback) { @@ -67,6 +67,9 @@ class AutowiringFailedException extends RuntimeException return $this->messageCallback; } + /** + * @return string + */ public function getServiceId() { return $this->serviceId; diff --git a/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php b/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php index 2450ccb5c..9fc3b50b6 100644 --- a/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php +++ b/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php @@ -18,7 +18,7 @@ namespace Symfony\Component\DependencyInjection\Exception; */ class ParameterCircularReferenceException extends RuntimeException { - private $parameters; + private array $parameters; public function __construct(array $parameters, \Throwable $previous = null) { @@ -27,6 +27,9 @@ class ParameterCircularReferenceException extends RuntimeException $this->parameters = $parameters; } + /** + * @return array + */ public function getParameters() { return $this->parameters; diff --git a/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php b/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php index 5d3831014..69f7b3a50 100644 --- a/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php +++ b/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php @@ -20,11 +20,11 @@ use Psr\Container\NotFoundExceptionInterface; */ class ParameterNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface { - private $key; - private $sourceId; - private $sourceKey; - private $alternatives; - private $nonNestedAlternative; + private string $key; + private ?string $sourceId; + private ?string $sourceKey; + private array $alternatives; + private ?string $nonNestedAlternative; /** * @param string $key The requested parameter key @@ -47,12 +47,17 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not $this->updateRepr(); } + /** + * @return void + */ public function updateRepr() { if (null !== $this->sourceId) { $this->message = sprintf('The service "%s" has a dependency on a non-existent parameter "%s".', $this->sourceId, $this->key); } elseif (null !== $this->sourceKey) { $this->message = sprintf('The parameter "%s" has a dependency on a non-existent parameter "%s".', $this->sourceKey, $this->key); + } elseif ('.' === ($this->key[0] ?? '')) { + $this->message = sprintf('Parameter "%s" not found. It was probably deleted during the compilation of the container.', $this->key); } else { $this->message = sprintf('You have requested a non-existent parameter "%s".', $this->key); } @@ -69,21 +74,33 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not } } + /** + * @return string + */ public function getKey() { return $this->key; } + /** + * @return string|null + */ public function getSourceId() { return $this->sourceId; } + /** + * @return string|null + */ public function getSourceKey() { return $this->sourceKey; } + /** + * @return void + */ public function setSourceId(?string $sourceId) { $this->sourceId = $sourceId; @@ -91,6 +108,9 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not $this->updateRepr(); } + /** + * @return void + */ public function setSourceKey(?string $sourceKey) { $this->sourceKey = $sourceKey; diff --git a/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php b/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php index a38671bcf..d62c22567 100644 --- a/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php +++ b/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php @@ -18,8 +18,8 @@ namespace Symfony\Component\DependencyInjection\Exception; */ class ServiceCircularReferenceException extends RuntimeException { - private $serviceId; - private $path; + private string $serviceId; + private array $path; public function __construct(string $serviceId, array $path, \Throwable $previous = null) { @@ -29,11 +29,17 @@ class ServiceCircularReferenceException extends RuntimeException $this->path = $path; } + /** + * @return string + */ public function getServiceId() { return $this->serviceId; } + /** + * @return array + */ public function getPath() { return $this->path; diff --git a/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php b/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php index f91afae39..d56db7727 100644 --- a/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php +++ b/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php @@ -20,9 +20,9 @@ use Psr\Container\NotFoundExceptionInterface; */ class ServiceNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface { - private $id; - private $sourceId; - private $alternatives; + private string $id; + private ?string $sourceId; + private array $alternatives; public function __construct(string $id, string $sourceId = null, \Throwable $previous = null, array $alternatives = [], string $msg = null) { @@ -50,16 +50,25 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo $this->alternatives = $alternatives; } + /** + * @return string + */ public function getId() { return $this->id; } + /** + * @return string|null + */ public function getSourceId() { return $this->sourceId; } + /** + * @return array + */ public function getAlternatives() { return $this->alternatives; diff --git a/lib/symfony/dependency-injection/ExpressionLanguage.php b/lib/symfony/dependency-injection/ExpressionLanguage.php index 961c737e8..1a7f5fd38 100644 --- a/lib/symfony/dependency-injection/ExpressionLanguage.php +++ b/lib/symfony/dependency-injection/ExpressionLanguage.php @@ -27,13 +27,10 @@ if (!class_exists(BaseExpressionLanguage::class)) { */ class ExpressionLanguage extends BaseExpressionLanguage { - /** - * {@inheritdoc} - */ - public function __construct(CacheItemPoolInterface $cache = null, array $providers = [], callable $serviceCompiler = null) + public function __construct(CacheItemPoolInterface $cache = null, array $providers = [], callable $serviceCompiler = null, \Closure $getEnv = null) { // prepend the default provider to let users override it easily - array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler)); + array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler, $getEnv)); parent::__construct($cache, $providers); } diff --git a/lib/symfony/dependency-injection/ExpressionLanguageProvider.php b/lib/symfony/dependency-injection/ExpressionLanguageProvider.php index 9198ca0a4..d0cc1f70b 100644 --- a/lib/symfony/dependency-injection/ExpressionLanguageProvider.php +++ b/lib/symfony/dependency-injection/ExpressionLanguageProvider.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\ExpressionLanguage\ExpressionFunction; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -19,32 +20,38 @@ use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; * * To get a service, use service('request'). * To get a parameter, use parameter('kernel.debug'). + * To get an env variable, use env('SOME_VARIABLE'). * * @author Fabien Potencier */ class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface { - private $serviceCompiler; + private ?\Closure $serviceCompiler; - public function __construct(callable $serviceCompiler = null) + private ?\Closure $getEnv; + + public function __construct(callable $serviceCompiler = null, \Closure $getEnv = null) { - $this->serviceCompiler = $serviceCompiler; + $this->serviceCompiler = null === $serviceCompiler ? null : $serviceCompiler(...); + $this->getEnv = $getEnv; } - public function getFunctions() + public function getFunctions(): array { return [ - new ExpressionFunction('service', $this->serviceCompiler ?: function ($arg) { - return sprintf('$this->get(%s)', $arg); - }, function (array $variables, $value) { - return $variables['container']->get($value); + new ExpressionFunction('service', $this->serviceCompiler ?? fn ($arg) => sprintf('$container->get(%s)', $arg), fn (array $variables, $value) => $variables['container']->get($value)), + + new ExpressionFunction('parameter', fn ($arg) => sprintf('$container->getParameter(%s)', $arg), fn (array $variables, $value) => $variables['container']->getParameter($value)), + + new ExpressionFunction('env', fn ($arg) => sprintf('$container->getEnv(%s)', $arg), function (array $variables, $value) { + if (!$this->getEnv) { + throw new LogicException('You need to pass a getEnv closure to the expression langage provider to use the "env" function.'); + } + + return ($this->getEnv)($value); }), - new ExpressionFunction('parameter', function ($arg) { - return sprintf('$this->getParameter(%s)', $arg); - }, function (array $variables, $value) { - return $variables['container']->getParameter($value); - }), + new ExpressionFunction('arg', fn ($arg) => sprintf('$args?->get(%s)', $arg), fn (array $variables, $value) => $variables['args']?->get($value)), ]; } } diff --git a/lib/symfony/dependency-injection/Extension/AbstractExtension.php b/lib/symfony/dependency-injection/Extension/AbstractExtension.php new file mode 100644 index 000000000..c5c2f17ad --- /dev/null +++ b/lib/symfony/dependency-injection/Extension/AbstractExtension.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\Configuration; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * An Extension that provides configuration hooks. + * + * @author Yonel Ceruto + */ +abstract class AbstractExtension extends Extension implements ConfigurableExtensionInterface, PrependExtensionInterface +{ + use ExtensionTrait; + + public function configure(DefinitionConfigurator $definition): void + { + } + + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + { + return new Configuration($this, $container, $this->getAlias()); + } + + final public function prepend(ContainerBuilder $container): void + { + $callback = function (ContainerConfigurator $configurator) use ($container) { + $this->prependExtension($configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this); + } + + final public function load(array $configs, ContainerBuilder $container): void + { + $config = $this->processConfiguration($this->getConfiguration([], $container), $configs); + + $callback = function (ContainerConfigurator $configurator) use ($config, $container) { + $this->loadExtension($config, $configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this); + } +} diff --git a/lib/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php b/lib/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php new file mode 100644 index 000000000..b8927e427 --- /dev/null +++ b/lib/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\ConfigurableInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * @author Yonel Ceruto + */ +interface ConfigurableExtensionInterface extends ConfigurableInterface +{ + /** + * Allows an extension to prepend the extension configurations. + */ + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void; + + /** + * Loads a specific configuration. + */ + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void; +} diff --git a/lib/symfony/dependency-injection/Extension/Extension.php b/lib/symfony/dependency-injection/Extension/Extension.php index ef6c1aaa3..d0bd05ea4 100644 --- a/lib/symfony/dependency-injection/Extension/Extension.php +++ b/lib/symfony/dependency-injection/Extension/Extension.php @@ -26,10 +26,10 @@ use Symfony\Component\DependencyInjection\Exception\LogicException; */ abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface { - private $processedConfigs = []; + private array $processedConfigs = []; /** - * {@inheritdoc} + * @return string|false */ public function getXsdValidationBasePath() { @@ -37,7 +37,7 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn } /** - * {@inheritdoc} + * @return string */ public function getNamespace() { @@ -60,11 +60,9 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn * * This can be overridden in a sub-class to specify the alias manually. * - * @return string - * * @throws BadMethodCallException When the extension name does not follow conventions */ - public function getAlias() + public function getAlias(): string { $className = static::class; if (!str_ends_with($className, 'Extension')) { @@ -76,7 +74,7 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn } /** - * {@inheritdoc} + * @return ConfigurationInterface|null */ public function getConfiguration(array $config, ContainerBuilder $container) { @@ -124,11 +122,9 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn } /** - * @return bool - * * @throws InvalidArgumentException When the config is not enableable */ - protected function isConfigEnabled(ContainerBuilder $container, array $config) + protected function isConfigEnabled(ContainerBuilder $container, array $config): bool { if (!\array_key_exists('enabled', $config)) { throw new InvalidArgumentException("The config array has no 'enabled' key."); diff --git a/lib/symfony/dependency-injection/Extension/ExtensionInterface.php b/lib/symfony/dependency-injection/Extension/ExtensionInterface.php index f2373ed5e..bd57eef73 100644 --- a/lib/symfony/dependency-injection/Extension/ExtensionInterface.php +++ b/lib/symfony/dependency-injection/Extension/ExtensionInterface.php @@ -23,6 +23,10 @@ interface ExtensionInterface /** * Loads a specific configuration. * + * @param array> $configs + * + * @return void + * * @throws \InvalidArgumentException When provided tag is not defined in this extension */ public function load(array $configs, ContainerBuilder $container); diff --git a/lib/symfony/dependency-injection/Extension/ExtensionTrait.php b/lib/symfony/dependency-injection/Extension/ExtensionTrait.php new file mode 100644 index 000000000..5bd88892f --- /dev/null +++ b/lib/symfony/dependency-injection/Extension/ExtensionTrait.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Loader\DelegatingLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; +use Symfony\Component\DependencyInjection\Loader\GlobFileLoader; +use Symfony\Component\DependencyInjection\Loader\IniFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** + * @author Yonel Ceruto + */ +trait ExtensionTrait +{ + private function executeConfiguratorCallback(ContainerBuilder $container, \Closure $callback, ConfigurableExtensionInterface $subject): void + { + $env = $container->getParameter('kernel.environment'); + $loader = $this->createContainerLoader($container, $env); + $file = (new \ReflectionObject($subject))->getFileName(); + $bundleLoader = $loader->getResolver()->resolve($file); + if (!$bundleLoader instanceof PhpFileLoader) { + throw new \LogicException('Unable to create the ContainerConfigurator.'); + } + $bundleLoader->setCurrentDir(\dirname($file)); + $instanceof = &\Closure::bind(fn &() => $this->instanceof, $bundleLoader, $bundleLoader)(); + + try { + $callback(new ContainerConfigurator($container, $bundleLoader, $instanceof, $file, $file, $env)); + } finally { + $instanceof = []; + $bundleLoader->registerAliasesForSinglyImplementedInterfaces(); + } + } + + private function createContainerLoader(ContainerBuilder $container, string $env): DelegatingLoader + { + $buildDir = $container->getParameter('kernel.build_dir'); + $locator = new FileLocator(); + $resolver = new LoaderResolver([ + new XmlFileLoader($container, $locator, $env), + new YamlFileLoader($container, $locator, $env), + new IniFileLoader($container, $locator, $env), + new PhpFileLoader($container, $locator, $env, new ConfigBuilderGenerator($buildDir)), + new GlobFileLoader($container, $locator, $env), + new DirectoryLoader($container, $locator, $env), + new ClosureLoader($container, $env), + ]); + + return new DelegatingLoader($resolver); + } +} diff --git a/lib/symfony/dependency-injection/Extension/PrependExtensionInterface.php b/lib/symfony/dependency-injection/Extension/PrependExtensionInterface.php index 5bd18d79f..0df94e109 100644 --- a/lib/symfony/dependency-injection/Extension/PrependExtensionInterface.php +++ b/lib/symfony/dependency-injection/Extension/PrependExtensionInterface.php @@ -17,6 +17,8 @@ interface PrependExtensionInterface { /** * Allow an extension to prepend the extension configurations. + * + * @return void */ public function prepend(ContainerBuilder $container); } diff --git a/lib/symfony/dependency-injection/LICENSE b/lib/symfony/dependency-injection/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/lib/symfony/dependency-injection/LICENSE +++ b/lib/symfony/dependency-injection/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php b/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php index a9d78115d..92c4b4484 100644 --- a/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php +++ b/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php @@ -25,8 +25,8 @@ interface InstantiatorInterface /** * Instantiates a proxy object. * - * @param string $id Identifier of the requested service - * @param callable $realInstantiator Zero-argument callback that is capable of producing the real service instance + * @param string $id Identifier of the requested service + * @param callable(object=) $realInstantiator A callback that is capable of producing the real service instance * * @return object */ diff --git a/lib/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php b/lib/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php new file mode 100644 index 000000000..40b128df7 --- /dev/null +++ b/lib/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; + +/** + * @author Nicolas Grekas + */ +final class LazyServiceInstantiator implements InstantiatorInterface +{ + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object + { + $dumper = new LazyServiceDumper(); + + if (!$dumper->isProxyCandidate($definition, $asGhostObject, $id)) { + throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id)); + } + + if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject), false)) { + eval($dumper->getProxyCode($definition, $id)); + } + + return $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator); + } +} diff --git a/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php b/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php index 1696e7a90..a0c445ebb 100644 --- a/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php +++ b/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php @@ -15,18 +15,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; /** - * {@inheritdoc} - * * Noop proxy instantiator - produces the real service instead of a proxy instance. * * @author Marco Pivetta */ class RealServiceInstantiator implements InstantiatorInterface { - /** - * {@inheritdoc} - */ - public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator) + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object { return $realInstantiator(); } diff --git a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php index 351560d29..520977763 100644 --- a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php +++ b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php @@ -23,21 +23,20 @@ interface DumperInterface /** * Inspects whether the given definitions should produce proxy instantiation logic in the dumped container. * - * @return bool + * @param bool|null &$asGhostObject Set to true after the call if the proxy is a ghost object + * @param string|null $id */ - public function isProxyCandidate(Definition $definition); + public function isProxyCandidate(Definition $definition/* , bool &$asGhostObject = null, string $id = null */): bool; /** * Generates the code to be used to instantiate a proxy in the dumped factory code. - * - * @return string */ - public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode); + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string; /** * Generates the code for the lazy proxy. * - * @return string + * @param string|null $id */ - public function getProxyCode(Definition $definition); + public function getProxyCode(Definition $definition/* , string $id = null */): string; } diff --git a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php new file mode 100644 index 000000000..2571fccbf --- /dev/null +++ b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\VarExporter\Exception\LogicException; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * @author Nicolas Grekas + */ +final class LazyServiceDumper implements DumperInterface +{ + public function __construct( + private string $salt = '', + ) { + } + + public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool + { + $asGhostObject = false; + + if ($definition->hasTag('proxy')) { + if (!$definition->isLazy()) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": setting the "proxy" tag on a service requires it to be "lazy".', $id ?? $definition->getClass())); + } + + return true; + } + + if (!$definition->isLazy()) { + return false; + } + + if (!($class = $definition->getClass()) || !(class_exists($class) || interface_exists($class, false))) { + return false; + } + + if ($definition->getFactory()) { + return true; + } + + foreach ($definition->getMethodCalls() as $call) { + if ($call[2] ?? false) { + return true; + } + } + + try { + $asGhostObject = (bool) ProxyHelper::generateLazyGhost(new \ReflectionClass($class)); + } catch (LogicException) { + } + + return true; + } + + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string + { + $instantiation = 'return'; + + if ($definition->isShared()) { + $instantiation .= sprintf(' $container->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); + } + + $asGhostObject = str_contains($factoryCode, '$proxy'); + $proxyClass = $this->getProxyClass($definition, $asGhostObject); + + if (!$asGhostObject) { + return <<createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyProxy(static fn () => $factoryCode)); + } + + + EOF; + } + + $factoryCode = sprintf('static fn ($proxy) => %s', $factoryCode); + + return <<createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyGhost($factoryCode)); + } + + + EOF; + } + + public function getProxyCode(Definition $definition, string $id = null): string + { + if (!$this->isProxyCandidate($definition, $asGhostObject, $id)) { + throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id ?? $definition->getClass())); + } + $proxyClass = $this->getProxyClass($definition, $asGhostObject, $class); + + if ($asGhostObject) { + try { + return 'class '.$proxyClass.ProxyHelper::generateLazyGhost($class); + } catch (LogicException $e) { + throw new InvalidArgumentException(sprintf('Cannot generate lazy ghost for service "%s".', $id ?? $definition->getClass()), 0, $e); + } + } + $interfaces = []; + + if ($definition->hasTag('proxy')) { + foreach ($definition->getTag('proxy') as $tag) { + if (!isset($tag['interface'])) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": the "interface" attribute is missing on a "proxy" tag.', $id ?? $definition->getClass())); + } + if (!interface_exists($tag['interface']) && !class_exists($tag['interface'], false)) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": several "proxy" tags found but "%s" is not an interface.', $id ?? $definition->getClass(), $tag['interface'])); + } + if ('object' !== $definition->getClass() && !is_a($class->name, $tag['interface'], true)) { + throw new InvalidArgumentException(sprintf('Invalid "proxy" tag for service "%s": class "%s" doesn\'t implement "%s".', $id ?? $definition->getClass(), $definition->getClass(), $tag['interface'])); + } + $interfaces[] = new \ReflectionClass($tag['interface']); + } + + $class = 1 === \count($interfaces) && !$interfaces[0]->isInterface() ? array_pop($interfaces) : null; + } elseif ($class->isInterface()) { + $interfaces = [$class]; + $class = null; + } + + try { + return (\PHP_VERSION_ID >= 80200 && $class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyProxy($class, $interfaces); + } catch (LogicException $e) { + throw new InvalidArgumentException(sprintf('Cannot generate lazy proxy for service "%s".', $id ?? $definition->getClass()), 0, $e); + } + } + + public function getProxyClass(Definition $definition, bool $asGhostObject, \ReflectionClass &$class = null): string + { + $class = 'object' !== $definition->getClass() ? $definition->getClass() : 'stdClass'; + $class = new \ReflectionClass($class); + + return preg_replace('/^.*\\\\/', '', $definition->getClass()) + .($asGhostObject ? 'Ghost' : 'Proxy') + .ucfirst(substr(hash('sha256', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7)); + } +} diff --git a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php index 7e0f14c32..daa6fed79 100644 --- a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php +++ b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php @@ -22,26 +22,17 @@ use Symfony\Component\DependencyInjection\Definition; */ class NullDumper implements DumperInterface { - /** - * {@inheritdoc} - */ - public function isProxyCandidate(Definition $definition): bool + public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool { - return false; + return $asGhostObject = false; } - /** - * {@inheritdoc} - */ public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string { return ''; } - /** - * {@inheritdoc} - */ - public function getProxyCode(Definition $definition): string + public function getProxyCode(Definition $definition, string $id = null): string { return ''; } diff --git a/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php b/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php index f33011ad1..bde7d6a3f 100644 --- a/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php +++ b/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php @@ -11,10 +11,12 @@ namespace Symfony\Component\DependencyInjection\LazyProxy; +trigger_deprecation('symfony/dependency-injection', '6.2', 'The "%s" class is deprecated, use "%s" instead.', ProxyHelper::class, \Symfony\Component\VarExporter\ProxyHelper::class); + /** * @author Nicolas Grekas * - * @internal + * @deprecated since Symfony 6.2, use VarExporter's ProxyHelper instead */ class ProxyHelper { diff --git a/lib/symfony/dependency-injection/Loader/ClosureLoader.php b/lib/symfony/dependency-injection/Loader/ClosureLoader.php index fe2b91a2a..94305ae94 100644 --- a/lib/symfony/dependency-injection/Loader/ClosureLoader.php +++ b/lib/symfony/dependency-injection/Loader/ClosureLoader.php @@ -23,7 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class ClosureLoader extends Loader { - private $container; + private ContainerBuilder $container; public function __construct(ContainerBuilder $container, string $env = null) { @@ -31,18 +31,12 @@ class ClosureLoader extends Loader parent::__construct($env); } - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { return $resource($this->container, $this->env); } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { return $resource instanceof \Closure; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php index 6de2d6764..a2aa85387 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\Config\Loader\ParamConfigurator; +use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; @@ -26,13 +27,16 @@ abstract class AbstractConfigurator public const FACTORY = 'unknown'; /** - * @var callable(mixed, bool)|null + * @var \Closure(mixed, bool):mixed|null */ public static $valuePreProcessor; /** @internal */ - protected $definition; + protected Definition|Alias|null $definition = null; + /** + * @return mixed + */ public function __call(string $method, array $args) { if (method_exists($this, 'set'.$method)) { @@ -42,14 +46,14 @@ abstract class AbstractConfigurator throw new \BadMethodCallException(sprintf('Call to undefined method "%s::%s()".', static::class, $method)); } - /** - * @return array - */ - public function __sleep() + public function __sleep(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } + /** + * @return void + */ public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); @@ -58,12 +62,11 @@ abstract class AbstractConfigurator /** * Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value. * - * @param mixed $value - * @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars and arrays are + * @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars, arrays and enum are * * @return mixed the value, optionally cast to a Definition/Reference */ - public static function processValue($value, $allowServices = false) + public static function processValue(mixed $value, bool $allowServices = false): mixed { if (\is_array($value)) { foreach ($value as $k => $v) { @@ -101,6 +104,7 @@ abstract class AbstractConfigurator switch (true) { case null === $value: case \is_scalar($value): + case $value instanceof \UnitEnum: return $value; case $value instanceof ArgumentInterface: diff --git a/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php index 96d6fd75a..abf88ff25 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php @@ -18,7 +18,7 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator { protected $parent; protected $id; - private $defaultTags = []; + private array $defaultTags = []; public function __construct(ServicesConfigurator $parent, Definition $definition, string $id = null, array $defaultTags = []) { diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php index ac6fdb6d0..52d03fb09 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php @@ -30,13 +30,13 @@ class ContainerConfigurator extends AbstractConfigurator { public const FACTORY = 'container'; - private $container; - private $loader; - private $instanceof; - private $path; - private $file; - private $anonymousCount = 0; - private $env; + private ContainerBuilder $container; + private PhpFileLoader $loader; + private array $instanceof; + private string $path; + private string $file; + private int $anonymousCount = 0; + private ?string $env; public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, string $env = null) { @@ -48,17 +48,17 @@ class ContainerConfigurator extends AbstractConfigurator $this->env = $env; } - final public function extension(string $namespace, array $config) + final public function extension(string $namespace, array $config): void { if (!$this->container->hasExtension($namespace)) { - $extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $this->file, $namespace, $extensions ? implode('", "', $extensions) : 'none')); } $this->container->loadFromExtension($namespace, static::processValue($config)); } - final public function import(string $resource, string $type = null, $ignoreErrors = false) + final public function import(string $resource, string $type = null, bool|string $ignoreErrors = false): void { $this->loader->setCurrentDir(\dirname($this->path)); $this->loader->import($resource, $type, $ignoreErrors, $this->file); @@ -82,10 +82,7 @@ class ContainerConfigurator extends AbstractConfigurator return $this->env; } - /** - * @return static - */ - final public function withPath(string $path): self + final public function withPath(string $path): static { $clone = clone $this; $clone->path = $clone->file = $path; @@ -103,18 +100,6 @@ function param(string $name): ParamConfigurator return new ParamConfigurator($name); } -/** - * Creates a service reference. - * - * @deprecated since Symfony 5.1, use service() instead. - */ -function ref(string $id): ReferenceConfigurator -{ - trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "service()" instead.', __FUNCTION__); - - return new ReferenceConfigurator($id); -} - /** * Creates a reference to a service. */ @@ -123,18 +108,6 @@ function service(string $serviceId): ReferenceConfigurator return new ReferenceConfigurator($serviceId); } -/** - * Creates an inline service. - * - * @deprecated since Symfony 5.1, use inline_service() instead. - */ -function inline(string $class = null): InlineServiceConfigurator -{ - trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "inline_service()" instead.', __FUNCTION__); - - return new InlineServiceConfigurator(new Definition($class)); -} - /** * Creates an inline service. */ @@ -146,11 +119,17 @@ function inline_service(string $class = null): InlineServiceConfigurator /** * Creates a service locator. * - * @param ReferenceConfigurator[] $values + * @param array $values */ function service_locator(array $values): ServiceLocatorArgument { - return new ServiceLocatorArgument(AbstractConfigurator::processValue($values, true)); + $values = AbstractConfigurator::processValue($values, true); + + if (isset($values[0])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Using integers as keys in a "service_locator()" argument is deprecated. The keys will default to the IDs of the original services in 7.0.'); + } + + return new ServiceLocatorArgument($values); } /** @@ -166,17 +145,17 @@ function iterator(array $values): IteratorArgument /** * Creates a lazy iterator by tag name. */ -function tagged_iterator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null): TaggedIteratorArgument +function tagged_iterator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): TaggedIteratorArgument { - return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod); + return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf); } /** * Creates a service locator by tag name. */ -function tagged_locator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null): ServiceLocatorArgument +function tagged_locator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): ServiceLocatorArgument { - return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod)); + return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf)); } /** @@ -210,3 +189,13 @@ function service_closure(string $serviceId): ClosureReferenceConfigurator { return new ClosureReferenceConfigurator($serviceId); } + +/** + * Creates a closure. + */ +function closure(string|array|ReferenceConfigurator|Expression $callable): InlineServiceConfigurator +{ + return (new InlineServiceConfigurator(new Definition('Closure'))) + ->factory(['Closure', 'fromCallable']) + ->args([$callable]); +} diff --git a/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php index e0b42750d..2236cd77a 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php @@ -26,7 +26,7 @@ class DefaultsConfigurator extends AbstractServiceConfigurator public const FACTORY = 'defaults'; - private $path; + private ?string $path; public function __construct(ServicesConfigurator $parent, Definition $definition, string $path = null) { @@ -42,17 +42,13 @@ class DefaultsConfigurator extends AbstractServiceConfigurator * * @throws InvalidArgumentException when an invalid tag name or attribute is provided */ - final public function tag(string $name, array $attributes = []): self + final public function tag(string $name, array $attributes = []): static { if ('' === $name) { throw new InvalidArgumentException('The tag name in "_defaults" must be a non-empty string.'); } - foreach ($attributes as $attribute => $value) { - if (null !== $value && !\is_scalar($value)) { - throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type.', $name, $attribute)); - } - } + $this->validateAttributes($name, $attributes); $this->definition->addTag($name, $attributes); @@ -66,4 +62,16 @@ class DefaultsConfigurator extends AbstractServiceConfigurator { return $this->parent->instanceof($fqcn); } + + private function validateAttributes(string $tag, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($tag, $value, [...$path, $name]); + } elseif (!\is_scalar($value ?? '')) { + $name = implode('.', [...$path, $name]); + throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type or an array of scalar-type.', $tag, $name)); + } + } + } } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php index d1864f564..fe6780326 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php @@ -18,7 +18,7 @@ class EnvConfigurator extends ParamConfigurator /** * @var string[] */ - private $stack; + private array $stack; public function __construct(string $name) { @@ -33,7 +33,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function __call(string $name, array $arguments): self + public function __call(string $name, array $arguments): static { $processor = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $name)); @@ -45,7 +45,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function custom(string $processor, ...$args): self + public function custom(string $processor, ...$args): static { array_unshift($this->stack, $processor, ...$args); @@ -55,7 +55,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function base64(): self + public function base64(): static { array_unshift($this->stack, 'base64'); @@ -65,7 +65,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function bool(): self + public function bool(): static { array_unshift($this->stack, 'bool'); @@ -75,7 +75,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function not(): self + public function not(): static { array_unshift($this->stack, 'not'); @@ -85,7 +85,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function const(): self + public function const(): static { array_unshift($this->stack, 'const'); @@ -95,7 +95,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function csv(): self + public function csv(): static { array_unshift($this->stack, 'csv'); @@ -105,7 +105,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function file(): self + public function file(): static { array_unshift($this->stack, 'file'); @@ -115,7 +115,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function float(): self + public function float(): static { array_unshift($this->stack, 'float'); @@ -125,7 +125,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function int(): self + public function int(): static { array_unshift($this->stack, 'int'); @@ -135,7 +135,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function json(): self + public function json(): static { array_unshift($this->stack, 'json'); @@ -145,7 +145,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function key(string $key): self + public function key(string $key): static { array_unshift($this->stack, 'key', $key); @@ -155,7 +155,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function url(): self + public function url(): static { array_unshift($this->stack, 'url'); @@ -165,7 +165,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function queryString(): self + public function queryString(): static { array_unshift($this->stack, 'query_string'); @@ -175,7 +175,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function resolve(): self + public function resolve(): static { array_unshift($this->stack, 'resolve'); @@ -185,7 +185,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function default(string $fallbackParam): self + public function default(string $fallbackParam): static { array_unshift($this->stack, 'default', $fallbackParam); @@ -195,7 +195,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function string(): self + public function string(): static { array_unshift($this->stack, 'string'); @@ -205,7 +205,7 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function trim(): self + public function trim(): static { array_unshift($this->stack, 'trim'); @@ -215,10 +215,22 @@ class EnvConfigurator extends ParamConfigurator /** * @return $this */ - public function require(): self + public function require(): static { array_unshift($this->stack, 'require'); return $this; } + + /** + * @param class-string<\BackedEnum> $backedEnumClassName + * + * @return $this + */ + public function enum(string $backedEnumClassName): static + { + array_unshift($this->stack, 'enum', $backedEnumClassName); + + return $this; + } } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php new file mode 100644 index 000000000..7fe0d3da1 --- /dev/null +++ b/lib/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * @author Nicolas Grekas + */ +class FromCallableConfigurator extends AbstractServiceConfigurator +{ + use Traits\AbstractTrait; + use Traits\AutoconfigureTrait; + use Traits\AutowireTrait; + use Traits\BindTrait; + use Traits\DecorateTrait; + use Traits\DeprecateTrait; + use Traits\LazyTrait; + use Traits\PublicTrait; + use Traits\ShareTrait; + use Traits\TagTrait; + + public const FACTORY = 'services'; + + private ServiceConfigurator $serviceConfigurator; + + public function __construct(ServiceConfigurator $serviceConfigurator, Definition $definition) + { + $this->serviceConfigurator = $serviceConfigurator; + + parent::__construct($serviceConfigurator->parent, $definition, $serviceConfigurator->id); + } + + public function __destruct() + { + $this->serviceConfigurator->__destruct(); + } +} diff --git a/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php index da90a0a4c..0b1990e06 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php @@ -23,6 +23,7 @@ class InlineServiceConfigurator extends AbstractConfigurator use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\FactoryTrait; use Traits\FileTrait; use Traits\LazyTrait; @@ -32,9 +33,9 @@ class InlineServiceConfigurator extends AbstractConfigurator public const FACTORY = 'service'; - private $id = '[inline]'; - private $allowParent = true; - private $path = null; + private string $id = '[inline]'; + private bool $allowParent = true; + private ?string $path = null; public function __construct(Definition $definition) { diff --git a/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php index fbba62304..2db004051 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php @@ -22,6 +22,7 @@ class InstanceofConfigurator extends AbstractServiceConfigurator use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\LazyTrait; use Traits\PropertyTrait; use Traits\PublicTrait; @@ -30,7 +31,7 @@ class InstanceofConfigurator extends AbstractServiceConfigurator public const FACTORY = 'instanceof'; - private $path; + private ?string $path; public function __construct(ServicesConfigurator $parent, Definition $definition, string $id, string $path = null) { diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php index f0cf177d7..df5a94b43 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php @@ -22,7 +22,7 @@ class ParametersConfigurator extends AbstractConfigurator { public const FACTORY = 'parameters'; - private $container; + private ContainerBuilder $container; public function __construct(ContainerBuilder $container) { @@ -30,11 +30,9 @@ class ParametersConfigurator extends AbstractConfigurator } /** - * Creates a parameter. - * * @return $this */ - final public function set(string $name, $value): self + final public function set(string $name, mixed $value): static { if ($value instanceof Expression) { throw new InvalidArgumentException(sprintf('Using an expression in parameter "%s" is not allowed.', $name)); @@ -46,11 +44,9 @@ class ParametersConfigurator extends AbstractConfigurator } /** - * Creates a parameter. - * * @return $this */ - final public function __invoke(string $name, $value): self + final public function __invoke(string $name, mixed $value): static { return $this->set($name, $value); } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php index e1b3702aa..4ab957a85 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php @@ -26,6 +26,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\DeprecateTrait; use Traits\FactoryTrait; use Traits\LazyTrait; @@ -37,12 +38,13 @@ class PrototypeConfigurator extends AbstractServiceConfigurator public const FACTORY = 'load'; - private $loader; - private $resource; - private $excludes; - private $allowParent; + private PhpFileLoader $loader; + private string $resource; + private ?array $excludes = null; + private bool $allowParent; + private ?string $path; - public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent) + public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent, string $path = null) { $definition = new Definition(); if (!$defaults->isPublic() || !$defaults->isPrivate()) { @@ -57,6 +59,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator $this->loader = $loader; $this->resource = $resource; $this->allowParent = $allowParent; + $this->path = $path; parent::__construct($parent, $definition, $namespace, $defaults->getTags()); } @@ -65,10 +68,10 @@ class PrototypeConfigurator extends AbstractServiceConfigurator { parent::__destruct(); - if ($this->loader) { - $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes); + if (isset($this->loader)) { + $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes, $this->path); } - $this->loader = null; + unset($this->loader); } /** @@ -78,7 +81,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator * * @return $this */ - final public function exclude($excludes): self + final public function exclude(array|string $excludes): static { $this->excludes = (array) $excludes; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php index fa042538c..4a83f9c66 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php @@ -19,10 +19,10 @@ use Symfony\Component\DependencyInjection\ContainerInterface; class ReferenceConfigurator extends AbstractConfigurator { /** @internal */ - protected $id; + protected string $id; /** @internal */ - protected $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + protected int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; public function __construct(string $id) { @@ -32,7 +32,7 @@ class ReferenceConfigurator extends AbstractConfigurator /** * @return $this */ - final public function ignoreOnInvalid(): self + final public function ignoreOnInvalid(): static { $this->invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; @@ -42,7 +42,7 @@ class ReferenceConfigurator extends AbstractConfigurator /** * @return $this */ - final public function nullOnInvalid(): self + final public function nullOnInvalid(): static { $this->invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; @@ -52,17 +52,14 @@ class ReferenceConfigurator extends AbstractConfigurator /** * @return $this */ - final public function ignoreOnUninitialized(): self + final public function ignoreOnUninitialized(): static { $this->invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; return $this; } - /** - * @return string - */ - public function __toString() + public function __toString(): string { return $this->id; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php index 932ecd351..9042ed1d6 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php @@ -27,10 +27,12 @@ class ServiceConfigurator extends AbstractServiceConfigurator use Traits\CallTrait; use Traits\ClassTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\DecorateTrait; use Traits\DeprecateTrait; use Traits\FactoryTrait; use Traits\FileTrait; + use Traits\FromCallableTrait; use Traits\LazyTrait; use Traits\ParentTrait; use Traits\PropertyTrait; @@ -41,11 +43,11 @@ class ServiceConfigurator extends AbstractServiceConfigurator public const FACTORY = 'services'; - private $container; - private $instanceof; - private $allowParent; - private $path; - private $destructed = false; + private ContainerBuilder $container; + private array $instanceof; + private bool $allowParent; + private ?string $path; + private bool $destructed = false; public function __construct(ContainerBuilder $container, array $instanceof, bool $allowParent, ServicesConfigurator $parent, Definition $definition, ?string $id, array $defaultTags, string $path = null) { diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php index 388251e26..ee4d1ad16 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php @@ -26,13 +26,13 @@ class ServicesConfigurator extends AbstractConfigurator { public const FACTORY = 'services'; - private $defaults; - private $container; - private $loader; - private $instanceof; - private $path; - private $anonymousHash; - private $anonymousCount; + private Definition $defaults; + private ContainerBuilder $container; + private PhpFileLoader $loader; + private array $instanceof; + private ?string $path; + private string $anonymousHash; + private int $anonymousCount; public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path = null, int &$anonymousCount = 0) { @@ -101,7 +101,7 @@ class ServicesConfigurator extends AbstractConfigurator * * @return $this */ - final public function remove(string $id): self + final public function remove(string $id): static { $this->container->removeDefinition($id); $this->container->removeAlias($id); @@ -129,7 +129,7 @@ class ServicesConfigurator extends AbstractConfigurator */ final public function load(string $namespace, string $resource): PrototypeConfigurator { - return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, true); + return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, true, $this->path); } /** diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php index 82ba21d7b..b42b0708c 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php @@ -19,7 +19,7 @@ trait AbstractTrait * * @return $this */ - final public function abstract(bool $abstract = true): self + final public function abstract(bool $abstract = true): static { $this->definition->setAbstract($abstract); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php index 5c9a47560..67051f31f 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php @@ -18,7 +18,7 @@ trait ArgumentTrait * * @return $this */ - final public function args(array $arguments): self + final public function args(array $arguments): static { $this->definition->setArguments(static::processValue($arguments, true)); @@ -28,12 +28,9 @@ trait ArgumentTrait /** * Sets one argument to pass to the service constructor/factory method. * - * @param string|int $key - * @param mixed $value - * * @return $this */ - final public function arg($key, $value): self + final public function arg(string|int $key, mixed $value): static { $this->definition->setArgument($key, static::processValue($value, true)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php index 9eab22cfe..f5762c55b 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php @@ -22,7 +22,7 @@ trait AutoconfigureTrait * * @throws InvalidArgumentException when a parent is already set */ - final public function autoconfigure(bool $autoconfigured = true): self + final public function autoconfigure(bool $autoconfigured = true): static { $this->definition->setAutoconfigured($autoconfigured); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php index 2837a0201..9bce28f9a 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php @@ -18,7 +18,7 @@ trait AutowireTrait * * @return $this */ - final public function autowire(bool $autowired = true): self + final public function autowire(bool $autowired = true): static { $this->definition->setAutowired($autowired); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php index 3021e0708..6bf6b6f43 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php @@ -29,7 +29,7 @@ trait BindTrait * * @return $this */ - final public function bind(string $nameOrFqcn, $valueOrRef): self + final public function bind(string $nameOrFqcn, mixed $valueOrRef): static { $valueOrRef = static::processValue($valueOrRef, true); $bindings = $this->definition->getBindings(); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php index 28f92d274..dbfb158e9 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php @@ -26,7 +26,7 @@ trait CallTrait * * @throws InvalidArgumentException on empty $method param */ - final public function call(string $method, array $arguments = [], bool $returnsClone = false): self + final public function call(string $method, array $arguments = [], bool $returnsClone = false): static { $this->definition->addMethodCall($method, static::processValue($arguments, true), $returnsClone); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php index 20da791aa..429cebcb6 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php @@ -18,7 +18,7 @@ trait ClassTrait * * @return $this */ - final public function class(?string $class): self + final public function class(?string $class): static { $this->definition->setClass($class); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php index 25d363c9a..a4b447c08 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php @@ -11,16 +11,16 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; +use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; + trait ConfiguratorTrait { /** * Sets a configurator to call after the service is fully initialized. * - * @param string|array $configurator A PHP callable reference - * * @return $this */ - final public function configurator($configurator): self + final public function configurator(string|array|ReferenceConfigurator $configurator): static { $this->definition->setConfigurator(static::processValue($configurator, true)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php new file mode 100644 index 000000000..7f16ed589 --- /dev/null +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait ConstructorTrait +{ + /** + * Sets a static constructor. + * + * @return $this + */ + final public function constructor(string $constructor): static + { + $this->definition->setFactory([null, $constructor]); + + return $this; + } +} diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php index b3a1ae1b5..ae6d3c948 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php @@ -25,7 +25,7 @@ trait DecorateTrait * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ - final public function decorate(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): self + final public function decorate(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static { $this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php index ea77e456d..04ff9a047 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php @@ -26,21 +26,8 @@ trait DeprecateTrait * * @throws InvalidArgumentException when the message template is invalid */ - final public function deprecate(/* string $package, string $version, string $message */): self + final public function deprecate(string $package, string $version, string $message): static { - $args = \func_get_args(); - $package = $version = $message = ''; - - if (\func_num_args() < 3) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); - - $message = (string) ($args[0] ?? null); - } else { - $package = (string) $args[0]; - $version = (string) $args[1]; - $message = (string) $args[2]; - } - $this->definition->setDeprecated($package, $version, $message); return $this; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php index 1286ba4c1..1c19f1d88 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php @@ -13,17 +13,16 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; +use Symfony\Component\ExpressionLanguage\Expression; trait FactoryTrait { /** * Sets a factory. * - * @param string|array|ReferenceConfigurator $factory A PHP callable reference - * * @return $this */ - final public function factory($factory): self + final public function factory(string|array|ReferenceConfigurator|Expression $factory): static { if (\is_string($factory) && 1 === substr_count($factory, ':')) { $factoryParts = explode(':', $factory); @@ -31,6 +30,10 @@ trait FactoryTrait throw new InvalidArgumentException(sprintf('Invalid factory "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $factory, $factoryParts[0], $factoryParts[1])); } + if ($factory instanceof Expression) { + $factory = '@='.$factory; + } + $this->definition->setFactory(static::processValue($factory, true)); return $this; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php index 5f42aef8f..7b72181ee 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php @@ -18,7 +18,7 @@ trait FileTrait * * @return $this */ - final public function file(string $file): self + final public function file(string $file): static { $this->definition->setFile($file); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php new file mode 100644 index 000000000..e3508ab89 --- /dev/null +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Loader\Configurator\FromCallableConfigurator; +use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; +use Symfony\Component\ExpressionLanguage\Expression; + +trait FromCallableTrait +{ + final public function fromCallable(string|array|ReferenceConfigurator|Expression $callable): FromCallableConfigurator + { + if ($this->definition instanceof ChildDefinition) { + throw new InvalidArgumentException('The configuration key "parent" is unsupported when using "fromCallable()".'); + } + + foreach ([ + 'synthetic' => 'isSynthetic', + 'factory' => 'getFactory', + 'file' => 'getFile', + 'arguments' => 'getArguments', + 'properties' => 'getProperties', + 'configurator' => 'getConfigurator', + 'calls' => 'getMethodCalls', + ] as $key => $method) { + if ($this->definition->$method()) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported when using "fromCallable()".', $key)); + } + } + + $this->definition->setFactory(['Closure', 'fromCallable']); + + if (\is_string($callable) && 1 === substr_count($callable, ':')) { + $parts = explode(':', $callable); + + throw new InvalidArgumentException(sprintf('Invalid callable "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $callable, $parts[0], $parts[1])); + } + + if ($callable instanceof Expression) { + $callable = '@='.$callable; + } + + $this->definition->setArguments([static::processValue($callable, true)]); + + if ('Closure' !== ($this->definition->getClass() ?? 'Closure')) { + $this->definition->setLazy(true); + } else { + $this->definition->setClass('Closure'); + } + + return new FromCallableConfigurator($this, $this->definition); + } +} diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php index 2829defb5..ac4326b85 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php @@ -20,7 +20,7 @@ trait LazyTrait * * @return $this */ - final public function lazy($lazy = true): self + final public function lazy(bool|string $lazy = true): static { $this->definition->setLazy((bool) $lazy); if (\is_string($lazy)) { diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php index 37194e50e..409602581 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php @@ -23,7 +23,7 @@ trait ParentTrait * * @throws InvalidArgumentException when parent cannot be set */ - final public function parent(string $parent): self + final public function parent(string $parent): static { if (!$this->allowParent) { throw new InvalidArgumentException(sprintf('A parent cannot be defined when either "_instanceof" or "_defaults" are also defined for service prototype "%s".', $this->id)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php index 10fdcfb82..0dab40fb6 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php @@ -18,7 +18,7 @@ trait PropertyTrait * * @return $this */ - final public function property(string $name, $value): self + final public function property(string $name, mixed $value): static { $this->definition->setProperty($name, static::processValue($value, true)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php index f15756c1b..3d88d7432 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php @@ -16,7 +16,7 @@ trait PublicTrait /** * @return $this */ - final public function public(): self + final public function public(): static { $this->definition->setPublic(true); @@ -26,7 +26,7 @@ trait PublicTrait /** * @return $this */ - final public function private(): self + final public function private(): static { $this->definition->setPublic(false); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php index 16fde0f29..801fabcce 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php @@ -18,7 +18,7 @@ trait ShareTrait * * @return $this */ - final public function share(bool $shared = true): self + final public function share(bool $shared = true): static { $this->definition->setShared($shared); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php index cb08b1133..5e8c4b3c6 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php @@ -19,7 +19,7 @@ trait SyntheticTrait * * @return $this */ - final public function synthetic(bool $synthetic = true): self + final public function synthetic(bool $synthetic = true): static { $this->definition->setSynthetic($synthetic); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php index ba9f8afa9..a38d04a83 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php @@ -20,20 +20,28 @@ trait TagTrait * * @return $this */ - final public function tag(string $name, array $attributes = []): self + final public function tag(string $name, array $attributes = []): static { if ('' === $name) { throw new InvalidArgumentException(sprintf('The tag name for service "%s" must be a non-empty string.', $this->id)); } - foreach ($attributes as $attribute => $value) { - if (!\is_scalar($value) && null !== $value) { - throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $this->id, $name, $attribute)); - } - } + $this->validateAttributes($name, $attributes); $this->definition->addTag($name, $attributes); return $this; } + + private function validateAttributes(string $tag, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($tag, $value, [...$path, $name]); + } elseif (!\is_scalar($value ?? '')) { + $name = implode('.', [...$path, $name]); + throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type or an array of scalar-types for service "%s", tag "%s", attribute "%s".', $this->id, $tag, $name)); + } + } + } } diff --git a/lib/symfony/dependency-injection/Loader/DirectoryLoader.php b/lib/symfony/dependency-injection/Loader/DirectoryLoader.php index b4e9a5917..1b5e81d19 100644 --- a/lib/symfony/dependency-injection/Loader/DirectoryLoader.php +++ b/lib/symfony/dependency-injection/Loader/DirectoryLoader.php @@ -18,10 +18,7 @@ namespace Symfony\Component\DependencyInjection\Loader; */ class DirectoryLoader extends FileLoader { - /** - * {@inheritdoc} - */ - public function load($file, string $type = null) + public function load(mixed $file, string $type = null): mixed { $file = rtrim($file, '/'); $path = $this->locator->locate($file); @@ -42,10 +39,7 @@ class DirectoryLoader extends FileLoader return null; } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { if ('directory' === $type) { return true; diff --git a/lib/symfony/dependency-injection/Loader/FileLoader.php b/lib/symfony/dependency-injection/Loader/FileLoader.php index f5f78e30f..4b56c1788 100644 --- a/lib/symfony/dependency-injection/Loader/FileLoader.php +++ b/lib/symfony/dependency-injection/Loader/FileLoader.php @@ -17,12 +17,16 @@ use Symfony\Component\Config\FileLocatorInterface; use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Exclude; use Symfony\Component\DependencyInjection\Attribute\When; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; /** * FileLoader is the abstract class used by all built-in loaders that are file based. @@ -38,6 +42,8 @@ abstract class FileLoader extends BaseFileLoader protected $instanceof = []; protected $interfaces = []; protected $singlyImplemented = []; + /** @var array */ + protected $aliases = []; protected $autoRegisterAliasesForSinglyImplementedInterfaces = true; public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null) @@ -48,11 +54,9 @@ abstract class FileLoader extends BaseFileLoader } /** - * {@inheritdoc} - * * @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found */ - public function import($resource, string $type = null, $ignoreErrors = false, string $sourceResource = null, $exclude = null) + public function import(mixed $resource, string $type = null, bool|string $ignoreErrors = false, string $sourceResource = null, $exclude = null): mixed { $args = \func_get_args(); @@ -90,8 +94,11 @@ abstract class FileLoader extends BaseFileLoader * @param string $namespace The namespace prefix of classes in the scanned directory * @param string $resource The directory to look for classes, glob-patterns allowed * @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude + * @param string|null $source The path to the file that defines the auto-discovery rule + * + * @return void */ - public function registerClasses(Definition $prototype, string $namespace, string $resource, $exclude = null) + public function registerClasses(Definition $prototype, string $namespace, string $resource, string|array $exclude = null/* , string $source = null */) { if (!str_ends_with($namespace, '\\')) { throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": "%s".', $namespace)); @@ -99,66 +106,138 @@ abstract class FileLoader extends BaseFileLoader if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++$/', $namespace)) { throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace)); } + // This can happen with YAML files + if (\is_array($exclude) && \in_array(null, $exclude, true)) { + throw new InvalidArgumentException('The exclude list must not contain a "null" value.'); + } + // This can happen with XML files + if (\is_array($exclude) && \in_array('', $exclude, true)) { + throw new InvalidArgumentException('The exclude list must not contain an empty value.'); + } + $source = \func_num_args() > 4 ? func_get_arg(4) : null; $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); $autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null; - $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes); - // prepare for deep cloning - $serializedPrototype = serialize($prototype); + $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes, $source); + + $getPrototype = static fn () => clone $prototype; + $serialized = serialize($prototype); + + // avoid deep cloning if no definitions are nested + if (strpos($serialized, 'O:48:"Symfony\Component\DependencyInjection\Definition"', 55) + || strpos($serialized, 'O:53:"Symfony\Component\DependencyInjection\ChildDefinition"', 55) + ) { + // prepare for deep cloning + foreach (['Arguments', 'Properties', 'MethodCalls', 'Configurator', 'Factory', 'Bindings'] as $key) { + $serialized = serialize($prototype->{'get'.$key}()); + + if (strpos($serialized, 'O:48:"Symfony\Component\DependencyInjection\Definition"') + || strpos($serialized, 'O:53:"Symfony\Component\DependencyInjection\ChildDefinition"') + ) { + $getPrototype = static fn () => $getPrototype()->{'set'.$key}(unserialize($serialized)); + } + } + } + unset($serialized); foreach ($classes as $class => $errorMessage) { - if (null === $errorMessage && $autoconfigureAttributes && $this->env) { + if (null === $errorMessage && $autoconfigureAttributes) { $r = $this->container->getReflectionClass($class); - $attribute = null; - foreach ($r->getAttributes(When::class) as $attribute) { - if ($this->env === $attribute->newInstance()->env) { - $attribute = null; - break; - } - } - if (null !== $attribute) { + if ($r->getAttributes(Exclude::class)[0] ?? null) { + $this->addContainerExcludedTag($class, $source); continue; } + if ($this->env) { + $attribute = null; + foreach ($r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + if ($this->env === $attribute->newInstance()->env) { + $attribute = null; + break; + } + } + if (null !== $attribute) { + $this->addContainerExcludedTag($class, $source); + continue; + } + } } if (interface_exists($class, false)) { $this->interfaces[] = $class; } else { - $this->setDefinition($class, $definition = unserialize($serializedPrototype)); + $this->setDefinition($class, $definition = $getPrototype()); if (null !== $errorMessage) { $definition->addError($errorMessage); continue; } + $definition->setClass($class); + + $interfaces = []; foreach (class_implements($class, false) as $interface) { $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; + $interfaces[] = $interface; + } + + if (!$autoconfigureAttributes) { + continue; + } + $r = $this->container->getReflectionClass($class); + $defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null; + foreach ($r->getAttributes(AsAlias::class) as $attr) { + /** @var AsAlias $attribute */ + $attribute = $attr->newInstance(); + $alias = $attribute->id ?? $defaultAlias; + $public = $attribute->public; + if (null === $alias) { + throw new LogicException(sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class)); + } + if (isset($this->aliases[$alias])) { + throw new LogicException(sprintf('The "%s" alias has already been defined with the #[AsAlias] attribute in "%s".', $alias, $this->aliases[$alias])); + } + $this->aliases[$alias] = new Alias($class, $public); } } } + foreach ($this->aliases as $alias => $aliasDefinition) { + $this->container->setAlias($alias, $aliasDefinition); + } + if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) { $this->registerAliasesForSinglyImplementedInterfaces(); } } + /** + * @return void + */ public function registerAliasesForSinglyImplementedInterfaces() { foreach ($this->interfaces as $interface) { - if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) { + if (!empty($this->singlyImplemented[$interface]) && !isset($this->aliases[$interface]) && !$this->container->has($interface)) { $this->container->setAlias($interface, $this->singlyImplemented[$interface]); } } - $this->interfaces = $this->singlyImplemented = []; + $this->interfaces = $this->singlyImplemented = $this->aliases = []; } /** * Registers a definition in the container with its instanceof-conditionals. + * + * @return void */ protected function setDefinition(string $id, Definition $definition) { $this->container->removeBindings($id); + foreach ($definition->getTag('container.error') as $error) { + if (isset($error['message'])) { + $definition->addError($error['message']); + } + } + if ($this->isLoadingInstanceof) { if (!$definition instanceof ChildDefinition) { throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_debug_type($definition))); @@ -169,7 +248,7 @@ abstract class FileLoader extends BaseFileLoader } } - private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes): array + private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes, ?string $source): array { $parameterBag = $this->container->getParameterBag(); @@ -178,9 +257,7 @@ abstract class FileLoader extends BaseFileLoader $excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns)); foreach ($excludePatterns as $excludePattern) { foreach ($this->glob($excludePattern, true, $resource, true, true) as $path => $info) { - if (null === $excludePrefix) { - $excludePrefix = $resource->getPrefix(); - } + $excludePrefix ??= $resource->getPrefix(); // normalize Windows slashes and remove trailing slashes $excludePaths[rtrim(str_replace('\\', '/', $path), '/')] = true; @@ -189,7 +266,6 @@ abstract class FileLoader extends BaseFileLoader $pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern)); $classes = []; - $extRegexp = '/\\.php$/'; $prefixLen = null; foreach ($this->glob($pattern, true, $resource, false, false, $excludePaths) as $path => $info) { if (null === $prefixLen) { @@ -204,10 +280,10 @@ abstract class FileLoader extends BaseFileLoader continue; } - if (!preg_match($extRegexp, $path, $m) || !$info->isReadable()) { + if (!str_ends_with($path, '.php')) { continue; } - $class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, -\strlen($m[0]))), '\\'); + $class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, -4)), '\\'); if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $class)) { continue; @@ -242,6 +318,30 @@ abstract class FileLoader extends BaseFileLoader } } + if (null !== $prefixLen) { + foreach ($excludePaths as $path => $_) { + $class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, str_ends_with($path, '.php') ? -4 : null)), '\\'); + $this->addContainerExcludedTag($class, $source); + } + } + return $classes; } + + private function addContainerExcludedTag(string $class, ?string $source): void + { + if ($this->container->has($class)) { + return; + } + + static $attributes = []; + + if (null !== $source && !isset($attributes[$source])) { + $attributes[$source] = ['source' => sprintf('in "%s/%s"', basename(\dirname($source)), basename($source))]; + } + + $this->container->register($class, $class) + ->setAbstract(true) + ->addTag('container.excluded', null !== $source ? $attributes[$source] : []); + } } diff --git a/lib/symfony/dependency-injection/Loader/GlobFileLoader.php b/lib/symfony/dependency-injection/Loader/GlobFileLoader.php index e38aaf43b..50349b257 100644 --- a/lib/symfony/dependency-injection/Loader/GlobFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/GlobFileLoader.php @@ -18,10 +18,7 @@ namespace Symfony\Component\DependencyInjection\Loader; */ class GlobFileLoader extends FileLoader { - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { foreach ($this->glob($resource, false, $globResource) as $path => $info) { $this->import($path); @@ -32,10 +29,7 @@ class GlobFileLoader extends FileLoader return null; } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { return 'glob' === $type; } diff --git a/lib/symfony/dependency-injection/Loader/IniFileLoader.php b/lib/symfony/dependency-injection/Loader/IniFileLoader.php index d88d7a630..c177790e3 100644 --- a/lib/symfony/dependency-injection/Loader/IniFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/IniFileLoader.php @@ -21,10 +21,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; */ class IniFileLoader extends FileLoader { - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { $path = $this->locator->locate($resource); @@ -41,7 +38,11 @@ class IniFileLoader extends FileLoader if (isset($result['parameters']) && \is_array($result['parameters'])) { foreach ($result['parameters'] as $key => $value) { - $this->container->setParameter($key, $this->phpize($value)); + if (\is_array($value)) { + $this->container->setParameter($key, array_map($this->phpize(...), $value)); + } else { + $this->container->setParameter($key, $this->phpize($value)); + } } } @@ -54,10 +55,7 @@ class IniFileLoader extends FileLoader return null; } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { if (!\is_string($resource)) { return false; @@ -74,10 +72,8 @@ class IniFileLoader extends FileLoader * Note that the following features are not supported: * * strings with escaped quotes are not supported "foo\"bar"; * * string concatenation ("foo" "bar"). - * - * @return mixed */ - private function phpize(string $value) + private function phpize(string $value): mixed { // trim on the right as comments removal keep whitespaces if ($value !== $v = rtrim($value)) { @@ -85,21 +81,18 @@ class IniFileLoader extends FileLoader } $lowercaseValue = strtolower($value); - switch (true) { - case \defined($value): - return \constant($value); - case 'yes' === $lowercaseValue || 'on' === $lowercaseValue: - return true; - case 'no' === $lowercaseValue || 'off' === $lowercaseValue || 'none' === $lowercaseValue: - return false; - case isset($value[1]) && ( - ("'" === $value[0] && "'" === $value[\strlen($value) - 1]) || - ('"' === $value[0] && '"' === $value[\strlen($value) - 1]) - ): - // quoted string - return substr($value, 1, -1); - default: - return XmlUtils::phpize($value); - } + return match (true) { + \defined($value) => \constant($value), + 'yes' === $lowercaseValue, + 'on' === $lowercaseValue => true, + 'no' === $lowercaseValue, + 'off' === $lowercaseValue, + 'none' === $lowercaseValue => false, + isset($value[1]) && ( + ("'" === $value[0] && "'" === $value[\strlen($value) - 1]) + || ('"' === $value[0] && '"' === $value[\strlen($value) - 1]) + ) => substr($value, 1, -1), // quoted string + default => XmlUtils::phpize($value), + }; } } diff --git a/lib/symfony/dependency-injection/Loader/PhpFileLoader.php b/lib/symfony/dependency-injection/Loader/PhpFileLoader.php index 3815b28f0..e56fb5156 100644 --- a/lib/symfony/dependency-injection/Loader/PhpFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/PhpFileLoader.php @@ -34,7 +34,7 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura class PhpFileLoader extends FileLoader { protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; - private $generator; + private ?ConfigBuilderGeneratorInterface $generator; public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null, ConfigBuilderGeneratorInterface $generator = null) { @@ -42,10 +42,7 @@ class PhpFileLoader extends FileLoader $this->generator = $generator; } - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { // the container and loader variables are exposed to the included file below $container = $this->container; @@ -74,10 +71,7 @@ class PhpFileLoader extends FileLoader return null; } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { if (!\is_string($resource)) { return false; @@ -93,28 +87,23 @@ class PhpFileLoader extends FileLoader /** * Resolve the parameters to the $callback and execute it. */ - private function executeCallback(callable $callback, ContainerConfigurator $containerConfigurator, string $path) + private function executeCallback(callable $callback, ContainerConfigurator $containerConfigurator, string $path): void { - if (!$callback instanceof \Closure) { - $callback = \Closure::fromCallable($callback); - } - + $callback = $callback(...); $arguments = []; $configBuilders = []; $r = new \ReflectionFunction($callback); - if (\PHP_VERSION_ID >= 80000) { - $attribute = null; - foreach ($r->getAttributes(When::class) as $attribute) { - if ($this->env === $attribute->newInstance()->env) { - $attribute = null; - break; - } - } - if (null !== $attribute) { - return; + $attribute = null; + foreach ($r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + if ($this->env === $attribute->newInstance()->env) { + $attribute = null; + break; } } + if (null !== $attribute) { + return; + } foreach ($r->getParameters() as $parameter) { $reflectionType = $parameter->getType(); @@ -134,6 +123,12 @@ class PhpFileLoader extends FileLoader case self::class: $arguments[] = $this; break; + case 'string': + if (null !== $this->env && 'env' === $parameter->getName()) { + $arguments[] = $this->env; + break; + } + // no break default: try { $configBuilder = $this->configBuilder($type); @@ -174,20 +169,20 @@ class PhpFileLoader extends FileLoader return new $namespace(); } - // If it does not start with Symfony\Config\ we dont know how to handle this - if ('Symfony\\Config\\' !== substr($namespace, 0, 15)) { + // If it does not start with Symfony\Config\ we don't know how to handle this + if (!str_starts_with($namespace, 'Symfony\\Config\\')) { throw new InvalidArgumentException(sprintf('Could not find or generate class "%s".', $namespace)); } // Try to get the extension alias $alias = Container::underscore(substr($namespace, 15, -6)); - if (false !== strpos($alias, '\\')) { + if (str_contains($alias, '\\')) { throw new InvalidArgumentException('You can only use "root" ConfigBuilders from "Symfony\\Config\\" namespace. Nested classes like "Symfony\\Config\\Framework\\CacheConfig" cannot be used.'); } if (!$this->container->hasExtension($alias)) { - $extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s". Looked for namespace "%s", found "%s".', $namespace, $alias, $extensions ? implode('", "', $extensions) : 'none')); } diff --git a/lib/symfony/dependency-injection/Loader/XmlFileLoader.php b/lib/symfony/dependency-injection/Loader/XmlFileLoader.php index 73b0f0deb..b6eb6732b 100644 --- a/lib/symfony/dependency-injection/Loader/XmlFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/XmlFileLoader.php @@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Reference; @@ -40,10 +41,7 @@ class XmlFileLoader extends FileLoader protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { $path = $this->locator->locate($resource); @@ -95,10 +93,7 @@ class XmlFileLoader extends FileLoader } } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { if (!\is_string($resource)) { return false; @@ -111,14 +106,14 @@ class XmlFileLoader extends FileLoader return 'xml' === $type; } - private function parseParameters(\DOMDocument $xml, string $file, \DOMNode $root = null) + private function parseParameters(\DOMDocument $xml, string $file, \DOMNode $root = null): void { if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) { $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file)); } } - private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = null) + private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = null): void { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); @@ -134,7 +129,7 @@ class XmlFileLoader extends FileLoader } } - private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, \DOMNode $root = null) + private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, \DOMNode $root = null): void { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); @@ -184,7 +179,7 @@ class XmlFileLoader extends FileLoader } $excludes = [$service->getAttribute('exclude')]; } - $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes); + $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes, $file); } else { $this->setDefinition((string) $service->getAttribute('id'), $definition); } @@ -227,11 +222,11 @@ class XmlFileLoader extends FileLoader $version = $deprecated[0]->getAttribute('version') ?: ''; if (!$deprecated[0]->hasAttribute('package')) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); + throw new InvalidArgumentException(sprintf('Missing attribute "package" at node "deprecated" in "%s".', $file)); } if (!$deprecated[0]->hasAttribute('version')) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); + throw new InvalidArgumentException(sprintf('Missing attribute "version" at node "deprecated" in "%s".', $file)); } $alias->setDeprecated($package, $version, $message); @@ -258,7 +253,7 @@ class XmlFileLoader extends FileLoader foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) { if ($value = $service->getAttribute($key)) { $method = 'set'.$key; - $definition->$method($value = XmlUtils::phpize($value)); + $definition->$method(XmlUtils::phpize($value)); } } @@ -286,12 +281,12 @@ class XmlFileLoader extends FileLoader $package = $deprecated[0]->getAttribute('package') ?: ''; $version = $deprecated[0]->getAttribute('version') ?: ''; - if ('' === $package) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); + if (!$deprecated[0]->hasAttribute('package')) { + throw new InvalidArgumentException(sprintf('Missing attribute "package" at node "deprecated" in "%s".', $file)); } - if ('' === $version) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); + if (!$deprecated[0]->hasAttribute('version')) { + throw new InvalidArgumentException(sprintf('Missing attribute "version" at node "deprecated" in "%s".', $file)); } $definition->setDeprecated($package, $version, $message); @@ -304,6 +299,11 @@ class XmlFileLoader extends FileLoader $factory = $factories[0]; if ($function = $factory->getAttribute('function')) { $definition->setFactory($function); + } elseif ($expression = $factory->getAttribute('expression')) { + if (!class_exists(Expression::class)) { + throw new \LogicException('The "expression" attribute cannot be used on factories without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + $definition->setFactory('@='.$expression); } else { if ($childService = $factory->getAttribute('service')) { $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); @@ -315,6 +315,14 @@ class XmlFileLoader extends FileLoader } } + if ($constructor = $service->getAttribute('constructor')) { + if (null !== $definition->getFactory()) { + throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $service->getAttribute('id'))); + } + + $definition->setFactory([null, $constructor]); + } + if ($configurators = $this->getChildren($service, 'configurator')) { $configurator = $configurators[0]; if ($function = $configurator->getAttribute('function')) { @@ -337,10 +345,14 @@ class XmlFileLoader extends FileLoader $tags = $this->getChildren($service, 'tag'); foreach ($tags as $tag) { - $parameters = []; - $tagName = $tag->nodeValue; + $tagNameComesFromAttribute = $tag->childElementCount || '' === $tag->nodeValue; + if ('' === $tagName = $tagNameComesFromAttribute ? $tag->getAttribute('name') : $tag->nodeValue) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', (string) $service->getAttribute('id'), $file)); + } + + $parameters = $this->getTagAttributes($tag, sprintf('The attribute name of tag "%s" for service "%s" in %s must be a non-empty string.', $tagName, (string) $service->getAttribute('id'), $file)); foreach ($tag->attributes as $name => $node) { - if ('name' === $name && '' === $tagName) { + if ($tagNameComesFromAttribute && 'name' === $name) { continue; } @@ -351,10 +363,6 @@ class XmlFileLoader extends FileLoader $parameters[$name] = XmlUtils::phpize($node->nodeValue); } - if ('' === $tagName && '' === $tagName = $tag->getAttribute('name')) { - throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $service->getAttribute('id'), $file)); - } - $definition->addTag($tagName, $parameters); } @@ -391,6 +399,52 @@ class XmlFileLoader extends FileLoader $definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior); } + if ($callable = $this->getChildren($service, 'from-callable')) { + if ($definition instanceof ChildDefinition) { + throw new InvalidArgumentException(sprintf('Attribute "parent" is unsupported when using "" on service "%s".', (string) $service->getAttribute('id'))); + } + + foreach ([ + 'Attribute "synthetic"' => 'isSynthetic', + 'Attribute "file"' => 'getFile', + 'Tag ""' => 'getFactory', + 'Tag ""' => 'getArguments', + 'Tag ""' => 'getProperties', + 'Tag ""' => 'getConfigurator', + 'Tag ""' => 'getMethodCalls', + ] as $key => $method) { + if ($definition->$method()) { + throw new InvalidArgumentException($key.sprintf(' is unsupported when using "" on service "%s".', (string) $service->getAttribute('id'))); + } + } + + $definition->setFactory(['Closure', 'fromCallable']); + + if ('Closure' !== ($definition->getClass() ?? 'Closure')) { + $definition->setLazy(true); + } else { + $definition->setClass('Closure'); + } + + $callable = $callable[0]; + if ($function = $callable->getAttribute('function')) { + $definition->setArguments([$function]); + } elseif ($expression = $callable->getAttribute('expression')) { + if (!class_exists(Expression::class)) { + throw new \LogicException('The "expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + $definition->setArguments(['@='.$expression]); + } else { + if ($childService = $callable->getAttribute('service')) { + $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); + } else { + $class = $callable->hasAttribute('class') ? $callable->getAttribute('class') : null; + } + + $definition->setArguments([[$class, $callable->getAttribute('method') ?: '__invoke']]); + } + } + return $definition; } @@ -402,7 +456,7 @@ class XmlFileLoader extends FileLoader private function parseFileToDOM(string $file): \DOMDocument { try { - $dom = XmlUtils::loadFile($file, [$this, 'validateSchema']); + $dom = XmlUtils::loadFile($file, $this->validateSchema(...)); } catch (\InvalidArgumentException $e) { throw new InvalidArgumentException(sprintf('Unable to parse file "%s": ', $file).$e->getMessage(), $e->getCode(), $e); } @@ -415,7 +469,7 @@ class XmlFileLoader extends FileLoader /** * Processes anonymous services. */ - private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null) + private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null): void { $definitions = []; $count = 0; @@ -480,6 +534,7 @@ class XmlFileLoader extends FileLoader $key = $arg->getAttribute('key'); } + $trim = $arg->hasAttribute('trim') && XmlUtils::phpize($arg->getAttribute('trim')); $onInvalid = $arg->getAttribute('on-invalid'); $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; if ('ignore' == $onInvalid) { @@ -490,7 +545,7 @@ class XmlFileLoader extends FileLoader $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } - switch ($arg->getAttribute('type')) { + switch ($type = $arg->getAttribute('type')) { case 'service': if ('' === $arg->getAttribute('id')) { throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file)); @@ -510,38 +565,49 @@ class XmlFileLoader extends FileLoader break; case 'iterator': $arg = $this->getArgumentsAsPhp($arg, $name, $file); - try { - $arguments[$key] = new IteratorArgument($arg); - } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file)); - } + $arguments[$key] = new IteratorArgument($arg); break; + case 'closure': case 'service_closure': - if ('' === $arg->getAttribute('id')) { - throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service_closure" has no or empty "id" attribute in "%s".', $name, $file)); + if ('' !== $arg->getAttribute('id')) { + $arg = new Reference($arg->getAttribute('id'), $invalidBehavior); + } else { + $arg = $this->getArgumentsAsPhp($arg, $name, $file); } - - $arguments[$key] = new ServiceClosureArgument(new Reference($arg->getAttribute('id'), $invalidBehavior)); + $arguments[$key] = match ($type) { + 'service_closure' => new ServiceClosureArgument($arg), + 'closure' => (new Definition('Closure')) + ->setFactory(['Closure', 'fromCallable']) + ->addArgument($arg), + }; break; case 'service_locator': $arg = $this->getArgumentsAsPhp($arg, $name, $file); - try { - $arguments[$key] = new ServiceLocatorArgument($arg); - } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service_locator" only accepts maps of type="service" references in "%s".', $name, $file)); + + if (isset($arg[0])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Skipping "key" argument or using integers as values in a "service_locator" tag is deprecated. The keys will default to the IDs of the original services in 7.0.'); } + + $arguments[$key] = new ServiceLocatorArgument($arg); break; case 'tagged': case 'tagged_iterator': case 'tagged_locator': - $type = $arg->getAttribute('type'); $forLocator = 'tagged_locator' === $type; if (!$arg->getAttribute('tag')) { throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file)); } - $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null); + $excludes = array_column($this->getChildren($arg, 'exclude'), 'nodeValue'); + if ($arg->hasAttribute('exclude')) { + if (\count($excludes) > 0) { + throw new InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $excludes = [$arg->getAttribute('exclude')]; + } + + $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes, !$arg->hasAttribute('exclude-self') || XmlUtils::phpize($arg->getAttribute('exclude-self'))); if ($forLocator) { $arguments[$key] = new ServiceLocatorArgument($arguments[$key]); @@ -557,13 +623,13 @@ class XmlFileLoader extends FileLoader $arguments[$key] = new AbstractArgument($arg->nodeValue); break; case 'string': - $arguments[$key] = $arg->nodeValue; + $arguments[$key] = $trim ? trim($arg->nodeValue) : $arg->nodeValue; break; case 'constant': $arguments[$key] = \constant(trim($arg->nodeValue)); break; default: - $arguments[$key] = XmlUtils::phpize($arg->nodeValue); + $arguments[$key] = XmlUtils::phpize($trim ? trim($arg->nodeValue) : $arg->nodeValue); } } @@ -587,14 +653,36 @@ class XmlFileLoader extends FileLoader return $children; } + private function getTagAttributes(\DOMNode $node, string $missingName): array + { + $parameters = []; + $children = $this->getChildren($node, 'attribute'); + + foreach ($children as $childNode) { + if ('' === $name = $childNode->getAttribute('name')) { + throw new InvalidArgumentException($missingName); + } + + if ($this->getChildren($childNode, 'attribute')) { + $parameters[$name] = $this->getTagAttributes($childNode, $missingName); + } else { + if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) { + $parameters[$normalizedName] = XmlUtils::phpize($childNode->nodeValue); + } + // keep not normalized key + $parameters[$name] = XmlUtils::phpize($childNode->nodeValue); + } + } + + return $parameters; + } + /** * Validates a documents XML schema. * - * @return bool - * * @throws RuntimeException When extension references a non-existent XSD file */ - public function validateSchema(\DOMDocument $dom) + public function validateSchema(\DOMDocument $dom): bool { $schemaLocations = ['http://symfony.com/schema/dic/services' => str_replace('\\', '/', __DIR__.'/schema/dic/services/services-1.0.xsd')]; @@ -671,11 +759,6 @@ EOF private function shouldEnableEntityLoader(): bool { - // Version prior to 8.0 can be enabled without deprecation - if (\PHP_VERSION_ID < 80000) { - return true; - } - static $dom, $schema; if (null === $dom) { $dom = new \DOMDocument(); @@ -699,7 +782,7 @@ EOF return !@$dom->schemaValidateSource($schema); } - private function validateAlias(\DOMElement $alias, string $file) + private function validateAlias(\DOMElement $alias, string $file): void { foreach ($alias->attributes as $name => $node) { if (!\in_array($name, ['alias', 'id', 'public'])) { @@ -711,7 +794,7 @@ EOF if (!$child instanceof \DOMElement || self::NS !== $child->namespaceURI) { continue; } - if (!\in_array($child->localName, ['deprecated'], true)) { + if ('deprecated' !== $child->localName) { throw new InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file)); } } @@ -722,7 +805,7 @@ EOF * * @throws InvalidArgumentException When no extension is found corresponding to a tag */ - private function validateExtensions(\DOMDocument $dom, string $file) + private function validateExtensions(\DOMDocument $dom, string $file): void { foreach ($dom->documentElement->childNodes as $node) { if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) { @@ -731,7 +814,7 @@ EOF // can it be handled by an extension? if (!$this->container->hasExtension($node->namespaceURI)) { - $extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getNamespace(); }, $this->container->getExtensions())); + $extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getNamespace(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? implode('", "', $extensionNamespaces) : 'none')); } } @@ -740,7 +823,7 @@ EOF /** * Loads from an extension. */ - private function loadFromExtensions(\DOMDocument $xml) + private function loadFromExtensions(\DOMDocument $xml): void { foreach ($xml->documentElement->childNodes as $node) { if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) { @@ -772,10 +855,8 @@ EOF * * The nested-tags are converted to keys (bar) * * @param \DOMElement $element A \DOMElement instance - * - * @return mixed */ - public static function convertDomElementToArray(\DOMElement $element) + public static function convertDomElementToArray(\DOMElement $element): mixed { return XmlUtils::convertDomElementToArray($element); } diff --git a/lib/symfony/dependency-injection/Loader/YamlFileLoader.php b/lib/symfony/dependency-injection/Loader/YamlFileLoader.php index 8756e89ed..822b45ef7 100644 --- a/lib/symfony/dependency-injection/Loader/YamlFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/YamlFileLoader.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Reference; @@ -63,6 +64,7 @@ class YamlFileLoader extends FileLoader 'autowire' => 'autowire', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind', + 'constructor' => 'constructor', ]; private const PROTOTYPE_KEYWORDS = [ @@ -84,6 +86,7 @@ class YamlFileLoader extends FileLoader 'autowire' => 'autowire', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind', + 'constructor' => 'constructor', ]; private const INSTANCEOF_KEYWORDS = [ @@ -96,6 +99,7 @@ class YamlFileLoader extends FileLoader 'tags' => 'tags', 'autowire' => 'autowire', 'bind' => 'bind', + 'constructor' => 'constructor', ]; private const DEFAULTS_KEYWORDS = [ @@ -106,17 +110,14 @@ class YamlFileLoader extends FileLoader 'bind' => 'bind', ]; - private $yamlParser; + private YamlParser $yamlParser; - private $anonymousServicesCount; - private $anonymousServicesSuffix; + private int $anonymousServicesCount; + private string $anonymousServicesSuffix; protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null) + public function load(mixed $resource, string $type = null): mixed { $path = $this->locator->locate($resource); @@ -149,7 +150,7 @@ class YamlFileLoader extends FileLoader return null; } - private function loadContent(array $content, string $path) + private function loadContent(array $content, string $path): void { // imports $this->parseImports($content, $path); @@ -180,10 +181,7 @@ class YamlFileLoader extends FileLoader } } - /** - * {@inheritdoc} - */ - public function supports($resource, string $type = null) + public function supports(mixed $resource, string $type = null): bool { if (!\is_string($resource)) { return false; @@ -196,7 +194,7 @@ class YamlFileLoader extends FileLoader return \in_array($type, ['yaml', 'yml'], true); } - private function parseImports(array $content, string $file) + private function parseImports(array $content, string $file): void { if (!isset($content['imports'])) { return; @@ -220,7 +218,7 @@ class YamlFileLoader extends FileLoader } } - private function parseDefinitions(array $content, string $file, bool $trackBindings = true) + private function parseDefinitions(array $content, string $file, bool $trackBindings = true): void { if (!isset($content['services'])) { return; @@ -303,11 +301,7 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in "%s".', $file)); } - foreach ($tag as $attribute => $value) { - if (!\is_scalar($value) && null !== $value) { - throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, $attribute, $file)); - } - } + $this->validateAttributes(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, '%s', $file), $tag); } } @@ -336,13 +330,9 @@ class YamlFileLoader extends FileLoader } /** - * Parses a definition. - * - * @param array|string|null $service - * * @throws InvalidArgumentException When tags are invalid */ - private function parseDefinition(string $id, $service, string $file, array $defaults, bool $return = false, bool $trackBindings = true) + private function parseDefinition(string $id, array|string|null $service, string $file, array $defaults, bool $return = false, bool $trackBindings = true): Definition|Alias|null { if (preg_match('/^_[a-zA-Z0-9_]*$/', $id)) { throw new InvalidArgumentException(sprintf('Service names that start with an underscore are reserved. Rename the "%s" service or define it in XML instead.', $id)); @@ -362,11 +352,7 @@ class YamlFileLoader extends FileLoader $service = ['arguments' => $service]; } - if (null === $service) { - $service = []; - } - - if (!\is_array($service)) { + if (!\is_array($service ??= [])) { throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but "%s" found for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file)); } @@ -414,6 +400,22 @@ class YamlFileLoader extends FileLoader $definition = isset($service[0]) && $service[0] instanceof Definition ? array_shift($service) : null; $return = null === $definition ? $return : true; + if (isset($service['from_callable'])) { + foreach (['alias', 'parent', 'synthetic', 'factory', 'file', 'arguments', 'properties', 'configurator', 'calls'] as $key) { + if (isset($service['factory'])) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for the service "%s" when using "from_callable" in "%s".', $key, $id, $file)); + } + } + + if ('Closure' !== $service['class'] ??= 'Closure') { + $service['lazy'] = true; + } + + $service['factory'] = ['Closure', 'fromCallable']; + $service['arguments'] = [$service['from_callable']]; + unset($service['from_callable']); + } + $this->checkDefinition($id, $service, $file); if (isset($service['alias'])) { @@ -434,11 +436,11 @@ class YamlFileLoader extends FileLoader $deprecation = \is_array($value) ? $value : ['message' => $value]; if (!isset($deprecation['package'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); + throw new InvalidArgumentException(sprintf('Missing attribute "package" of the "deprecated" option in "%s".', $file)); } if (!isset($deprecation['version'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); + throw new InvalidArgumentException(sprintf('Missing attribute "version" of the "deprecated" option in "%s".', $file)); } $alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); @@ -505,11 +507,11 @@ class YamlFileLoader extends FileLoader $deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']]; if (!isset($deprecation['package'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); + throw new InvalidArgumentException(sprintf('Missing attribute "package" of the "deprecated" option in "%s".', $file)); } if (!isset($deprecation['version'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); + throw new InvalidArgumentException(sprintf('Missing attribute "version" of the "deprecated" option in "%s".', $file)); } $definition->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); @@ -519,6 +521,14 @@ class YamlFileLoader extends FileLoader $definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file)); } + if (isset($service['constructor'])) { + if (null !== $definition->getFactory()) { + throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $id)); + } + + $definition->setFactory([null, $service['constructor']]); + } + if (isset($service['file'])) { $definition->setFile($service['file']); } @@ -615,11 +625,7 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file)); } - foreach ($tag as $attribute => $value) { - if (!\is_scalar($value) && null !== $value) { - throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, $attribute, $file)); - } - } + $this->validateAttributes(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, '%s', $file), $tag); $definition->addTag($name, $tag); } @@ -696,24 +702,31 @@ class YamlFileLoader extends FileLoader } $exclude = $service['exclude'] ?? null; $namespace = $service['namespace'] ?? $id; - $this->registerClasses($definition, $namespace, $service['resource'], $exclude); + $this->registerClasses($definition, $namespace, $service['resource'], $exclude, $file); } else { $this->setDefinition($id, $definition); } + + return null; } /** - * Parses a callable. - * - * @param string|array $callable A callable reference - * * @throws InvalidArgumentException When errors occur - * - * @return string|array|Reference */ - private function parseCallable($callable, string $parameter, string $id, string $file) + private function parseCallable(mixed $callable, string $parameter, string $id, string $file): string|array|Reference { if (\is_string($callable)) { + if (str_starts_with($callable, '@=')) { + if ('factory' !== $parameter) { + throw new InvalidArgumentException(sprintf('Using expressions in "%s" for the "%s" service is not supported in "%s".', $parameter, $id, $file)); + } + if (!class_exists(Expression::class)) { + throw new \LogicException('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + + return $callable; + } + if ('' !== $callable && '@' === $callable[0]) { if (!str_contains($callable, ':')) { return [$this->resolveServices($callable, $file), '__invoke']; @@ -743,14 +756,12 @@ class YamlFileLoader extends FileLoader /** * Loads a YAML file. * - * @return array|null - * * @throws InvalidArgumentException when the given file is not a local file or when it does not exist */ - protected function loadFile(string $file) + protected function loadFile(string $file): ?array { if (!class_exists(\Symfony\Component\Yaml\Parser::class)) { - throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.'); + throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed. Try running "composer require symfony/yaml".'); } if (!stream_is_local($file)) { @@ -761,9 +772,7 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('The file "%s" does not exist.', $file)); } - if (null === $this->yamlParser) { - $this->yamlParser = new YamlParser(); - } + $this->yamlParser ??= new YamlParser(); try { $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS); @@ -779,7 +788,7 @@ class YamlFileLoader extends FileLoader * * @throws InvalidArgumentException When service file is not valid */ - private function validate($content, string $file): ?array + private function validate(mixed $content, string $file): ?array { if (null === $content) { return $content; @@ -790,12 +799,12 @@ class YamlFileLoader extends FileLoader } foreach ($content as $namespace => $data) { - if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || str_starts_with($namespace, 'when@')) { continue; } if (!$this->container->hasExtension($namespace)) { - $extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none')); } } @@ -803,31 +812,29 @@ class YamlFileLoader extends FileLoader return $content; } - /** - * @return mixed - */ - private function resolveServices($value, string $file, bool $isParameter = false) + private function resolveServices(mixed $value, string $file, bool $isParameter = false): mixed { if ($value instanceof TaggedValue) { $argument = $value->getValue(); + + if ('closure' === $value->getTag()) { + $argument = $this->resolveServices($argument, $file, $isParameter); + + return (new Definition('Closure')) + ->setFactory(['Closure', 'fromCallable']) + ->addArgument($argument); + } if ('iterator' === $value->getTag()) { if (!\is_array($argument)) { throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts sequences in "%s".', $file)); } $argument = $this->resolveServices($argument, $file, $isParameter); - try { - return new IteratorArgument($argument); - } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file)); - } + + return new IteratorArgument($argument); } if ('service_closure' === $value->getTag()) { $argument = $this->resolveServices($argument, $file, $isParameter); - if (!$argument instanceof Reference) { - throw new InvalidArgumentException(sprintf('"!service_closure" tag only accepts service references in "%s".', $file)); - } - return new ServiceClosureArgument($argument); } if ('service_locator' === $value->getTag()) { @@ -837,21 +844,21 @@ class YamlFileLoader extends FileLoader $argument = $this->resolveServices($argument, $file, $isParameter); - try { - return new ServiceLocatorArgument($argument); - } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file)); + if (isset($argument[0])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Using integers as keys in a "!service_locator" tag is deprecated. The keys will default to the IDs of the original services in 7.0.'); } + + return new ServiceLocatorArgument($argument); } if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], true)) { $forLocator = 'tagged_locator' === $value->getTag(); if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) { - if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method', 'default_priority_method'])) { - throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by", "default_index_method", and "default_priority_method".', $value->getTag(), implode('", "', $diff))); + if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude', 'exclude_self'])) { + throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "%s".', $value->getTag(), implode('", "', $diff), implode('", "', $supportedKeys))); } - $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null); + $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null), $argument['exclude_self'] ?? true); } elseif (\is_string($argument) && $argument) { $argument = new TaggedIteratorArgument($argument, null, null, $forLocator); } else { @@ -932,10 +939,10 @@ class YamlFileLoader extends FileLoader return $value; } - private function loadFromExtensions(array $content) + private function loadFromExtensions(array $content): void { foreach ($content as $namespace => $values) { - if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || str_starts_with($namespace, 'when@')) { continue; } @@ -947,7 +954,7 @@ class YamlFileLoader extends FileLoader } } - private function checkDefinition(string $id, array $definition, string $file) + private function checkDefinition(string $id, array $definition, string $file): void { if ($this->isLoadingInstanceof) { $keywords = self::INSTANCEOF_KEYWORDS; @@ -963,4 +970,16 @@ class YamlFileLoader extends FileLoader } } } + + private function validateAttributes(string $message, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($message, $value, [...$path, $name]); + } elseif (!\is_scalar($value ?? '')) { + $name = implode('.', [...$path, $name]); + throw new InvalidArgumentException(sprintf($message, $name)); + } + } + } } diff --git a/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd b/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd index 3c3000254..c071e3466 100644 --- a/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd +++ b/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd @@ -115,6 +115,17 @@ + + + + + + + + + + + - + + @@ -157,6 +169,7 @@ + @@ -173,13 +186,14 @@ + - + @@ -197,6 +211,7 @@ + @@ -208,24 +223,29 @@ - - - - - - + + + + + - - - + + + + + + + + + @@ -242,6 +262,7 @@ + @@ -259,7 +280,7 @@ - + @@ -270,10 +291,11 @@ - + - + + @@ -284,6 +306,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -313,6 +355,7 @@ + diff --git a/lib/symfony/dependency-injection/Parameter.php b/lib/symfony/dependency-injection/Parameter.php index e182e1240..90dcc9204 100644 --- a/lib/symfony/dependency-injection/Parameter.php +++ b/lib/symfony/dependency-injection/Parameter.php @@ -18,17 +18,14 @@ namespace Symfony\Component\DependencyInjection; */ class Parameter { - private $id; + private string $id; public function __construct(string $id) { $this->id = $id; } - /** - * @return string - */ - public function __toString() + public function __toString(): string { return $this->id; } diff --git a/lib/symfony/dependency-injection/ParameterBag/ContainerBag.php b/lib/symfony/dependency-injection/ParameterBag/ContainerBag.php index 54aaa556b..7aa5ff80a 100644 --- a/lib/symfony/dependency-injection/ParameterBag/ContainerBag.php +++ b/lib/symfony/dependency-injection/ParameterBag/ContainerBag.php @@ -18,37 +18,24 @@ use Symfony\Component\DependencyInjection\Container; */ class ContainerBag extends FrozenParameterBag implements ContainerBagInterface { - private $container; + private Container $container; public function __construct(Container $container) { $this->container = $container; } - /** - * {@inheritdoc} - */ - public function all() + public function all(): array { return $this->container->getParameterBag()->all(); } - /** - * {@inheritdoc} - * - * @return array|bool|string|int|float|\UnitEnum|null - */ - public function get(string $name) + public function get(string $name): array|bool|string|int|float|\UnitEnum|null { return $this->container->getParameter($name); } - /** - * {@inheritdoc} - * - * @return bool - */ - public function has(string $name) + public function has(string $name): bool { return $this->container->hasParameter($name); } diff --git a/lib/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php b/lib/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php index f8380ac97..eeff6538c 100644 --- a/lib/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php +++ b/lib/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php @@ -23,35 +23,31 @@ interface ContainerBagInterface extends ContainerInterface { /** * Gets the service container parameters. - * - * @return array */ - public function all(); + public function all(): array; /** * Replaces parameter placeholders (%name%) by their values. * - * @param mixed $value A value + * @template TValue of array|scalar + * + * @param TValue $value + * + * @return mixed + * + * @psalm-return (TValue is scalar ? array|scalar : array) * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist */ - public function resolveValue($value); + public function resolveValue(mixed $value); /** * Escape parameter placeholders %. - * - * @param mixed $value - * - * @return mixed */ - public function escapeValue($value); + public function escapeValue(mixed $value): mixed; /** * Unescape parameter placeholders %. - * - * @param mixed $value - * - * @return mixed */ - public function unescapeValue($value); + public function unescapeValue(mixed $value): mixed; } diff --git a/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php b/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php index 67b8aeeb1..4719d2126 100644 --- a/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php +++ b/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php @@ -19,17 +19,14 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ class EnvPlaceholderParameterBag extends ParameterBag { - private $envPlaceholderUniquePrefix; - private $envPlaceholders = []; - private $unusedEnvPlaceholders = []; - private $providedTypes = []; + private string $envPlaceholderUniquePrefix; + private array $envPlaceholders = []; + private array $unusedEnvPlaceholders = []; + private array $providedTypes = []; - private static $counter = 0; + private static int $counter = 0; - /** - * {@inheritdoc} - */ - public function get(string $name) + public function get(string $name): array|bool|string|int|float|\UnitEnum|null { if (str_starts_with($name, 'env(') && str_ends_with($name, ')') && 'env()' !== $name) { $env = substr($name, 4, -1); @@ -44,15 +41,15 @@ class EnvPlaceholderParameterBag extends ParameterBag return $placeholder; // return first result } } - if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $env)) { + if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w*+$/', $env)) { throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name)); } if ($this->has($name) && null !== ($defaultValue = parent::get($name)) && !\is_string($defaultValue)) { throw new RuntimeException(sprintf('The default value of an env() parameter must be a string or null, but "%s" given to "%s".', get_debug_type($defaultValue), $name)); } - $uniqueName = md5($name.'_'.self::$counter++); - $placeholder = sprintf('%s_%s_%s', $this->getEnvPlaceholderUniquePrefix(), strtr($env, ':-.', '___'), $uniqueName); + $uniqueName = hash('xxh128', $name.'_'.self::$counter++); + $placeholder = sprintf('%s_%s_%s', $this->getEnvPlaceholderUniquePrefix(), strtr($env, ':-.\\', '____'), $uniqueName); $this->envPlaceholders[$env][$placeholder] = $placeholder; return $placeholder; @@ -66,10 +63,10 @@ class EnvPlaceholderParameterBag extends ParameterBag */ public function getEnvPlaceholderUniquePrefix(): string { - if (null === $this->envPlaceholderUniquePrefix) { + if (!isset($this->envPlaceholderUniquePrefix)) { $reproducibleEntropy = unserialize(serialize($this->parameters)); array_walk_recursive($reproducibleEntropy, function (&$v) { $v = null; }); - $this->envPlaceholderUniquePrefix = 'env_'.substr(md5(serialize($reproducibleEntropy)), -16); + $this->envPlaceholderUniquePrefix = 'env_'.substr(hash('xxh128', serialize($reproducibleEntropy)), -16); } return $this->envPlaceholderUniquePrefix; @@ -80,7 +77,7 @@ class EnvPlaceholderParameterBag extends ParameterBag * * @return string[][] A map of env var names to their placeholders */ - public function getEnvPlaceholders() + public function getEnvPlaceholders(): array { return $this->envPlaceholders; } @@ -90,6 +87,9 @@ class EnvPlaceholderParameterBag extends ParameterBag return $this->unusedEnvPlaceholders; } + /** + * @return void + */ public function clearUnusedEnvPlaceholders() { $this->unusedEnvPlaceholders = []; @@ -97,6 +97,8 @@ class EnvPlaceholderParameterBag extends ParameterBag /** * Merges the env placeholders of another EnvPlaceholderParameterBag. + * + * @return void */ public function mergeEnvPlaceholders(self $bag) { @@ -119,6 +121,8 @@ class EnvPlaceholderParameterBag extends ParameterBag /** * Maps env prefixes to their corresponding PHP types. + * + * @return void */ public function setProvidedTypes(array $providedTypes) { @@ -130,13 +134,13 @@ class EnvPlaceholderParameterBag extends ParameterBag * * @return string[][] */ - public function getProvidedTypes() + public function getProvidedTypes(): array { return $this->providedTypes; } /** - * {@inheritdoc} + * @return void */ public function resolve() { diff --git a/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php b/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php index 5a4aaf8b2..1ede09038 100644 --- a/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php +++ b/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php @@ -25,17 +25,17 @@ class FrozenParameterBag extends ParameterBag * all keys are already lowercased. * * This is always the case when used internally. - * - * @param array $parameters An array of parameters */ - public function __construct(array $parameters = []) - { + public function __construct( + array $parameters = [], + protected array $deprecatedParameters = [], + ) { $this->parameters = $parameters; $this->resolved = true; } /** - * {@inheritdoc} + * @return never */ public function clear() { @@ -43,7 +43,7 @@ class FrozenParameterBag extends ParameterBag } /** - * {@inheritdoc} + * @return never */ public function add(array $parameters) { @@ -51,15 +51,23 @@ class FrozenParameterBag extends ParameterBag } /** - * {@inheritdoc} + * @return never */ - public function set(string $name, $value) + public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value) { throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); } /** - * {@inheritdoc} + * @return never + */ + public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.') + { + throw new LogicException('Impossible to call deprecate() on a frozen ParameterBag.'); + } + + /** + * @return never */ public function remove(string $name) { diff --git a/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php b/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php index b1bf77568..c1cd9087f 100644 --- a/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php +++ b/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php @@ -24,6 +24,7 @@ class ParameterBag implements ParameterBagInterface { protected $parameters = []; protected $resolved = false; + protected array $deprecatedParameters = []; public function __construct(array $parameters = []) { @@ -31,7 +32,7 @@ class ParameterBag implements ParameterBagInterface } /** - * {@inheritdoc} + * @return void */ public function clear() { @@ -39,7 +40,7 @@ class ParameterBag implements ParameterBagInterface } /** - * {@inheritdoc} + * @return void */ public function add(array $parameters) { @@ -48,18 +49,17 @@ class ParameterBag implements ParameterBagInterface } } - /** - * {@inheritdoc} - */ - public function all() + public function all(): array { return $this->parameters; } - /** - * {@inheritdoc} - */ - public function get(string $name) + public function allDeprecated(): array + { + return $this->deprecatedParameters; + } + + public function get(string $name): array|bool|string|int|float|\UnitEnum|null { if (!\array_key_exists($name, $this->parameters)) { if (!$name) { @@ -93,35 +93,58 @@ class ParameterBag implements ParameterBagInterface throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative); } + if (isset($this->deprecatedParameters[$name])) { + trigger_deprecation(...$this->deprecatedParameters[$name]); + } + return $this->parameters[$name]; } /** - * {@inheritdoc} + * @return void */ - public function set(string $name, $value) + public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value) { + if (is_numeric($name)) { + trigger_deprecation('symfony/dependency-injection', '6.2', sprintf('Using numeric parameter name "%s" is deprecated and will throw as of 7.0.', $name)); + // uncomment the following line in 7.0 + // throw new InvalidArgumentException(sprintf('The parameter name "%s" cannot be numeric.', $name)); + } + $this->parameters[$name] = $value; } /** - * {@inheritdoc} + * Deprecates a service container parameter. + * + * @return void + * + * @throws ParameterNotFoundException if the parameter is not defined */ - public function has(string $name) + public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.') + { + if (!\array_key_exists($name, $this->parameters)) { + throw new ParameterNotFoundException($name); + } + + $this->deprecatedParameters[$name] = [$package, $version, $message, $name]; + } + + public function has(string $name): bool { return \array_key_exists($name, $this->parameters); } /** - * {@inheritdoc} + * @return void */ public function remove(string $name) { - unset($this->parameters[$name]); + unset($this->parameters[$name], $this->deprecatedParameters[$name]); } /** - * {@inheritdoc} + * @return void */ public function resolve() { @@ -148,27 +171,34 @@ class ParameterBag implements ParameterBagInterface /** * Replaces parameter placeholders (%name%) by their values. * - * @param mixed $value A value - * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * @template TValue of array|scalar * - * @return mixed + * @param TValue $value + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * + * @psalm-return (TValue is scalar ? array|scalar : array) * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist * @throws ParameterCircularReferenceException if a circular reference if detected * @throws RuntimeException when a given parameter has a type problem */ - public function resolveValue($value, array $resolving = []) + public function resolveValue(mixed $value, array $resolving = []): mixed { if (\is_array($value)) { $args = []; - foreach ($value as $k => $v) { - $args[\is_string($k) ? $this->resolveValue($k, $resolving) : $k] = $this->resolveValue($v, $resolving); + foreach ($value as $key => $v) { + $resolvedKey = \is_string($key) ? $this->resolveValue($key, $resolving) : $key; + if (!\is_scalar($resolvedKey) && !$resolvedKey instanceof \Stringable) { + throw new RuntimeException(sprintf('Array keys must be a scalar-value, but found key "%s" to resolve to type "%s".', $key, get_debug_type($resolvedKey))); + } + + $args[$resolvedKey] = $this->resolveValue($v, $resolving); } return $args; } - if (!\is_string($value) || 2 > \strlen($value)) { + if (!\is_string($value) || '' === $value || !str_contains($value, '%')) { return $value; } @@ -180,13 +210,11 @@ class ParameterBag implements ParameterBagInterface * * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) * - * @return mixed - * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist * @throws ParameterCircularReferenceException if a circular reference if detected * @throws RuntimeException when a given parameter has a type problem */ - public function resolveString(string $value, array $resolving = []) + public function resolveString(string $value, array $resolving = []): mixed { // we do this to deal with non string values (Boolean, integer, ...) // as the preg_replace_callback throw an exception when trying @@ -227,15 +255,15 @@ class ParameterBag implements ParameterBagInterface }, $value); } + /** + * @return bool + */ public function isResolved() { return $this->resolved; } - /** - * {@inheritdoc} - */ - public function escapeValue($value) + public function escapeValue(mixed $value): mixed { if (\is_string($value)) { return str_replace('%', '%%', $value); @@ -253,10 +281,7 @@ class ParameterBag implements ParameterBagInterface return $value; } - /** - * {@inheritdoc} - */ - public function unescapeValue($value) + public function unescapeValue(mixed $value): mixed { if (\is_string($value)) { return str_replace('%%', '%', $value); diff --git a/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php b/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php index 808a0fa42..18ddfde14 100644 --- a/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php +++ b/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php @@ -24,6 +24,8 @@ interface ParameterBagInterface /** * Clears all parameters. * + * @return void + * * @throws LogicException if the ParameterBagInterface cannot be cleared */ public function clear(); @@ -31,76 +33,68 @@ interface ParameterBagInterface /** * Adds parameters to the service container parameters. * + * @return void + * * @throws LogicException if the parameter cannot be added */ public function add(array $parameters); /** * Gets the service container parameters. - * - * @return array */ - public function all(); + public function all(): array; /** * Gets a service container parameter. * - * @return array|bool|string|int|float|\UnitEnum|null - * * @throws ParameterNotFoundException if the parameter is not defined */ - public function get(string $name); + public function get(string $name): array|bool|string|int|float|\UnitEnum|null; /** * Removes a parameter. + * + * @return void */ public function remove(string $name); /** * Sets a service container parameter. * - * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value + * @return void * * @throws LogicException if the parameter cannot be set */ - public function set(string $name, $value); + public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value); /** * Returns true if a parameter name is defined. - * - * @return bool */ - public function has(string $name); + public function has(string $name): bool; /** * Replaces parameter placeholders (%name%) by their values for all parameters. + * + * @return void */ public function resolve(); /** * Replaces parameter placeholders (%name%) by their values. * - * @param mixed $value A value + * @return mixed * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist */ - public function resolveValue($value); + public function resolveValue(mixed $value); /** * Escape parameter placeholders %. - * - * @param mixed $value - * - * @return mixed */ - public function escapeValue($value); + public function escapeValue(mixed $value): mixed; /** * Unescape parameter placeholders %. - * - * @param mixed $value - * - * @return mixed */ - public function unescapeValue($value); + public function unescapeValue(mixed $value): mixed; } diff --git a/lib/symfony/dependency-injection/Reference.php b/lib/symfony/dependency-injection/Reference.php index 7f7b32cc6..2a89dda56 100644 --- a/lib/symfony/dependency-injection/Reference.php +++ b/lib/symfony/dependency-injection/Reference.php @@ -18,8 +18,8 @@ namespace Symfony\Component\DependencyInjection; */ class Reference { - private $id; - private $invalidBehavior; + private string $id; + private int $invalidBehavior; public function __construct(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { @@ -27,20 +27,15 @@ class Reference $this->invalidBehavior = $invalidBehavior; } - /** - * @return string - */ - public function __toString() + public function __toString(): string { return $this->id; } /** * Returns the behavior to be used when the service does not exist. - * - * @return int */ - public function getInvalidBehavior() + public function getInvalidBehavior(): int { return $this->invalidBehavior; } diff --git a/lib/symfony/dependency-injection/ReverseContainer.php b/lib/symfony/dependency-injection/ReverseContainer.php index 280e9e2dd..22d1b35df 100644 --- a/lib/symfony/dependency-injection/ReverseContainer.php +++ b/lib/symfony/dependency-injection/ReverseContainer.php @@ -21,19 +21,17 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; */ final class ReverseContainer { - private $serviceContainer; - private $reversibleLocator; - private $tagName; - private $getServiceId; + private Container $serviceContainer; + private ContainerInterface $reversibleLocator; + private string $tagName; + private \Closure $getServiceId; public function __construct(Container $serviceContainer, ContainerInterface $reversibleLocator, string $tagName = 'container.reversible') { $this->serviceContainer = $serviceContainer; $this->reversibleLocator = $reversibleLocator; $this->tagName = $tagName; - $this->getServiceId = \Closure::bind(function (object $service): ?string { - return array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null; - }, $serviceContainer, Container::class); + $this->getServiceId = \Closure::bind(fn (object $service): ?string => array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null, $serviceContainer, Container::class); } /** @@ -63,10 +61,6 @@ final class ReverseContainer */ public function getService(string $id): object { - if ($this->serviceContainer->has($id)) { - return $this->serviceContainer->get($id); - } - if ($this->reversibleLocator->has($id)) { return $this->reversibleLocator->get($id); } @@ -75,7 +69,6 @@ final class ReverseContainer throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName)); } - // will throw a ServiceNotFoundException - $this->serviceContainer->get($id); + return $this->serviceContainer->get($id); } } diff --git a/lib/symfony/dependency-injection/ServiceLocator.php b/lib/symfony/dependency-injection/ServiceLocator.php index 4be0d6f72..f36bfe5cb 100644 --- a/lib/symfony/dependency-injection/ServiceLocator.php +++ b/lib/symfony/dependency-injection/ServiceLocator.php @@ -23,22 +23,21 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; /** * @author Robin Chalas * @author Nicolas Grekas + * + * @template-covariant T of mixed + * + * @implements ServiceProviderInterface */ -class ServiceLocator implements ServiceProviderInterface +class ServiceLocator implements ServiceProviderInterface, \Countable { use ServiceLocatorTrait { get as private doGet; } - private $externalId; - private $container; + private ?string $externalId = null; + private ?Container $container = null; - /** - * {@inheritdoc} - * - * @return mixed - */ - public function get(string $id) + public function get(string $id): mixed { if (!$this->externalId) { return $this->doGet($id); @@ -55,13 +54,15 @@ class ServiceLocator implements ServiceProviderInterface } $r = new \ReflectionProperty($e, 'message'); - $r->setAccessible(true); $r->setValue($e, $message); throw $e; } } + /** + * @return mixed + */ public function __invoke(string $id) { return isset($this->factories[$id]) ? $this->get($id) : null; @@ -69,10 +70,8 @@ class ServiceLocator implements ServiceProviderInterface /** * @internal - * - * @return static */ - public function withContext(string $externalId, Container $container): self + public function withContext(string $externalId, Container $container): static { $locator = clone $this; $locator->externalId = $externalId; @@ -81,6 +80,11 @@ class ServiceLocator implements ServiceProviderInterface return $locator; } + public function count(): int + { + return \count($this->getProvidedServices()); + } + private function createNotFoundException(string $id): NotFoundExceptionInterface { if ($this->loading) { @@ -90,7 +94,7 @@ class ServiceLocator implements ServiceProviderInterface } $class = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 4); - $class = isset($class[3]['object']) ? \get_class($class[3]['object']) : null; + $class = isset($class[3]['object']) ? $class[3]['object']::class : null; $externalId = $this->externalId ?: $class; $msg = []; diff --git a/lib/symfony/dependency-injection/TaggedContainerInterface.php b/lib/symfony/dependency-injection/TaggedContainerInterface.php index 25d5a098f..a108a996e 100644 --- a/lib/symfony/dependency-injection/TaggedContainerInterface.php +++ b/lib/symfony/dependency-injection/TaggedContainerInterface.php @@ -22,8 +22,6 @@ interface TaggedContainerInterface extends ContainerInterface * Returns service ids for a given tag. * * @param string $name The tag name - * - * @return array */ - public function findTaggedServiceIds(string $name); + public function findTaggedServiceIds(string $name): array; } diff --git a/lib/symfony/dependency-injection/TypedReference.php b/lib/symfony/dependency-injection/TypedReference.php index 4099a0059..9b431cd65 100644 --- a/lib/symfony/dependency-injection/TypedReference.php +++ b/lib/symfony/dependency-injection/TypedReference.php @@ -18,22 +18,28 @@ namespace Symfony\Component\DependencyInjection; */ class TypedReference extends Reference { - private $type; - private $name; + private string $type; + private ?string $name; + private array $attributes; /** * @param string $id The service identifier * @param string $type The PHP type of the identified service * @param int $invalidBehavior The behavior when the service does not exist * @param string|null $name The name of the argument targeting the service + * @param array $attributes The attributes to be used */ - public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, string $name = null) + public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, string $name = null, array $attributes = []) { $this->name = $type === $id ? $name : null; parent::__construct($id, $invalidBehavior); $this->type = $type; + $this->attributes = $attributes; } + /** + * @return string + */ public function getType() { return $this->type; @@ -43,4 +49,9 @@ class TypedReference extends Reference { return $this->name; } + + public function getAttributes(): array + { + return $this->attributes; + } } diff --git a/lib/symfony/dependency-injection/Variable.php b/lib/symfony/dependency-injection/Variable.php index 21d33ebb2..bb275cef6 100644 --- a/lib/symfony/dependency-injection/Variable.php +++ b/lib/symfony/dependency-injection/Variable.php @@ -26,17 +26,14 @@ namespace Symfony\Component\DependencyInjection; */ class Variable { - private $name; + private string $name; public function __construct(string $name) { $this->name = $name; } - /** - * @return string - */ - public function __toString() + public function __toString(): string { return $this->name; } diff --git a/lib/symfony/dependency-injection/composer.json b/lib/symfony/dependency-injection/composer.json index cb891c790..dc4a9feaf 100644 --- a/lib/symfony/dependency-injection/composer.json +++ b/lib/symfony/dependency-injection/composer.json @@ -16,35 +16,27 @@ } ], "require": { - "php": ">=7.2.5", - "psr/container": "^1.1.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/service-contracts": "^1.1.6|^2" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" }, "require-dev": { - "symfony/yaml": "^4.4.26|^5.0|^6.0", - "symfony/config": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/yaml": "", - "symfony/config": "", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" + "symfony/yaml": "^5.4|^6.0|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<5.3", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4.26" + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" }, "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0|2.0" + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "autoload": { "psr-4": { "Symfony\\Component\\DependencyInjection\\": "" }, diff --git a/lib/symfony/deprecation-contracts/.gitignore b/lib/symfony/deprecation-contracts/.gitignore deleted file mode 100644 index c49a5d8df..000000000 --- a/lib/symfony/deprecation-contracts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/deprecation-contracts/LICENSE b/lib/symfony/deprecation-contracts/LICENSE index 406242ff2..0ed3a2465 100644 --- a/lib/symfony/deprecation-contracts/LICENSE +++ b/lib/symfony/deprecation-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2020-2022 Fabien Potencier +Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/deprecation-contracts/README.md b/lib/symfony/deprecation-contracts/README.md index 4957933a6..9814864c0 100644 --- a/lib/symfony/deprecation-contracts/README.md +++ b/lib/symfony/deprecation-contracts/README.md @@ -22,5 +22,5 @@ trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use This will generate the following message: `Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` -While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +While not recommended, the deprecation notices can be completely ignored by declaring an empty `function trigger_deprecation() {}` in your application. diff --git a/lib/symfony/deprecation-contracts/composer.json b/lib/symfony/deprecation-contracts/composer.json index cc7cc1237..c6d02d874 100644 --- a/lib/symfony/deprecation-contracts/composer.json +++ b/lib/symfony/deprecation-contracts/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.1" + "php": ">=8.1" }, "autoload": { "files": [ @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/lib/symfony/deprecation-contracts/function.php b/lib/symfony/deprecation-contracts/function.php index d4371504a..2d56512ba 100644 --- a/lib/symfony/deprecation-contracts/function.php +++ b/lib/symfony/deprecation-contracts/function.php @@ -20,7 +20,7 @@ if (!function_exists('trigger_deprecation')) { * * @author Nicolas Grekas */ - function trigger_deprecation(string $package, string $version, string $message, ...$args): void + function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void { @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); } diff --git a/lib/symfony/dotenv/CHANGELOG.md b/lib/symfony/dotenv/CHANGELOG.md index 3cf07eecc..f3b7b7cf1 100644 --- a/lib/symfony/dotenv/CHANGELOG.md +++ b/lib/symfony/dotenv/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Add a new `filter` argument to `debug:dotenv` command to filter variable names + 5.4 --- diff --git a/lib/symfony/dotenv/Command/DebugCommand.php b/lib/symfony/dotenv/Command/DebugCommand.php index 8ceb1fd48..e60c83fae 100644 --- a/lib/symfony/dotenv/Command/DebugCommand.php +++ b/lib/symfony/dotenv/Command/DebugCommand.php @@ -11,7 +11,11 @@ namespace Symfony\Component\Dotenv\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -22,13 +26,21 @@ use Symfony\Component\Dotenv\Dotenv; * * @author Christopher Hertel */ +#[AsCommand(name: 'debug:dotenv', description: 'List all dotenv files with variables and values')] final class DebugCommand extends Command { + /** + * @deprecated since Symfony 6.1 + */ protected static $defaultName = 'debug:dotenv'; - protected static $defaultDescription = 'Lists all dotenv files with variables and values'; - private $kernelEnvironment; - private $projectDirectory; + /** + * @deprecated since Symfony 6.1 + */ + protected static $defaultDescription = 'List all dotenv files with variables and values'; + + private string $kernelEnvironment; + private string $projectDirectory; public function __construct(string $kernelEnvironment, string $projectDirectory) { @@ -38,6 +50,25 @@ final class DebugCommand extends Command parent::__construct(); } + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('filter', InputArgument::OPTIONAL, 'The name of an environment variable or a filter.', null, $this->getAvailableVars(...)), + ]) + ->setHelp(<<<'EOT' +The %command.full_name% command displays all the environment variables configured by dotenv: + + php %command.full_name% + +To get specific variables, specify its full or partial name: + + php %command.full_name% FOO_BAR + +EOT + ); + } + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -50,9 +81,7 @@ final class DebugCommand extends Command } $envFiles = $this->getEnvFiles(); - $availableFiles = array_filter($envFiles, function (string $file) { - return is_file($this->getFilePath($file)); - }); + $availableFiles = array_filter($envFiles, fn (string $file) => is_file($this->getFilePath($file))); if (\in_array('.env.local.php', $availableFiles, true)) { $io->warning('Due to existing dump file (.env.local.php) all other dotenv files are skipped.'); @@ -63,35 +92,51 @@ final class DebugCommand extends Command } $io->section('Scanned Files (in descending priority)'); - $io->listing(array_map(static function (string $envFile) use ($availableFiles) { - return \in_array($envFile, $availableFiles, true) - ? sprintf('✓ %s', $envFile) - : sprintf('⨯ %s', $envFile); - }, $envFiles)); + $io->listing(array_map(static fn (string $envFile) => \in_array($envFile, $availableFiles, true) + ? sprintf('✓ %s', $envFile) + : sprintf('⨯ %s', $envFile), $envFiles)); + + $nameFilter = $input->getArgument('filter'); + $variables = $this->getVariables($availableFiles, $nameFilter); $io->section('Variables'); - $io->table( - array_merge(['Variable', 'Value'], $availableFiles), - $this->getVariables($availableFiles) - ); - $io->comment('Note real values might be different between web and CLI.'); + if ($variables || null === $nameFilter) { + $io->table( + array_merge(['Variable', 'Value'], $availableFiles), + $this->getVariables($availableFiles, $nameFilter) + ); + + $io->comment('Note that values might be different between web and CLI.'); + } else { + $io->warning(sprintf('No variables match the given filter "%s".', $nameFilter)); + } return 0; } - private function getVariables(array $envFiles): array + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { - $vars = explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ''); - sort($vars); + if ($input->mustSuggestArgumentValuesFor('filter')) { + $suggestions->suggestValues($this->getAvailableVars()); + } + } + + private function getVariables(array $envFiles, ?string $nameFilter): array + { + $vars = $this->getAvailableVars(); $output = []; $fileValues = []; foreach ($vars as $var) { + if (null !== $nameFilter && 0 !== stripos($var, $nameFilter)) { + continue; + } + $realValue = $_SERVER[$var]; $varDetails = [$var, $realValue]; foreach ($envFiles as $envFile) { - $values = $fileValues[$envFile] ?? $fileValues[$envFile] = $this->loadValues($envFile); + $values = $fileValues[$envFile] ??= $this->loadValues($envFile); $varString = $values[$var] ?? 'n/a'; $shortenedVar = $this->getHelper('formatter')->truncate($varString, 30); @@ -104,6 +149,14 @@ final class DebugCommand extends Command return $output; } + private function getAvailableVars(): array + { + $vars = explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ''); + sort($vars); + + return $vars; + } + private function getEnvFiles(): array { $files = [ diff --git a/lib/symfony/dotenv/Command/DotenvDumpCommand.php b/lib/symfony/dotenv/Command/DotenvDumpCommand.php index 44110543f..051f4b05a 100644 --- a/lib/symfony/dotenv/Command/DotenvDumpCommand.php +++ b/lib/symfony/dotenv/Command/DotenvDumpCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Dotenv\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -25,13 +26,11 @@ use Symfony\Component\Dotenv\Dotenv; * @internal */ #[Autoconfigure(bind: ['$projectDir' => '%kernel.project_dir%', '$defaultEnv' => '%kernel.environment%'])] +#[AsCommand(name: 'dotenv:dump', description: 'Compile .env files to .env.local.php')] final class DotenvDumpCommand extends Command { - protected static $defaultName = 'dotenv:dump'; - protected static $defaultDescription = 'Compiles .env files to .env.local.php'; - - private $projectDir; - private $defaultEnv; + private string $projectDir; + private ?string $defaultEnv; public function __construct(string $projectDir, string $defaultEnv = null) { @@ -41,10 +40,7 @@ final class DotenvDumpCommand extends Command parent::__construct(); } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -60,9 +56,6 @@ EOT ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $config = []; diff --git a/lib/symfony/dotenv/Dotenv.php b/lib/symfony/dotenv/Dotenv.php index 2c76d52c8..6e693ac28 100644 --- a/lib/symfony/dotenv/Dotenv.php +++ b/lib/symfony/dotenv/Dotenv.php @@ -29,28 +29,19 @@ final class Dotenv public const STATE_VARNAME = 0; public const STATE_VALUE = 1; - private $path; - private $cursor; - private $lineno; - private $data; - private $end; - private $values; - private $envKey; - private $debugKey; - private $prodEnvs = ['prod']; - private $usePutenv = false; + private string $path; + private int $cursor; + private int $lineno; + private string $data; + private int $end; + private array $values = []; + private string $envKey; + private string $debugKey; + private array $prodEnvs = ['prod']; + private bool $usePutenv = false; - /** - * @param string $envKey - */ - public function __construct($envKey = 'APP_ENV', string $debugKey = 'APP_DEBUG') + public function __construct(string $envKey = 'APP_ENV', string $debugKey = 'APP_DEBUG') { - if (\in_array($envKey = (string) $envKey, ['1', ''], true)) { - trigger_deprecation('symfony/dotenv', '5.1', 'Passing a boolean to the constructor of "%s" is deprecated, use "Dotenv::usePutenv()".', __CLASS__); - $this->usePutenv = (bool) $envKey; - $envKey = 'APP_ENV'; - } - $this->envKey = $envKey; $this->debugKey = $debugKey; } @@ -58,7 +49,7 @@ final class Dotenv /** * @return $this */ - public function setProdEnvs(array $prodEnvs): self + public function setProdEnvs(array $prodEnvs): static { $this->prodEnvs = $prodEnvs; @@ -67,11 +58,11 @@ final class Dotenv /** * @param bool $usePutenv If `putenv()` should be used to define environment variables or not. - * Beware that `putenv()` is not thread safe, that's why this setting defaults to false + * Beware that `putenv()` is not thread safe, that's why it's not enabled by default * * @return $this */ - public function usePutenv(bool $usePutenv = true): self + public function usePutenv(bool $usePutenv = true): static { $this->usePutenv = $usePutenv; @@ -81,8 +72,8 @@ final class Dotenv /** * Loads one or several .env files. * - * @param string $path A file to load - * @param string[] ...$extraPaths A list of additional files to load + * @param string $path A file to load + * @param string ...$extraPaths A list of additional files to load * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable @@ -98,10 +89,11 @@ final class Dotenv * .env.local is always ignored in test env because tests should produce the same results for everyone. * .env.dist is loaded when it exists and .env is not found. * - * @param string $path A file to load - * @param string $envKey|null The name of the env vars that defines the app env - * @param string $defaultEnv The app env to use when none is defined - * @param array $testEnvs A list of app envs for which .env.local should be ignored + * @param string $path A file to load + * @param string|null $envKey The name of the env vars that defines the app env + * @param string $defaultEnv The app env to use when none is defined + * @param array $testEnvs A list of app envs for which .env.local should be ignored + * @param bool $overrideExistingVars Whether existing environment variables set by the system should be overridden * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable @@ -161,14 +153,14 @@ final class Dotenv $k = $this->debugKey; $debug = $_SERVER[$k] ?? !\in_array($_SERVER[$this->envKey], $this->prodEnvs, true); - $_SERVER[$k] = $_ENV[$k] = (int) $debug || (!\is_bool($debug) && filter_var($debug, \FILTER_VALIDATE_BOOLEAN)) ? '1' : '0'; + $_SERVER[$k] = $_ENV[$k] = (int) $debug || (!\is_bool($debug) && filter_var($debug, \FILTER_VALIDATE_BOOL)) ? '1' : '0'; } /** * Loads one or several .env files and enables override existing vars. * - * @param string $path A file to load - * @param string[] ...$extraPaths A list of additional files to load + * @param string $path A file to load + * @param string ...$extraPaths A list of additional files to load * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable @@ -182,7 +174,7 @@ final class Dotenv * Sets values as environment variables (via putenv, $_ENV, and $_SERVER). * * @param array $values An array of env variables - * @param bool $overrideExistingVars true when existing environment variables must be overridden + * @param bool $overrideExistingVars Whether existing environment variables set by the system should be overridden */ public function populate(array $values, bool $overrideExistingVars = false): void { @@ -190,7 +182,7 @@ final class Dotenv $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '')); foreach ($values as $name => $value) { - $notHttpName = 0 !== strpos($name, 'HTTP_'); + $notHttpName = !str_starts_with($name, 'HTTP_'); if (isset($_SERVER[$name]) && $notHttpName && !isset($_ENV[$name])) { $_ENV[$name] = $_SERVER[$name]; } @@ -268,8 +260,7 @@ final class Dotenv return $this->values; } finally { $this->values = []; - $this->data = null; - $this->path = null; + unset($this->path, $this->cursor, $this->lineno, $this->data, $this->end); } } @@ -314,7 +305,7 @@ final class Dotenv throw $this->createFormatException('Whitespace are not supported before the value'); } - $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ($_ENV['SYMFONY_DOTENV_VARS'] ?? ''))); + $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '')); unset($loadedVars['']); $v = ''; @@ -420,7 +411,7 @@ final class Dotenv return $value; } - private function skipEmptyLines() + private function skipEmptyLines(): void { if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) { $this->moveCursor($match[0]); @@ -429,7 +420,7 @@ final class Dotenv private function resolveCommands(string $value, array $loadedVars): string { - if (false === strpos($value, '$')) { + if (!str_contains($value, '$')) { return $value; } @@ -453,19 +444,14 @@ final class Dotenv } if (!class_exists(Process::class)) { - throw new \LogicException('Resolving commands requires the Symfony Process component.'); + throw new \LogicException('Resolving commands requires the Symfony Process component. Try running "composer require symfony/process".'); } - $process = method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline('echo '.$matches[0]) : new Process('echo '.$matches[0]); - - if (!method_exists(Process::class, 'fromShellCommandline') && method_exists(Process::class, 'inheritEnvironmentVariables')) { - // Symfony 3.4 does not inherit env vars by default: - $process->inheritEnvironmentVariables(); - } + $process = Process::fromShellCommandline('echo '.$matches[0]); $env = []; foreach ($this->values as $name => $value) { - if (isset($loadedVars[$name]) || (!isset($_ENV[$name]) && !(isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')))) { + if (isset($loadedVars[$name]) || (!isset($_ENV[$name]) && !(isset($_SERVER[$name]) && !str_starts_with($name, 'HTTP_')))) { $env[$name] = $value; } } @@ -473,7 +459,7 @@ final class Dotenv try { $process->mustRun(); - } catch (ProcessException $e) { + } catch (ProcessException) { throw $this->createFormatException(sprintf('Issue expanding a command (%s)', $process->getErrorOutput())); } @@ -483,7 +469,7 @@ final class Dotenv private function resolveVariables(string $value, array $loadedVars): string { - if (false === strpos($value, '$')) { + if (!str_contains($value, '$')) { return $value; } @@ -518,7 +504,7 @@ final class Dotenv $value = $this->values[$name]; } elseif (isset($_ENV[$name])) { $value = $_ENV[$name]; - } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) { + } elseif (isset($_SERVER[$name]) && !str_starts_with($name, 'HTTP_')) { $value = $_SERVER[$name]; } elseif (isset($this->values[$name])) { $value = $this->values[$name]; @@ -549,7 +535,7 @@ final class Dotenv return $value; } - private function moveCursor(string $text) + private function moveCursor(string $text): void { $this->cursor += \strlen($text); $this->lineno += substr_count($text, "\n"); diff --git a/lib/symfony/dotenv/Exception/FormatException.php b/lib/symfony/dotenv/Exception/FormatException.php index 3ac77e592..8f1aa84b2 100644 --- a/lib/symfony/dotenv/Exception/FormatException.php +++ b/lib/symfony/dotenv/Exception/FormatException.php @@ -18,7 +18,7 @@ namespace Symfony\Component\Dotenv\Exception; */ final class FormatException extends \LogicException implements ExceptionInterface { - private $context; + private FormatExceptionContext $context; public function __construct(string $message, FormatExceptionContext $context, int $code = 0, \Throwable $previous = null) { diff --git a/lib/symfony/dotenv/Exception/FormatExceptionContext.php b/lib/symfony/dotenv/Exception/FormatExceptionContext.php index 96d902fc8..11db3ab49 100644 --- a/lib/symfony/dotenv/Exception/FormatExceptionContext.php +++ b/lib/symfony/dotenv/Exception/FormatExceptionContext.php @@ -16,10 +16,10 @@ namespace Symfony\Component\Dotenv\Exception; */ final class FormatExceptionContext { - private $data; - private $path; - private $lineno; - private $cursor; + private string $data; + private string $path; + private int $lineno; + private int $cursor; public function __construct(string $data, string $path, int $lineno, int $cursor) { diff --git a/lib/symfony/dotenv/LICENSE b/lib/symfony/dotenv/LICENSE index f2345234a..0223acd4a 100644 --- a/lib/symfony/dotenv/LICENSE +++ b/lib/symfony/dotenv/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2023 Fabien Potencier +Copyright (c) 2016-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/dotenv/composer.json b/lib/symfony/dotenv/composer.json index bec034f87..2a65c08f5 100644 --- a/lib/symfony/dotenv/composer.json +++ b/lib/symfony/dotenv/composer.json @@ -16,12 +16,15 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1" }, "require-dev": { - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Dotenv\\": "" }, diff --git a/lib/symfony/error-handler/BufferingLogger.php b/lib/symfony/error-handler/BufferingLogger.php index cfd55c61f..b33e07996 100644 --- a/lib/symfony/error-handler/BufferingLogger.php +++ b/lib/symfony/error-handler/BufferingLogger.php @@ -20,7 +20,7 @@ use Psr\Log\AbstractLogger; */ class BufferingLogger extends AbstractLogger { - private $logs = []; + private array $logs = []; public function log($level, $message, array $context = []): void { @@ -35,14 +35,14 @@ class BufferingLogger extends AbstractLogger return $logs; } - /** - * @return array - */ - public function __sleep() + public function __sleep(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } + /** + * @return void + */ public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); @@ -51,21 +51,21 @@ class BufferingLogger extends AbstractLogger public function __destruct() { foreach ($this->logs as [$level, $message, $context]) { - if (false !== strpos($message, '{')) { + if (str_contains($message, '{')) { foreach ($context as $key => $val) { if (null === $val || \is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) { $message = str_replace("{{$key}}", $val, $message); } elseif ($val instanceof \DateTimeInterface) { - $message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message); + $message = str_replace("{{$key}}", $val->format(\DateTimeInterface::RFC3339), $message); } elseif (\is_object($val)) { - $message = str_replace("{{$key}}", '[object '.\get_class($val).']', $message); + $message = str_replace("{{$key}}", '[object '.get_debug_type($val).']', $message); } else { $message = str_replace("{{$key}}", '['.\gettype($val).']', $message); } } } - error_log(sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message)); + error_log(sprintf('%s [%s] %s', date(\DateTimeInterface::RFC3339), $level, $message)); } } } diff --git a/lib/symfony/error-handler/CHANGELOG.md b/lib/symfony/error-handler/CHANGELOG.md index 2976566a1..6a5e8fb60 100644 --- a/lib/symfony/error-handler/CHANGELOG.md +++ b/lib/symfony/error-handler/CHANGELOG.md @@ -1,6 +1,22 @@ CHANGELOG ========= +6.4 +--- + + * `FlattenExceptionNormalizer` no longer implements `ContextAwareNormalizerInterface` + +6.3 +--- + + * Display exception properties in the HTML error page + +6.1 +--- + + * Report overridden `@final` constants and properties + * Read environment variable `SYMFONY_IDE` to configure file link format + 5.4 --- diff --git a/lib/symfony/error-handler/Debug.php b/lib/symfony/error-handler/Debug.php index 343a35a77..d54a38c4c 100644 --- a/lib/symfony/error-handler/Debug.php +++ b/lib/symfony/error-handler/Debug.php @@ -22,16 +22,15 @@ class Debug { error_reporting(-1); - if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { ini_set('display_errors', 0); - } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || \ini_get('error_log')) { + } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOL) || \ini_get('error_log')) { // CLI - display errors only if they're not already logged to STDERR ini_set('display_errors', 1); } @ini_set('zend.assertions', 1); ini_set('assert.active', 1); - ini_set('assert.warning', 0); ini_set('assert.exception', 1); DebugClassLoader::enable(); diff --git a/lib/symfony/error-handler/DebugClassLoader.php b/lib/symfony/error-handler/DebugClassLoader.php index 6e4adeab0..16af2d063 100644 --- a/lib/symfony/error-handler/DebugClassLoader.php +++ b/lib/symfony/error-handler/DebugClassLoader.php @@ -21,6 +21,7 @@ use PHPUnit\Framework\MockObject\MockObject; use Prophecy\Prophecy\ProphecySubjectInterface; use ProxyManager\Proxy\ProxyInterface; use Symfony\Component\ErrorHandler\Internal\TentativeTypes; +use Symfony\Component\VarExporter\LazyObjectInterface; /** * Autoloader checking if the class is really defined in the file found. @@ -56,7 +57,7 @@ class DebugClassLoader 'null' => 'null', 'resource' => 'resource', 'boolean' => 'bool', - 'true' => 'bool', + 'true' => 'true', 'false' => 'false', 'integer' => 'int', 'array' => 'array', @@ -74,6 +75,7 @@ class DebugClassLoader '$this' => 'static', 'list' => 'array', 'class-string' => 'string', + 'never' => 'never', ]; private const BUILTIN_RETURN_TYPES = [ @@ -91,6 +93,9 @@ class DebugClassLoader 'parent' => true, 'mixed' => true, 'static' => true, + 'null' => true, + 'true' => true, + 'never' => true, ]; private const MAGIC_METHODS = [ @@ -101,34 +106,39 @@ class DebugClassLoader '__serialize' => 'array', ]; + /** + * @var callable + */ private $classLoader; - private $isFinder; - private $loaded = []; - private $patchTypes; + private bool $isFinder; + private array $loaded = []; + private array $patchTypes = []; - private static $caseCheck; - private static $checkedClasses = []; - private static $final = []; - private static $finalMethods = []; - private static $deprecated = []; - private static $internal = []; - private static $internalMethods = []; - private static $annotatedParameters = []; - private static $darwinCache = ['/' => ['/', []]]; - private static $method = []; - private static $returnTypes = []; - private static $methodTraits = []; - private static $fileOffsets = []; + private static int $caseCheck; + private static array $checkedClasses = []; + private static array $final = []; + private static array $finalMethods = []; + private static array $finalProperties = []; + private static array $finalConstants = []; + private static array $deprecated = []; + private static array $internal = []; + private static array $internalMethods = []; + private static array $annotatedParameters = []; + private static array $darwinCache = ['/' => ['/', []]]; + private static array $method = []; + private static array $returnTypes = []; + private static array $methodTraits = []; + private static array $fileOffsets = []; public function __construct(callable $classLoader) { $this->classLoader = $classLoader; $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile'); - parse_str(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes); + parse_str($_ENV['SYMFONY_PATCH_TYPE_DECLARATIONS'] ?? $_SERVER['SYMFONY_PATCH_TYPE_DECLARATIONS'] ?? getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes); $this->patchTypes += [ 'force' => null, 'php' => \PHP_MAJOR_VERSION.'.'.\PHP_MINOR_VERSION, - 'deprecations' => \PHP_VERSION_ID >= 70400, + 'deprecations' => true, ]; if ('phpdoc' === $this->patchTypes['force']) { @@ -146,7 +156,7 @@ class DebugClassLoader if (false === $test || false === $i) { // filesystem is case sensitive self::$caseCheck = 0; - } elseif (substr($test, -\strlen($file)) === $file) { + } elseif (str_ends_with($test, $file)) { // filesystem is case insensitive and realpath() normalizes the case of characters self::$caseCheck = 1; } elseif ('Darwin' === \PHP_OS_FAMILY) { @@ -245,6 +255,7 @@ class DebugClassLoader && !is_subclass_of($symbols[$i], ProphecySubjectInterface::class) && !is_subclass_of($symbols[$i], Proxy::class) && !is_subclass_of($symbols[$i], ProxyInterface::class) + && !is_subclass_of($symbols[$i], LazyObjectInterface::class) && !is_subclass_of($symbols[$i], LegacyProxy::class) && !is_subclass_of($symbols[$i], MockInterface::class) && !is_subclass_of($symbols[$i], IMock::class) @@ -332,7 +343,7 @@ class DebugClassLoader } if (!$exists) { - if (false !== strpos($class, '/')) { + if (str_contains($class, '/')) { throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); } @@ -354,7 +365,7 @@ class DebugClassLoader } $deprecations = []; - $className = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; + $className = str_contains($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; // Don't trigger deprecations for classes in the same vendor if ($class !== $className) { @@ -373,7 +384,7 @@ class DebugClassLoader // Detect annotations on the class if ($doc = $this->parsePhpDoc($refl)) { - $classIsTemplate = isset($doc['template']); + $classIsTemplate = isset($doc['template']) || isset($doc['template-covariant']); foreach (['final', 'deprecated', 'internal'] as $annotation) { if (null !== $description = $doc[$annotation][0] ?? null) { @@ -428,7 +439,7 @@ class DebugClassLoader } } elseif (!$refl->isInterface()) { if (!strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) - && 0 === strpos($className, 'Symfony\\') + && str_starts_with($className, 'Symfony\\') && (!class_exists(InstalledVersions::class) || 'symfony/symfony' !== InstalledVersions::getRootPackage()['name']) ) { @@ -466,8 +477,10 @@ class DebugClassLoader self::$finalMethods[$class] = []; self::$internalMethods[$class] = []; self::$annotatedParameters[$class] = []; + self::$finalProperties[$class] = []; + self::$finalConstants[$class] = []; foreach ($parentAndOwnInterfaces as $use) { - foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes'] as $property) { + foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes', 'finalProperties', 'finalConstants'] as $property) { if (isset(self::${$property}[$use])) { self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use]; } @@ -483,7 +496,7 @@ class DebugClassLoader } $returnType = implode('|', $returnType); - self::$returnTypes[$class] += [$method => [$returnType, 0 === strpos($returnType, '?') ? substr($returnType, 1).'|null' : $returnType, $use, '']]; + self::$returnTypes[$class] += [$method => [$returnType, str_starts_with($returnType, '?') ? substr($returnType, 1).'|null' : $returnType, $use, '']]; } } } @@ -518,7 +531,7 @@ class DebugClassLoader // To read method annotations $doc = $this->parsePhpDoc($method); - if (($classIsTemplate || isset($doc['template'])) && $method->hasReturnType()) { + if (($classIsTemplate || isset($doc['template']) || isset($doc['template-covariant'])) && $method->hasReturnType()) { unset($doc['return']); } @@ -537,7 +550,7 @@ class DebugClassLoader $forcePatchTypes = $this->patchTypes['force']; - if ($canAddReturnType = null !== $forcePatchTypes && false === strpos($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) { + if ($canAddReturnType = null !== $forcePatchTypes && !str_contains($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) { if ('void' !== (self::MAGIC_METHODS[$method->name] ?? 'void')) { $this->patchTypes['force'] = $forcePatchTypes ?: 'docblock'; } @@ -558,7 +571,7 @@ class DebugClassLoader $this->patchReturnTypeWillChange($method); } - if (null !== ($returnType ?? $returnType = self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !isset($doc['return'])) { + if (null !== ($returnType ??= self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !isset($doc['return'])) { [$normalizedType, $returnType, $declaringClass, $declaringFile] = \is_string($returnType) ? [$returnType, $returnType, '', ''] : $returnType; if ($canAddReturnType && 'docblock' !== $this->patchTypes['force']) { @@ -622,6 +635,31 @@ class DebugClassLoader } } + $finals = isset(self::$final[$class]) || $refl->isFinal() ? [] : [ + 'finalConstants' => $refl->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC | \ReflectionClassConstant::IS_PROTECTED), + 'finalProperties' => $refl->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED), + ]; + foreach ($finals as $type => $reflectors) { + foreach ($reflectors as $r) { + if ($r->class !== $class) { + continue; + } + + $doc = $this->parsePhpDoc($r); + + foreach ($parentAndOwnInterfaces as $use) { + if (isset(self::${$type}[$use][$r->name]) && !isset($doc['deprecated']) && ('finalConstants' === $type || substr($use, 0, strrpos($use, '\\')) !== substr($use, 0, strrpos($class, '\\')))) { + $msg = 'finalConstants' === $type ? '%s" constant' : '$%s" property'; + $deprecations[] = sprintf('The "%s::'.$msg.' is considered final. You should not override it in "%s".', self::${$type}[$use][$r->name], $r->name, $class); + } + } + + if (isset($doc['final']) || ('finalProperties' === $type && str_starts_with($class, 'Symfony\\') && !$r->hasType())) { + self::${$type}[$class][$r->name] = $class; + } + } + } + return $deprecations; } @@ -704,7 +742,7 @@ class DebugClassLoader $dirFiles = self::$darwinCache[$kDir][1]; - if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) { + if (!isset($dirFiles[$file]) && str_ends_with($file, ') : eval()\'d code')) { // Get the file name from "file_name.php(123) : eval()'d code" $file = substr($file, 0, strrpos($file, '(', -17)); } @@ -720,7 +758,7 @@ class DebugClassLoader if ('.' !== $f[0]) { $dirFiles[$f] = $f; if ($f === $file) { - $kFile = $k = $file; + $kFile = $file; } elseif ($f !== $k = strtolower($f)) { $dirFiles[$k] = $f; } @@ -762,14 +800,20 @@ class DebugClassLoader return; } - if ($nullable = 0 === strpos($types, 'null|')) { + if ('null' === $types) { + self::$returnTypes[$class][$method] = ['null', 'null', $class, $filename]; + + return; + } + + if ($nullable = str_starts_with($types, 'null|')) { $types = substr($types, 5); - } elseif ($nullable = '|null' === substr($types, -5)) { + } elseif ($nullable = str_ends_with($types, '|null')) { $types = substr($types, 0, -5); } $arrayType = ['array' => 'array']; $typesMap = []; - $glue = false !== strpos($types, '&') ? '&' : '|'; + $glue = str_contains($types, '&') ? '&' : '|'; foreach (explode($glue, $types) as $t) { $t = self::SPECIAL_RETURN_TYPES[strtolower($t)] ?? $t; $typesMap[$this->normalizeType($t, $class, $parent, $returnType)][$t] = $t; @@ -794,7 +838,7 @@ class DebugClassLoader $iterable = $object = true; foreach ($typesMap as $n => $t) { if ('null' !== $n) { - $iterable = $iterable && (\in_array($n, ['array', 'iterable']) || false !== strpos($n, 'Iterator')); + $iterable = $iterable && (\in_array($n, ['array', 'iterable']) || str_contains($n, 'Iterator')); $object = $object && (\in_array($n, ['callable', 'object', '$this', 'static']) || !isset(self::SPECIAL_RETURN_TYPES[$n])); } } @@ -821,6 +865,11 @@ class DebugClassLoader return; } + if (!preg_match('/^(?:\\\\?[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)+$/', $n)) { + // exclude any invalid PHP class name (e.g. `Cookie::SAMESITE_*`) + continue; + } + if (!isset($phpTypes[''])) { $phpTypes[] = $n; } @@ -863,7 +912,7 @@ class DebugClassLoader // We could resolve "use" statements to return the FQDN // but this would be too expensive for a runtime checker - if ('[]' !== substr($type, -2)) { + if (!str_ends_with($type, '[]')) { return $type; } @@ -881,9 +930,9 @@ class DebugClassLoader /** * Utility method to add #[ReturnTypeWillChange] where php triggers deprecations. */ - private function patchReturnTypeWillChange(\ReflectionMethod $method) + private function patchReturnTypeWillChange(\ReflectionMethod $method): void { - if (\PHP_VERSION_ID >= 80000 && \count($method->getAttributes(\ReturnTypeWillChange::class))) { + if (\count($method->getAttributes(\ReturnTypeWillChange::class))) { return; } @@ -909,7 +958,7 @@ class DebugClassLoader /** * Utility method to add @return annotations to the Symfony code-base where it triggers self-deprecations. */ - private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType) + private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType): void { static $patchedMethods = []; static $useStatements = []; @@ -921,10 +970,10 @@ class DebugClassLoader $patchedMethods[$file][$startLine] = true; $fileOffset = self::$fileOffsets[$file] ?? 0; $startLine += $fileOffset - 2; - if ($nullable = '|null' === substr($returnType, -5)) { + if ($nullable = str_ends_with($returnType, '|null')) { $returnType = substr($returnType, 0, -5); } - $glue = false !== strpos($returnType, '&') ? '&' : '|'; + $glue = str_contains($returnType, '&') ? '&' : '|'; $returnType = explode($glue, $returnType); $code = file($file); @@ -940,10 +989,10 @@ class DebugClassLoader continue; } - [$namespace, $useOffset, $useMap] = $useStatements[$file] ?? $useStatements[$file] = self::getUseStatements($file); + [$namespace, $useOffset, $useMap] = $useStatements[$file] ??= self::getUseStatements($file); if ('\\' !== $type[0]) { - [$declaringNamespace, , $declaringUseMap] = $useStatements[$declaringFile] ?? $useStatements[$declaringFile] = self::getUseStatements($declaringFile); + [$declaringNamespace, , $declaringUseMap] = $useStatements[$declaringFile] ??= self::getUseStatements($declaringFile); $p = strpos($type, '\\', 1); $alias = $p ? substr($type, 0, $p) : $type; @@ -981,7 +1030,7 @@ class DebugClassLoader if ('docblock' === $this->patchTypes['force'] || ('object' === $normalizedType && '7.1' === $this->patchTypes['php'])) { $returnType = implode($glue, $returnType).($nullable ? '|null' : ''); - if (false !== strpos($code[$startLine], '#[')) { + if (str_contains($code[$startLine], '#[')) { --$startLine; } @@ -1022,15 +1071,15 @@ EOTXT; break; } - if (0 === strpos($file[$i], 'namespace ')) { + if (str_starts_with($file[$i], 'namespace ')) { $namespace = substr($file[$i], \strlen('namespace '), -2).'\\'; $useOffset = $i + 2; } - if (0 === strpos($file[$i], 'use ')) { + if (str_starts_with($file[$i], 'use ')) { $useOffset = $i; - for (; 0 === strpos($file[$i], 'use '); ++$i) { + for (; str_starts_with($file[$i], 'use '); ++$i) { $u = explode(' as ', substr($file[$i], 4, -2), 2); if (1 === \count($u)) { @@ -1048,7 +1097,7 @@ EOTXT; return [$namespace, $useOffset, $useMap]; } - private function fixReturnStatements(\ReflectionMethod $method, string $returnType) + private function fixReturnStatements(\ReflectionMethod $method, string $returnType): void { if ('docblock' !== $this->patchTypes['force']) { if ('7.1' === $this->patchTypes['php'] && 'object' === ltrim($returnType, '?')) { @@ -1059,11 +1108,11 @@ EOTXT; return; } - if ('8.0' > $this->patchTypes['php'] && (false !== strpos($returnType, '|') || \in_array($returnType, ['mixed', 'static'], true))) { + if ('8.0' > $this->patchTypes['php'] && (str_contains($returnType, '|') || \in_array($returnType, ['mixed', 'static'], true))) { return; } - if ('8.1' > $this->patchTypes['php'] && false !== strpos($returnType, '&')) { + if ('8.1' > $this->patchTypes['php'] && str_contains($returnType, '&')) { return; } } @@ -1080,7 +1129,20 @@ EOTXT; } $end = $method->isGenerator() ? $i : $method->getEndLine(); + $inClosure = false; + $braces = 0; for (; $i < $end; ++$i) { + if (!$inClosure) { + $inClosure = str_contains($code[$i], 'function ('); + } + + if ($inClosure) { + $braces += substr_count($code[$i], '{') - substr_count($code[$i], '}'); + $inClosure = $braces > 0; + + continue; + } + if ('void' === $returnType) { $fixedCode[$i] = str_replace(' return null;', ' return;', $code[$i]); } elseif ('mixed' === $returnType || '?' === $returnType[0]) { diff --git a/lib/symfony/error-handler/Error/ClassNotFoundError.php b/lib/symfony/error-handler/Error/ClassNotFoundError.php index 443fba2c3..8a17745c4 100644 --- a/lib/symfony/error-handler/Error/ClassNotFoundError.php +++ b/lib/symfony/error-handler/Error/ClassNotFoundError.php @@ -13,9 +13,6 @@ namespace Symfony\Component\ErrorHandler\Error; class ClassNotFoundError extends \Error { - /** - * {@inheritdoc} - */ public function __construct(string $message, \Throwable $previous) { parent::__construct($message, $previous->getCode(), $previous->getPrevious()); @@ -26,7 +23,6 @@ class ClassNotFoundError extends \Error 'trace' => $previous->getTrace(), ] as $property => $value) { $refl = new \ReflectionProperty(\Error::class, $property); - $refl->setAccessible(true); $refl->setValue($this, $value); } } diff --git a/lib/symfony/error-handler/Error/FatalError.php b/lib/symfony/error-handler/Error/FatalError.php index 57fc690e2..a1fd5a995 100644 --- a/lib/symfony/error-handler/Error/FatalError.php +++ b/lib/symfony/error-handler/Error/FatalError.php @@ -13,11 +13,9 @@ namespace Symfony\Component\ErrorHandler\Error; class FatalError extends \Error { - private $error; + private array $error; /** - * {@inheritdoc} - * * @param array $error An array as returned by error_get_last() */ public function __construct(string $message, int $code, array $error, int $traceOffset = null, bool $traceArgs = true, array $trace = null) @@ -73,15 +71,11 @@ class FatalError extends \Error ] as $property => $value) { if (null !== $value) { $refl = new \ReflectionProperty(\Error::class, $property); - $refl->setAccessible(true); $refl->setValue($this, $value); } } } - /** - * {@inheritdoc} - */ public function getError(): array { return $this->error; diff --git a/lib/symfony/error-handler/Error/UndefinedFunctionError.php b/lib/symfony/error-handler/Error/UndefinedFunctionError.php index b57dd1579..5063b73d7 100644 --- a/lib/symfony/error-handler/Error/UndefinedFunctionError.php +++ b/lib/symfony/error-handler/Error/UndefinedFunctionError.php @@ -13,9 +13,6 @@ namespace Symfony\Component\ErrorHandler\Error; class UndefinedFunctionError extends \Error { - /** - * {@inheritdoc} - */ public function __construct(string $message, \Throwable $previous) { parent::__construct($message, $previous->getCode(), $previous->getPrevious()); @@ -26,7 +23,6 @@ class UndefinedFunctionError extends \Error 'trace' => $previous->getTrace(), ] as $property => $value) { $refl = new \ReflectionProperty(\Error::class, $property); - $refl->setAccessible(true); $refl->setValue($this, $value); } } diff --git a/lib/symfony/error-handler/Error/UndefinedMethodError.php b/lib/symfony/error-handler/Error/UndefinedMethodError.php index adc8731f3..35f15451c 100644 --- a/lib/symfony/error-handler/Error/UndefinedMethodError.php +++ b/lib/symfony/error-handler/Error/UndefinedMethodError.php @@ -13,9 +13,6 @@ namespace Symfony\Component\ErrorHandler\Error; class UndefinedMethodError extends \Error { - /** - * {@inheritdoc} - */ public function __construct(string $message, \Throwable $previous) { parent::__construct($message, $previous->getCode(), $previous->getPrevious()); @@ -26,7 +23,6 @@ class UndefinedMethodError extends \Error 'trace' => $previous->getTrace(), ] as $property => $value) { $refl = new \ReflectionProperty(\Error::class, $property); - $refl->setAccessible(true); $refl->setValue($this, $value); } } diff --git a/lib/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php b/lib/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php index f85d27515..a98075fe4 100644 --- a/lib/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php +++ b/lib/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php @@ -21,9 +21,6 @@ use Symfony\Component\ErrorHandler\Error\FatalError; */ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface { - /** - * {@inheritdoc} - */ public function enhance(\Throwable $error): ?\Throwable { // Some specific versions of PHP produce a fatal error when extending a not found class. @@ -143,7 +140,7 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface ]; if ($prefix) { - $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); + $candidates = array_filter($candidates, fn ($candidate) => str_starts_with($candidate, $prefix)); } // We cannot use the autoloader here as most of them use require; but if the class @@ -155,9 +152,17 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface } } + // Symfony may ship some polyfills, like "Normalizer". But if the Intl + // extension is already installed, the next require_once will fail with + // a compile error because the class is already defined. And this one + // does not throw a Throwable. So it's better to skip it here. + if (str_contains($file, 'Resources/stubs')) { + return null; + } + try { require_once $file; - } catch (\Throwable $e) { + } catch (\Throwable) { return null; } diff --git a/lib/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php b/lib/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php index f4c49c285..0458c26ac 100644 --- a/lib/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php +++ b/lib/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php @@ -19,9 +19,6 @@ use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError; */ class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface { - /** - * {@inheritdoc} - */ public function enhance(\Throwable $error): ?\Throwable { if ($error instanceof FatalError) { @@ -42,7 +39,7 @@ class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface $prefix = 'Call to undefined function '; $prefixLen = \strlen($prefix); - if (0 !== strpos($message, $prefix)) { + if (!str_starts_with($message, $prefix)) { return null; } diff --git a/lib/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php b/lib/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php index c4355f92c..80eaec9bf 100644 --- a/lib/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php +++ b/lib/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php @@ -19,9 +19,6 @@ use Symfony\Component\ErrorHandler\Error\UndefinedMethodError; */ class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface { - /** - * {@inheritdoc} - */ public function enhance(\Throwable $error): ?\Throwable { if ($error instanceof FatalError) { @@ -47,7 +44,7 @@ class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface $candidates = []; foreach ($methods as $definedMethodName) { $lev = levenshtein($methodName, $definedMethodName); - if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { + if ($lev <= \strlen($methodName) / 3 || str_contains($definedMethodName, $methodName)) { $candidates[] = $definedMethodName; } } diff --git a/lib/symfony/error-handler/ErrorHandler.php b/lib/symfony/error-handler/ErrorHandler.php index 003040242..1ee29e1b8 100644 --- a/lib/symfony/error-handler/ErrorHandler.php +++ b/lib/symfony/error-handler/ErrorHandler.php @@ -50,7 +50,7 @@ use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; */ class ErrorHandler { - private $levels = [ + private array $levels = [ \E_DEPRECATED => 'Deprecated', \E_USER_DEPRECATED => 'User Deprecated', \E_NOTICE => 'Notice', @@ -68,7 +68,7 @@ class ErrorHandler \E_CORE_ERROR => 'Core Error', ]; - private $loggers = [ + private array $loggers = [ \E_DEPRECATED => [null, LogLevel::INFO], \E_USER_DEPRECATED => [null, LogLevel::INFO], \E_NOTICE => [null, LogLevel::WARNING], @@ -86,24 +86,24 @@ class ErrorHandler \E_CORE_ERROR => [null, LogLevel::CRITICAL], ]; - private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED - private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED - private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE - private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE - private $loggedErrors = 0; - private $configureException; - private $debug; + private int $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private int $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private int $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE + private int $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE + private int $loggedErrors = 0; + private \Closure $configureException; + private bool $debug; - private $isRecursive = 0; - private $isRoot = false; + private bool $isRecursive = false; + private bool $isRoot = false; + /** @var callable|null */ private $exceptionHandler; - private $bootstrappingLogger; + private ?BufferingLogger $bootstrappingLogger = null; - private static $reservedMemory; - private static $toStringException; - private static $silencedErrorCache = []; - private static $silencedErrorCount = 0; - private static $exitCode = 0; + private static ?string $reservedMemory = null; + private static array $silencedErrorCache = []; + private static int $silencedErrorCount = 0; + private static int $exitCode = 0; /** * Registers the error handler. @@ -112,7 +112,7 @@ class ErrorHandler { if (null === self::$reservedMemory) { self::$reservedMemory = str_repeat('x', 32768); - register_shutdown_function(__CLASS__.'::handleFatalError'); + register_shutdown_function(self::handleFatalError(...)); } if ($handlerIsNew = null === $handler) { @@ -158,11 +158,9 @@ class ErrorHandler /** * Calls a function and turns any PHP error into \ErrorException. * - * @return mixed What $function(...$arguments) returns - * * @throws \ErrorException When $function(...$arguments) triggers a PHP error */ - public static function call(callable $function, ...$arguments) + public static function call(callable $function, mixed ...$arguments): mixed { set_error_handler(static function (int $type, string $message, string $file, int $line) { if (__FILE__ === $file) { @@ -188,7 +186,6 @@ class ErrorHandler $this->setDefaultLogger($bootstrappingLogger); } $traceReflector = new \ReflectionProperty(\Exception::class, 'trace'); - $traceReflector->setAccessible(true); $this->configureException = \Closure::bind(static function ($e, $trace, $file = null, $line = null) use ($traceReflector) { $traceReflector->setValue($e, $trace); $e->file = $file ?? $e->file; @@ -205,7 +202,7 @@ class ErrorHandler * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants * @param bool $replace Whether to replace or not any existing logger */ - public function setDefaultLogger(LoggerInterface $logger, $levels = \E_ALL, bool $replace = false): void + public function setDefaultLogger(LoggerInterface $logger, array|int|null $levels = \E_ALL, bool $replace = false): void { $loggers = []; @@ -216,9 +213,7 @@ class ErrorHandler } } } else { - if (null === $levels) { - $levels = \E_ALL; - } + $levels ??= \E_ALL; foreach ($this->loggers as $type => $log) { if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { $log[0] = $logger; @@ -235,8 +230,6 @@ class ErrorHandler * * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map * - * @return array The previous map - * * @throws \InvalidArgumentException */ public function setLoggers(array $loggers): array @@ -283,13 +276,6 @@ class ErrorHandler return $prev; } - /** - * Sets a user exception handler. - * - * @param callable(\Throwable $e)|null $handler - * - * @return callable|null The previous exception handler - */ public function setExceptionHandler(?callable $handler): ?callable { $prev = $this->exceptionHandler; @@ -303,8 +289,6 @@ class ErrorHandler * * @param int $levels A bit field of E_* constants for thrown errors * @param bool $replace Replace or amend the previous value - * - * @return int The previous value */ public function throwAt(int $levels, bool $replace = false): int { @@ -323,8 +307,6 @@ class ErrorHandler * * @param int $levels A bit field of E_* constants for scoped errors * @param bool $replace Replace or amend the previous value - * - * @return int The previous value */ public function scopeAt(int $levels, bool $replace = false): int { @@ -342,8 +324,6 @@ class ErrorHandler * * @param int $levels A bit field of E_* constants for traced errors * @param bool $replace Replace or amend the previous value - * - * @return int The previous value */ public function traceAt(int $levels, bool $replace = false): int { @@ -361,8 +341,6 @@ class ErrorHandler * * @param int $levels A bit field of E_* constants for screamed errors * @param bool $replace Replace or amend the previous value - * - * @return int The previous value */ public function screamAt(int $levels, bool $replace = false): int { @@ -381,7 +359,7 @@ class ErrorHandler private function reRegister(int $prev): void { if ($prev !== ($this->thrownErrors | $this->loggedErrors)) { - $handler = set_error_handler('is_int'); + $handler = set_error_handler(static fn () => null); $handler = \is_array($handler) ? $handler[0] : null; restore_error_handler(); if ($handler === $this) { @@ -406,7 +384,7 @@ class ErrorHandler */ public function handleError(int $type, string $message, string $file, int $line): bool { - if (\PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message[0] && false !== strpos($message, '" targeting switch is equivalent to "break')) { + if (\E_WARNING === $type && '"' === $message[0] && str_contains($message, '" targeting switch is equivalent to "break')) { $type = \E_DEPRECATED; } @@ -430,10 +408,7 @@ class ErrorHandler $logMessage = $this->levels[$type].': '.$message; - if (null !== self::$toStringException) { - $errorAsException = self::$toStringException; - self::$toStringException = null; - } elseif (!$throw && !($type & $level)) { + if (!$throw && !($type & $level)) { if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) { $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : []; $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace); @@ -457,7 +432,7 @@ class ErrorHandler return true; } } else { - if (false !== strpos($message, '@anonymous')) { + if (str_contains($message, '@anonymous')) { $backtrace = debug_backtrace(false, 5); for ($i = 1; isset($backtrace[$i]); ++$i) { @@ -478,70 +453,26 @@ class ErrorHandler if ($throw || $this->tracedErrors & $type) { $backtrace = $errorAsException->getTrace(); - $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); - ($this->configureException)($errorAsException, $lightTrace, $file, $line); + $backtrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); + ($this->configureException)($errorAsException, $backtrace, $file, $line); } else { ($this->configureException)($errorAsException, []); - $backtrace = []; } } if ($throw) { - if (\PHP_VERSION_ID < 70400 && \E_USER_ERROR & $type) { - for ($i = 1; isset($backtrace[$i]); ++$i) { - if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) - && '__toString' === $backtrace[$i]['function'] - && '->' === $backtrace[$i]['type'] - && !isset($backtrace[$i - 1]['class']) - && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) - ) { - // Here, we know trigger_error() has been called from __toString(). - // PHP triggers a fatal error when throwing from __toString(). - // A small convention allows working around the limitation: - // given a caught $e exception in __toString(), quitting the method with - // `return trigger_error($e, E_USER_ERROR);` allows this error handler - // to make $e get through the __toString() barrier. - - $context = 4 < \func_num_args() ? (func_get_arg(4) ?: []) : []; - - foreach ($context as $e) { - if ($e instanceof \Throwable && $e->__toString() === $message) { - self::$toStringException = $e; - - return true; - } - } - - // Display the original error message instead of the default one. - $this->handleException($errorAsException); - - // Stop the process by giving back the error to the native handler. - return false; - } - } - } - throw $errorAsException; } if ($this->isRecursive) { $log = 0; } else { - if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { - $currentErrorHandler = set_error_handler('is_int'); - restore_error_handler(); - } - try { $this->isRecursive = true; $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []); } finally { $this->isRecursive = false; - - if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { - set_error_handler($currentErrorHandler); - } } } @@ -553,7 +484,7 @@ class ErrorHandler * * @internal */ - public function handleException(\Throwable $exception) + public function handleException(\Throwable $exception): void { $handlerException = null; @@ -566,7 +497,7 @@ class ErrorHandler } if ($this->loggedErrors & $type) { - if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) { + if (str_contains($message = $exception->getMessage(), "@anonymous\0")) { $message = $this->parseAnonymousClass($message); } @@ -586,14 +517,7 @@ class ErrorHandler } } - if (!$exception instanceof OutOfMemoryError) { - foreach ($this->getErrorEnhancers() as $errorEnhancer) { - if ($e = $errorEnhancer->enhance($exception)) { - $exception = $e; - break; - } - } - } + $exception = $this->enhanceError($exception); $exceptionHandler = $this->exceptionHandler; $this->exceptionHandler = [$this, 'renderException']; @@ -604,9 +528,11 @@ class ErrorHandler try { if (null !== $exceptionHandler) { - return $exceptionHandler($exception); + $exceptionHandler($exception); + + return; } - $handlerException = $handlerException ?: $exception; + $handlerException ??= $exception; } catch (\Throwable $handlerException) { } if ($exception === $handlerException && null === $this->exceptionHandler) { @@ -682,7 +608,7 @@ class ErrorHandler $handler->throwAt(0, true); $trace = $error['backtrace'] ?? null; - if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + if (str_starts_with($error['message'], 'Allowed memory') || str_starts_with($error['message'], 'Out of memory')) { $fatalError = new OutOfMemoryError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, false, $trace); } else { $fatalError = new FatalError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, true, $trace); @@ -696,7 +622,7 @@ class ErrorHandler self::$exitCode = 255; $handler->handleException($fatalError); } - } catch (FatalError $e) { + } catch (FatalError) { // Ignore this re-throw } @@ -714,7 +640,7 @@ class ErrorHandler */ private function renderException(\Throwable $exception): void { - $renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug); + $renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug); $exception = $renderer->render($exception); @@ -729,6 +655,21 @@ class ErrorHandler echo $exception->getAsString(); } + public function enhanceError(\Throwable $exception): \Throwable + { + if ($exception instanceof OutOfMemoryError) { + return $exception; + } + + foreach ($this->getErrorEnhancers() as $errorEnhancer) { + if ($e = $errorEnhancer->enhance($exception)) { + return $e; + } + } + + return $exception; + } + /** * Override this method if you want to define more error enhancers. * @@ -791,8 +732,6 @@ class ErrorHandler */ private function parseAnonymousClass(string $message): string { - return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) { - return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; - }, $message); + return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); } } diff --git a/lib/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php b/lib/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php index 5c0f6a7dc..04b3edb1b 100644 --- a/lib/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php +++ b/lib/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php @@ -23,9 +23,6 @@ class_exists(CliDumper::class); */ class CliErrorRenderer implements ErrorRendererInterface { - /** - * {@inheritdoc} - */ public function render(\Throwable $exception): FlattenException { $cloner = new VarCloner(); diff --git a/lib/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php b/lib/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php index aba196603..2e2896241 100644 --- a/lib/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php +++ b/lib/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php @@ -20,6 +20,16 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; */ interface ErrorRendererInterface { + public const IDE_LINK_FORMATS = [ + 'textmate' => 'txmt://open?url=file://%f&line=%l', + 'macvim' => 'mvim://open?url=file://%f&line=%l', + 'emacs' => 'emacs://open?url=file://%f&line=%l', + 'sublime' => 'subl://open?url=file://%f&line=%l', + 'phpstorm' => 'phpstorm://open?file=%f&line=%l', + 'atom' => 'atom://core/open/file?filename=%f&line=%l', + 'vscode' => 'vscode://file/%f:%l', + ]; + /** * Renders a Throwable as a FlattenException. */ diff --git a/lib/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php b/lib/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php new file mode 100644 index 000000000..29fcf835b --- /dev/null +++ b/lib/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * Formats debug file links. + * + * @author JĂ©rĂ©my Romey + * + * @final + */ +class FileLinkFormatter +{ + private array|false $fileLinkFormat; + private ?RequestStack $requestStack = null; + private ?string $baseDir = null; + private \Closure|string|null $urlFormat; + + /** + * @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand + */ + public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null) + { + $fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? ''; + + if (!\is_array($f = $fileLinkFormat)) { + $f = (ErrorRendererInterface::IDE_LINK_FORMATS[$f] ?? $f) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; + $i = strpos($f, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); + $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); + } + + $this->fileLinkFormat = $fileLinkFormat; + $this->requestStack = $requestStack; + $this->baseDir = $baseDir; + $this->urlFormat = $urlFormat; + } + + /** + * @return string|false + */ + public function format(string $file, int $line): string|bool + { + if ($fmt = $this->getFileLinkFormat()) { + for ($i = 1; isset($fmt[$i]); ++$i) { + if (str_starts_with($file, $k = $fmt[$i++])) { + $file = substr_replace($file, $fmt[$i], 0, \strlen($k)); + break; + } + } + + return strtr($fmt[0], ['%f' => $file, '%l' => $line]); + } + + return false; + } + + /** + * @internal + */ + public function __sleep(): array + { + $this->fileLinkFormat = $this->getFileLinkFormat(); + + return ['fileLinkFormat']; + } + + /** + * @internal + */ + public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString): ?string + { + try { + return $router->generate($routeName).$queryString; + } catch (\Throwable) { + return null; + } + } + + private function getFileLinkFormat(): array|false + { + if ($this->fileLinkFormat) { + return $this->fileLinkFormat; + } + + if ($this->requestStack && $this->baseDir && $this->urlFormat) { + $request = $this->requestStack->getMainRequest(); + + if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) { + return [ + $request->getSchemeAndHttpHost().$this->urlFormat, + $this->baseDir.\DIRECTORY_SEPARATOR, '', + ]; + } + } + + return false; + } +} + +if (!class_exists(\Symfony\Component\HttpKernel\Debug\FileLinkFormatter::class, false)) { + class_alias(FileLinkFormatter::class, \Symfony\Component\HttpKernel\Debug\FileLinkFormatter::class); +} diff --git a/lib/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php b/lib/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php index b8a9aeda6..4e2a99b80 100644 --- a/lib/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php +++ b/lib/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php @@ -15,8 +15,9 @@ use Psr\Log\LoggerInterface; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; -use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; /** * @author Yonel Ceruto @@ -33,41 +34,29 @@ class HtmlErrorRenderer implements ErrorRendererInterface private const GHOST_HEART = 'M125.91386369681868,8.305165958366445 C128.95033202169043,-0.40540639102854037 140.8469835342744,8.305165958366445 125.91386369681868,19.504526138305664 C110.98208663272044,8.305165958366445 122.87795231771452,-0.40540639102854037 125.91386369681868,8.305165958366445 z'; private const GHOST_PLUS = 'M111.36824226379395,8.969108581542969 L118.69175148010254,8.969108581542969 L118.69175148010254,1.6455793380737305 L126.20429420471191,1.6455793380737305 L126.20429420471191,8.969108581542969 L133.52781105041504,8.969108581542969 L133.52781105041504,16.481630325317383 L126.20429420471191,16.481630325317383 L126.20429420471191,23.805158615112305 L118.69175148010254,23.805158615112305 L118.69175148010254,16.481630325317383 L111.36824226379395,16.481630325317383 z'; - private $debug; - private $charset; - private $fileLinkFormat; - private $projectDir; - private $outputBuffer; - private $logger; + private bool|\Closure $debug; + private string $charset; + private FileLinkFormatter $fileLinkFormat; + private ?string $projectDir; + private string|\Closure $outputBuffer; + private ?LoggerInterface $logger; - private static $template = 'views/error.html.php'; + private static string $template = 'views/error.html.php'; /** - * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it - * @param string|FileLinkFormatter|null $fileLinkFormat - * @param bool|callable $outputBuffer The output buffer as a string or a callable that should return it + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + * @param string|callable $outputBuffer The output buffer as a string or a callable that should return it */ - public function __construct($debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, $outputBuffer = '', LoggerInterface $logger = null) + public function __construct(bool|callable $debug = false, string $charset = null, string|FileLinkFormatter $fileLinkFormat = null, string $projectDir = null, string|callable $outputBuffer = '', LoggerInterface $logger = null) { - if (!\is_bool($debug) && !\is_callable($debug)) { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug))); - } - - if (!\is_string($outputBuffer) && !\is_callable($outputBuffer)) { - throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($outputBuffer))); - } - - $this->debug = $debug; + $this->debug = \is_bool($debug) ? $debug : $debug(...); $this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8'); - $this->fileLinkFormat = $fileLinkFormat ?: (\ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')); + $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : new FileLinkFormatter($fileLinkFormat); $this->projectDir = $projectDir; - $this->outputBuffer = $outputBuffer; + $this->outputBuffer = \is_string($outputBuffer) ? $outputBuffer : $outputBuffer(...); $this->logger = $logger; } - /** - * {@inheritdoc} - */ public function render(\Throwable $exception): FlattenException { $headers = ['Content-Type' => 'text/html; charset='.$this->charset]; @@ -76,7 +65,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface $headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine(); } - $exception = FlattenException::createFromThrowable($exception, null, $headers); + $exception = FlattenException::createWithDataRepresentation($exception, null, $headers); return $exception->setAsString($this->renderException($exception)); } @@ -151,14 +140,19 @@ class HtmlErrorRenderer implements ErrorRendererInterface 'exceptionMessage' => $exceptionMessage, 'statusText' => $statusText, 'statusCode' => $statusCode, - 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + 'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger), 'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)(), ]); } - /** - * Formats an array as a string. - */ + private function dumpValue(Data $value): string + { + $dumper = new HtmlDumper(); + $dumper->setTheme('light'); + + return $dumper->dump($value, true); + } + private function formatArgs(array $args): string { $result = []; @@ -183,7 +177,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface return implode(', ', $result); } - private function formatArgsAsText(array $args) + private function formatArgsAsText(array $args): string { return strip_tags($this->formatArgs($args)); } @@ -205,27 +199,13 @@ class HtmlErrorRenderer implements ErrorRendererInterface { $file = str_replace('\\', '/', $file); - if (null !== $this->projectDir && 0 === strpos($file, $this->projectDir)) { + if (null !== $this->projectDir && str_starts_with($file, $this->projectDir)) { return ltrim(substr($file, \strlen($this->projectDir)), '/'); } return null; } - /** - * Returns the link for a given file/line pair. - * - * @return string|false - */ - private function getFileLink(string $file, int $line) - { - if ($fmt = $this->fileLinkFormat) { - return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); - } - - return false; - } - /** * Formats a file path. * @@ -249,11 +229,9 @@ class HtmlErrorRenderer implements ErrorRendererInterface $text .= ' at line '.$line; } - if (false !== $link = $this->getFileLink($file, $line)) { - return sprintf('%s', $this->escape($link), $text); - } + $link = $this->fileLinkFormat->format($file, $line); - return $text; + return sprintf('%s', $this->escape($link), $text); } /** @@ -269,13 +247,21 @@ class HtmlErrorRenderer implements ErrorRendererInterface // highlight_file could throw warnings // see https://bugs.php.net/25725 $code = @highlight_file($file, true); - // remove main code/span tags - $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', function ($m) { - return "".str_replace('
', "

", $m[2]).''; - }, $code); - $content = explode('
', $code); + if (\PHP_VERSION_ID >= 80300) { + // remove main pre/code tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline code tags + $code = preg_replace_callback('#]++)>((?:[^<]*+\\n)++[^<]*+)#', fn ($m) => "".str_replace("\n", "\n", $m[2]).'', $code); + // Convert spaces to html entities to preserve indentation when rendered + $code = str_replace(' ', ' ', $code); + $content = explode("\n", $code); + } else { + // remove main code/span tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline spans + $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', fn ($m) => "".str_replace('
', "

", $m[2]).'', $code); + $content = explode('
', $code); + } $lines = []; if (0 > $srcContext) { @@ -292,7 +278,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface return ''; } - private function fixCodeMarkup(string $line) + private function fixCodeMarkup(string $line): string { // ending tag from previous line $opening = strpos($line, 'formatFile($match[2], $match[3]); - }, $text); + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text); } - private function formatLogMessage(string $message, array $context) + private function formatLogMessage(string $message, array $context): string { - if ($context && false !== strpos($message, '{')) { + if ($context && str_contains($message, '{')) { $replacements = []; foreach ($context as $key => $val) { if (\is_scalar($val)) { diff --git a/lib/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php b/lib/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php index cec8e4d41..1f286b784 100644 --- a/lib/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php +++ b/lib/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php @@ -24,38 +24,27 @@ use Symfony\Component\Serializer\SerializerInterface; */ class SerializerErrorRenderer implements ErrorRendererInterface { - private $serializer; - private $format; - private $fallbackErrorRenderer; - private $debug; + private SerializerInterface $serializer; + private string|\Closure $format; + private ErrorRendererInterface $fallbackErrorRenderer; + private bool|\Closure $debug; /** * @param string|callable(FlattenException) $format The format as a string or a callable that should return it * formats not supported by Request::getMimeTypes() should be given as mime types * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it */ - public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false) + public function __construct(SerializerInterface $serializer, string|callable $format, ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false) { - if (!\is_string($format) && !\is_callable($format)) { - throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($format))); - } - - if (!\is_bool($debug) && !\is_callable($debug)) { - throw new \TypeError(sprintf('Argument 4 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug))); - } - $this->serializer = $serializer; - $this->format = $format; + $this->format = \is_string($format) ? $format : $format(...); $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); - $this->debug = $debug; + $this->debug = \is_bool($debug) ? $debug : $debug(...); } - /** - * {@inheritdoc} - */ public function render(\Throwable $exception): FlattenException { - $headers = []; + $headers = ['Vary' => 'Accept']; $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); if ($debug) { $headers['X-Debug-Exception'] = rawurlencode($exception->getMessage()); @@ -66,19 +55,17 @@ class SerializerErrorRenderer implements ErrorRendererInterface try { $format = \is_string($this->format) ? $this->format : ($this->format)($flattenException); - $headers = [ - 'Content-Type' => Request::getMimeTypes($format)[0] ?? $format, - 'Vary' => 'Accept', - ]; + $headers['Content-Type'] = Request::getMimeTypes($format)[0] ?? $format; - return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [ + $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [ 'exception' => $exception, 'debug' => $debug, - ])) - ->setHeaders($flattenException->getHeaders() + $headers); - } catch (NotEncodableValueException $e) { - return $this->fallbackErrorRenderer->render($exception); + ])); + } catch (NotEncodableValueException) { + $flattenException = $this->fallbackErrorRenderer->render($exception); } + + return $flattenException->setHeaders($flattenException->getHeaders() + $headers); } public static function getPreferredFormat(RequestStack $requestStack): \Closure diff --git a/lib/symfony/error-handler/Exception/FlattenException.php b/lib/symfony/error-handler/Exception/FlattenException.php index 262dae62b..ab62b1be3 100644 --- a/lib/symfony/error-handler/Exception/FlattenException.php +++ b/lib/symfony/error-handler/Exception/FlattenException.php @@ -14,6 +14,10 @@ namespace Symfony\Component\ErrorHandler\Exception; use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Cloner\VarCloner; /** * FlattenException wraps a PHP Error or Exception to be able to serialize it. @@ -24,54 +28,26 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; */ class FlattenException { - /** @var string */ - private $message; + private string $message; + private string|int $code; + private ?self $previous = null; + private array $trace; + private string $traceAsString; + private string $class; + private int $statusCode; + private string $statusText; + private array $headers; + private string $file; + private int $line; + private ?string $asString = null; + private Data $dataRepresentation; - /** @var int|string */ - private $code; - - /** @var self|null */ - private $previous; - - /** @var array */ - private $trace; - - /** @var string */ - private $traceAsString; - - /** @var string */ - private $class; - - /** @var int */ - private $statusCode; - - /** @var string */ - private $statusText; - - /** @var array */ - private $headers; - - /** @var string */ - private $file; - - /** @var int */ - private $line; - - /** @var string|null */ - private $asString; - - /** - * @return static - */ - public static function create(\Exception $exception, int $statusCode = null, array $headers = []): self + public static function create(\Exception $exception, int $statusCode = null, array $headers = []): static { return static::createFromThrowable($exception, $statusCode, $headers); } - /** - * @return static - */ - public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): self + public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): static { $e = new static(); $e->setMessage($exception->getMessage()); @@ -84,9 +60,7 @@ class FlattenException $statusCode = 400; } - if (null === $statusCode) { - $statusCode = 500; - } + $statusCode ??= 500; if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { $statusText = Response::$statusTexts[$statusCode]; @@ -98,7 +72,7 @@ class FlattenException $e->setStatusCode($statusCode); $e->setHeaders($headers); $e->setTraceFromThrowable($exception); - $e->setClass(\get_class($exception)); + $e->setClass(get_debug_type($exception)); $e->setFile($exception->getFile()); $e->setLine($exception->getLine()); @@ -111,6 +85,33 @@ class FlattenException return $e; } + public static function createWithDataRepresentation(\Throwable $throwable, int $statusCode = null, array $headers = [], VarCloner $cloner = null): static + { + $e = static::createFromThrowable($throwable, $statusCode, $headers); + + static $defaultCloner; + + if (!$cloner ??= $defaultCloner) { + $cloner = $defaultCloner = new VarCloner(); + $cloner->addCasters([ + \Throwable::class => function (\Throwable $e, array $a, Stub $s, bool $isNested): array { + if (!$isNested) { + unset($a[Caster::PREFIX_PROTECTED.'message']); + unset($a[Caster::PREFIX_PROTECTED.'code']); + unset($a[Caster::PREFIX_PROTECTED.'file']); + unset($a[Caster::PREFIX_PROTECTED.'line']); + unset($a["\0Error\0trace"], $a["\0Exception\0trace"]); + unset($a["\0Error\0previous"], $a["\0Exception\0previous"]); + } + + return $a; + }, + ]); + } + + return $e->setDataRepresentation($cloner->cloneVar($throwable)); + } + public function toArray(): array { $exceptions = []; @@ -119,6 +120,7 @@ class FlattenException 'message' => $exception->getMessage(), 'class' => $exception->getClass(), 'trace' => $exception->getTrace(), + 'data' => $exception->getDataRepresentation(), ]; } @@ -133,7 +135,7 @@ class FlattenException /** * @return $this */ - public function setStatusCode(int $code): self + public function setStatusCode(int $code): static { $this->statusCode = $code; @@ -148,7 +150,7 @@ class FlattenException /** * @return $this */ - public function setHeaders(array $headers): self + public function setHeaders(array $headers): static { $this->headers = $headers; @@ -163,9 +165,9 @@ class FlattenException /** * @return $this */ - public function setClass(string $class): self + public function setClass(string $class): static { - $this->class = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; + $this->class = str_contains($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; return $this; } @@ -178,7 +180,7 @@ class FlattenException /** * @return $this */ - public function setFile(string $file): self + public function setFile(string $file): static { $this->file = $file; @@ -193,7 +195,7 @@ class FlattenException /** * @return $this */ - public function setLine(int $line): self + public function setLine(int $line): static { $this->line = $line; @@ -208,7 +210,7 @@ class FlattenException /** * @return $this */ - public function setStatusText(string $statusText): self + public function setStatusText(string $statusText): static { $this->statusText = $statusText; @@ -223,12 +225,10 @@ class FlattenException /** * @return $this */ - public function setMessage(string $message): self + public function setMessage(string $message): static { - if (false !== strpos($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) { - return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; - }, $message); + 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]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); } $this->message = $message; @@ -239,17 +239,15 @@ class FlattenException /** * @return int|string int most of the time (might be a string with PDOException) */ - public function getCode() + public function getCode(): int|string { return $this->code; } /** - * @param int|string $code - * * @return $this */ - public function setCode($code): self + public function setCode(int|string $code): static { $this->code = $code; @@ -264,7 +262,7 @@ class FlattenException /** * @return $this */ - public function setPrevious(?self $previous): self + public function setPrevious(?self $previous): static { $this->previous = $previous; @@ -293,7 +291,7 @@ class FlattenException /** * @return $this */ - public function setTraceFromThrowable(\Throwable $throwable): self + public function setTraceFromThrowable(\Throwable $throwable): static { $this->traceAsString = $throwable->getTraceAsString(); @@ -303,7 +301,7 @@ class FlattenException /** * @return $this */ - public function setTrace(array $trace, ?string $file, ?int $line): self + public function setTrace(array $trace, ?string $file, ?int $line): static { $this->trace = []; $this->trace[] = [ @@ -340,6 +338,21 @@ class FlattenException return $this; } + public function getDataRepresentation(): ?Data + { + return $this->dataRepresentation ?? null; + } + + /** + * @return $this + */ + public function setDataRepresentation(Data $data): static + { + $this->dataRepresentation = $data; + + return $this; + } + private function flattenArgs(array $args, int $level = 0, int &$count = 0): array { $result = []; @@ -350,7 +363,7 @@ class FlattenException if ($value instanceof \__PHP_Incomplete_Class) { $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)]; } elseif (\is_object($value)) { - $result[$key] = ['object', \get_class($value)]; + $result[$key] = ['object', get_debug_type($value)]; } elseif (\is_array($value)) { if ($level > 10) { $result[$key] = ['array', '*DEEP NESTED ARRAY*']; @@ -390,7 +403,7 @@ class FlattenException /** * @return $this */ - public function setAsString(?string $asString): self + public function setAsString(?string $asString): static { $this->asString = $asString; diff --git a/lib/symfony/error-handler/Exception/SilencedErrorContext.php b/lib/symfony/error-handler/Exception/SilencedErrorContext.php index 18defc72c..ac19e6391 100644 --- a/lib/symfony/error-handler/Exception/SilencedErrorContext.php +++ b/lib/symfony/error-handler/Exception/SilencedErrorContext.php @@ -20,10 +20,10 @@ class SilencedErrorContext implements \JsonSerializable { public $count = 1; - private $severity; - private $file; - private $line; - private $trace; + private int $severity; + private string $file; + private int $line; + private array $trace; public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1) { diff --git a/lib/symfony/error-handler/Internal/TentativeTypes.php b/lib/symfony/error-handler/Internal/TentativeTypes.php index 2168a1c07..1e8afe39b 100644 --- a/lib/symfony/error-handler/Internal/TentativeTypes.php +++ b/lib/symfony/error-handler/Internal/TentativeTypes.php @@ -753,6 +753,7 @@ class TentativeTypes 'isVariadic' => 'bool', 'isStatic' => 'bool', 'getClosureThis' => '?object', + 'getClosureCalledClass' => '?ReflectionClass', 'getClosureScopeClass' => '?ReflectionClass', 'getDocComment' => 'string|false', 'getEndLine' => 'int|false', diff --git a/lib/symfony/error-handler/LICENSE b/lib/symfony/error-handler/LICENSE index 9c907a46a..f37c76b59 100644 --- a/lib/symfony/error-handler/LICENSE +++ b/lib/symfony/error-handler/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/error-handler/Resources/assets/css/exception.css b/lib/symfony/error-handler/Resources/assets/css/exception.css index 7cb3206da..3e6eae5a9 100644 --- a/lib/symfony/error-handler/Resources/assets/css/exception.css +++ b/lib/symfony/error-handler/Resources/assets/css/exception.css @@ -9,12 +9,23 @@ --color-warning: #a46a1f; --color-error: #b0413e; --color-muted: #999; - --tab-background: #fff; + --tab-background: #f0f0f0; + --tab-border-color: #e5e5e5; + --tab-active-border-color: #d4d4d4; --tab-color: #444; - --tab-active-background: #666; - --tab-active-color: #fafafa; + --tab-active-background: #fff; + --tab-active-color: var(--color-text); --tab-disabled-background: #f5f5f5; --tab-disabled-color: #999; + --selected-badge-background: #e5e5e5; + --selected-badge-color: #525252; + --selected-badge-shadow: inset 0 0 0 1px #d4d4d4; + --selected-badge-warning-background: #fde496; + --selected-badge-warning-color: #785b02; + --selected-badge-warning-shadow: inset 0 0 0 1px #e6af05; + --selected-badge-danger-background: #FCE9ED; + --selected-badge-danger-color: #83122A; + --selected-badge-danger-shadow: inset 0 0 0 1px #F5B8C5; --metric-value-background: #fff; --metric-value-color: inherit; --metric-unit-color: #999; @@ -47,12 +58,23 @@ --color-text: #e0e0e0; --color-muted: #777; --color-error: #d43934; - --tab-background: #555; - --tab-color: #ccc; - --tab-active-background: #888; - --tab-active-color: #fafafa; + --tab-background: #404040; + --tab-border-color: #737373; + --tab-active-border-color: #171717; + --tab-color: var(--color-text); + --tab-active-background: #d4d4d4; + --tab-active-color: #262626; --tab-disabled-background: var(--page-background); - --tab-disabled-color: #777; + --tab-disabled-color: #a3a3a3; + --selected-badge-background: #555; + --selected-badge-color: #ddd; + --selected-badge-shadow: none; + --selected-badge-warning-background: #fcd55f; + --selected-badge-warning-color: #785b02; + --selected-badge-warning-shadow: inset 0 0 0 1px #af8503; + --selected-badge-danger-background: #B41939; + --selected-badge-danger-color: #FCE9ED; + --selected-badge-danger-shadow: none; --metric-value-background: #555; --metric-value-color: inherit; --metric-unit-color: #999; @@ -83,7 +105,7 @@ --card-label-color: var(--tab-active-color); } -html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0} +html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}summary{cursor: pointer} html { /* always display the vertical scrollbar to avoid jumps when toggling contents */ @@ -132,15 +154,96 @@ thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-vis .sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; } .sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; } -.tab-navigation { margin: 0 0 1em 0; padding: 0; } -.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; } -.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; } -.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); } -.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; } -.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); } -.tab-content > *:first-child { margin-top: 0; } -.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; } -.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; } +.tab-navigation { + background-color: var(--tab-background); + border-radius: 6px; + box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 5px var(--page-background); + display: inline-flex; + flex-wrap: wrap; + margin: 0 0 15px; + padding: 0; + user-select: none; + -webkit-user-select: none; +} +.sf-tabs-sm .tab-navigation { + box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 4px var(--page-background); + margin: 0 0 10px; +} +.tab-navigation .tab-control { + background: transparent; + border: 0; + box-shadow: none; + transition: box-shadow .05s ease-in, background-color .05s ease-in; + cursor: pointer; + font-size: 14px; + font-weight: 500; + line-height: 1.4; + margin: 0; + padding: 4px 14px; + position: relative; + text-align: center; + z-index: 1; +} +.sf-tabs-sm .tab-navigation .tab-control { + font-size: 13px; + padding: 2.5px 10px; +} +.tab-navigation .tab-control:before { + background: var(--tab-border-color); + bottom: 15%; + content: ""; + left: 0; + position: absolute; + top: 15%; + width: 1px; +} +.tab-navigation .tab-control:first-child:before, +.tab-navigation .tab-control.active + .tab-control:before, +.tab-navigation .tab-control.active:before { + width: 0; +} +.tab-navigation .tab-control .badge { + background: var(--selected-badge-background); + box-shadow: var(--selected-badge-shadow); + color: var(--selected-badge-color); + display: inline-block; + font-size: 12px; + font-weight: bold; + line-height: 1; + margin-left: 8px; + min-width: 10px; + padding: 2px 6px; + text-align: center; + white-space: nowrap; +} +.tab-navigation .tab-control.disabled { + color: var(--tab-disabled-color); +} +.tab-navigation .tab-control.active { + background-color: var(--tab-active-background); + border-radius: 6px; + box-shadow: inset 0 0 0 1.5px var(--tab-active-border-color); + color: var(--tab-active-color); + position: relative; + z-index: 1; +} +.theme-dark .tab-navigation li.active { + box-shadow: inset 0 0 0 1px var(--tab-border-color); +} +.tab-content > *:first-child { + margin-top: 0; +} +.tab-navigation .tab-control .badge.status-warning { + background: var(--selected-badge-warning-background); + box-shadow: var(--selected-badge-warning-shadow); + color: var(--selected-badge-warning-color); +} +.tab-navigation .tab-control .badge.status-error { + background: var(--selected-badge-danger-background); + box-shadow: var(--selected-badge-danger-shadow); + color: var(--selected-badge-danger-color); +} + .sf-tabs .tab:not(:first-child) { display: none; } [data-filters] { position: relative; } @@ -208,6 +311,10 @@ header .container { display: flex; justify-content: space-between; } .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } .exception-message a:hover { border-bottom-color: #ffffff; } +.exception-properties-wrapper { margin: .8em 0; } +.exception-properties { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); } +.exception-properties pre { margin: 0; padding: 0.2em 0; } + .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } .trace + .trace { margin-top: 30px; } @@ -217,7 +324,7 @@ header .container { display: flex; justify-content: space-between; } .trace-head .icon { position: absolute; right: 0; top: 0; } .trace-head .icon svg { fill: var(--base-5); height: 24px; width: 24px; } -.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; } +.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 0 0 1em; table-layout: fixed; } .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } diff --git a/lib/symfony/error-handler/Resources/assets/js/exception.js b/lib/symfony/error-handler/Resources/assets/js/exception.js index a85409da3..22ce675df 100644 --- a/lib/symfony/error-handler/Resources/assets/js/exception.js +++ b/lib/symfony/error-handler/Resources/assets/js/exception.js @@ -1,297 +1,304 @@ /* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. If you make any change in this file, verify the same change is needed in the other file. */ /* .tab'); + var tabNavigation = document.createElement('div'); + tabNavigation.className = 'tab-navigation'; + tabNavigation.setAttribute('role', 'tablist'); + + var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ + for (var j = 0; j < tabs.length; j++) { + var tabId = 'tab-' + i + '-' + j; + var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; + + var tabNavigationItem = document.createElement('button'); + addClass(tabNavigationItem, 'tab-control'); + tabNavigationItem.setAttribute('data-tab-id', tabId); + tabNavigationItem.setAttribute('role', 'tab'); + tabNavigationItem.setAttribute('aria-controls', tabId); + if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } + if (hasClass(tabs[j], 'disabled')) { + addClass(tabNavigationItem, 'disabled'); + } + tabNavigationItem.innerHTML = tabTitle; + tabNavigation.appendChild(tabNavigationItem); + + var tabContent = tabs[j].querySelector('.tab-content'); + tabContent.parentElement.setAttribute('id', tabId); + } + + tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); + addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); } - var addEventListener; + /* display the active tab and add the 'click' event listeners */ + for (i = 0; i < tabGroups.length; i++) { + tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation .tab-control'); - var el = document.createElement('div'); - if (!('addEventListener' in el)) { - addEventListener = function (element, eventName, callback) { - element.attachEvent('on' + eventName, callback); - }; - } else { - addEventListener = function (element, eventName, callback) { - element.addEventListener(eventName, callback, false); - }; - } + for (j = 0; j < tabNavigation.length; j++) { + tabId = tabNavigation[j].getAttribute('data-tab-id'); + var tabPanel = document.getElementById(tabId); + tabPanel.setAttribute('role', 'tabpanel'); + tabPanel.setAttribute('aria-labelledby', tabId); + tabPanel.querySelector('.tab-title').className = 'hidden'; - if (navigator.clipboard) { - document.querySelectorAll('[data-clipboard-text]').forEach(function(element) { - removeClass(element, 'hidden'); - element.addEventListener('click', function() { - navigator.clipboard.writeText(element.getAttribute('data-clipboard-text')); - }) - }); - } - - return { - addEventListener: addEventListener, - - createTabs: function() { - var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])'); - - /* create the tab navigation for each group of tabs */ - for (var i = 0; i < tabGroups.length; i++) { - var tabs = tabGroups[i].querySelectorAll(':scope > .tab'); - var tabNavigation = document.createElement('ul'); - tabNavigation.className = 'tab-navigation'; - - var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ - for (var j = 0; j < tabs.length; j++) { - var tabId = 'tab-' + i + '-' + j; - var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; - - var tabNavigationItem = document.createElement('li'); - tabNavigationItem.setAttribute('data-tab-id', tabId); - if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } - if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } - tabNavigationItem.innerHTML = tabTitle; - tabNavigation.appendChild(tabNavigationItem); - - var tabContent = tabs[j].querySelector('.tab-content'); - tabContent.parentElement.setAttribute('id', tabId); - } - - tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); - addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); + if (hasClass(tabNavigation[j], 'active')) { + tabPanel.className = 'block'; + tabNavigation[j].setAttribute('aria-selected', 'true'); + tabNavigation[j].removeAttribute('tabindex'); + } else { + tabPanel.className = 'hidden'; + tabNavigation[j].removeAttribute('aria-selected'); + tabNavigation[j].setAttribute('tabindex', '-1'); } - /* display the active tab and add the 'click' event listeners */ - for (i = 0; i < tabGroups.length; i++) { - tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li'); + tabNavigation[j].addEventListener('click', function(e) { + var activeTab = e.target || e.srcElement; - for (j = 0; j < tabNavigation.length; j++) { - tabId = tabNavigation[j].getAttribute('data-tab-id'); - document.getElementById(tabId).querySelector('.tab-title').className = 'hidden'; - - if (hasClass(tabNavigation[j], 'active')) { - document.getElementById(tabId).className = 'block'; - } else { - document.getElementById(tabId).className = 'hidden'; - } - - tabNavigation[j].addEventListener('click', function(e) { - var activeTab = e.target || e.srcElement; - - /* needed because when the tab contains HTML contents, user can click */ - /* on any of those elements instead of their parent '
  • ' element */ - while (activeTab.tagName.toLowerCase() !== 'li') { - activeTab = activeTab.parentNode; - } - - /* get the full list of tabs through the parent of the active tab element */ - var tabNavigation = activeTab.parentNode.children; - for (var k = 0; k < tabNavigation.length; k++) { - var tabId = tabNavigation[k].getAttribute('data-tab-id'); - document.getElementById(tabId).className = 'hidden'; - removeClass(tabNavigation[k], 'active'); - } - - addClass(activeTab, 'active'); - var activeTabId = activeTab.getAttribute('data-tab-id'); - document.getElementById(activeTabId).className = 'block'; - }); + /* needed because when the tab contains HTML contents, user can click */ + /* on any of those elements instead of their parent '
  • -

    formatFileFromText(nl2br($exceptionMessage)); ?>

    @@ -35,7 +34,7 @@ $last = $exceptionAsArrayCount - 1; foreach ($exceptionAsArray as $i => $e) { foreach ($e['trace'] as $trace) { - if ($trace['file'] && false === mb_strpos($trace['file'], '/vendor/') && false === mb_strpos($trace['file'], '/var/cache/') && $i < $last) { + if ($trace['file'] && !str_contains($trace['file'], '/vendor/') && !str_contains($trace['file'], '/var/cache/') && $i < $last) { $exceptionWithUserCode[] = $i; } } diff --git a/lib/symfony/error-handler/Resources/views/exception_full.html.php b/lib/symfony/error-handler/Resources/views/exception_full.html.php index 04f0fd579..9d5f6e336 100644 --- a/lib/symfony/error-handler/Resources/views/exception_full.html.php +++ b/lib/symfony/error-handler/Resources/views/exception_full.html.php @@ -2,9 +2,9 @@ - - - + + + <?= $_message; ?> diff --git a/lib/symfony/error-handler/Resources/views/trace.html.php b/lib/symfony/error-handler/Resources/views/trace.html.php index 8dfdb4ec8..aaf6be1e4 100644 --- a/lib/symfony/error-handler/Resources/views/trace.html.php +++ b/lib/symfony/error-handler/Resources/views/trace.html.php @@ -11,7 +11,7 @@ getFileLink($trace['file'], $lineNumber); + $fileLink = $this->fileLinkFormat->format($trace['file'], $lineNumber); $filePath = strtr(strip_tags($this->formatFile($trace['file'], $lineNumber)), [' at line '.$lineNumber => '']); $filePathParts = explode(\DIRECTORY_SEPARATOR, $filePath); ?> diff --git a/lib/symfony/error-handler/Resources/views/traces.html.php b/lib/symfony/error-handler/Resources/views/traces.html.php index f64d91713..fd834c144 100644 --- a/lib/symfony/error-handler/Resources/views/traces.html.php +++ b/lib/symfony/error-handler/Resources/views/traces.html.php @@ -12,7 +12,7 @@ $class = substr($exception['class'], $separator); ?> -
    +

    @@ -25,13 +25,21 @@

    escape($exception['message']); ?>

    + +
    + Show exception properties +
    + dumpValue($exception['data']) ?> +
    +
    +
    $trace) { - $isVendorTrace = $trace['file'] && (false !== mb_strpos($trace['file'], '/vendor/') || false !== mb_strpos($trace['file'], '/var/cache/')); + $isVendorTrace = $trace['file'] && (str_contains($trace['file'], '/vendor/') || str_contains($trace['file'], '/var/cache/')); $displayCodeSnippet = $isFirstUserCode && !$isVendorTrace; if ($displayCodeSnippet) { $isFirstUserCode = false; diff --git a/lib/symfony/error-handler/ThrowableUtils.php b/lib/symfony/error-handler/ThrowableUtils.php index 18d04988a..f8a6a9d8f 100644 --- a/lib/symfony/error-handler/ThrowableUtils.php +++ b/lib/symfony/error-handler/ThrowableUtils.php @@ -18,10 +18,7 @@ use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; */ class ThrowableUtils { - /** - * @param SilencedErrorContext|\Throwable - */ - public static function getSeverity($throwable): int + public static function getSeverity(SilencedErrorContext|\Throwable $throwable): int { if ($throwable instanceof \ErrorException || $throwable instanceof SilencedErrorContext) { return $throwable->getSeverity(); diff --git a/lib/symfony/error-handler/composer.json b/lib/symfony/error-handler/composer.json index bc0d88e9d..9affd4da5 100644 --- a/lib/symfony/error-handler/composer.json +++ b/lib/symfony/error-handler/composer.json @@ -16,14 +16,18 @@ } ], "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0", - "symfony/deprecation-contracts": "^2.1|^3" + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" }, diff --git a/lib/symfony/event-dispatcher-contracts/.gitignore b/lib/symfony/event-dispatcher-contracts/.gitignore deleted file mode 100644 index c49a5d8df..000000000 --- a/lib/symfony/event-dispatcher-contracts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/event-dispatcher-contracts/Event.php b/lib/symfony/event-dispatcher-contracts/Event.php index 46dcb2ba0..2e7f99890 100644 --- a/lib/symfony/event-dispatcher-contracts/Event.php +++ b/lib/symfony/event-dispatcher-contracts/Event.php @@ -30,11 +30,8 @@ use Psr\EventDispatcher\StoppableEventInterface; */ class Event implements StoppableEventInterface { - private $propagationStopped = false; + private bool $propagationStopped = false; - /** - * {@inheritdoc} - */ public function isPropagationStopped(): bool { return $this->propagationStopped; diff --git a/lib/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/lib/symfony/event-dispatcher-contracts/EventDispatcherInterface.php index 351dc5131..610d6ac06 100644 --- a/lib/symfony/event-dispatcher-contracts/EventDispatcherInterface.php +++ b/lib/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -21,11 +21,13 @@ interface EventDispatcherInterface extends PsrEventDispatcherInterface /** * Dispatches an event to all registered listeners. * - * @param object $event The event to pass to the event handlers/listeners + * @template T of object + * + * @param T $event The event to pass to the event handlers/listeners * @param string|null $eventName The name of the event to dispatch. If not supplied, * the class of $event should be used instead. * - * @return object The passed $event MUST be returned + * @return T The passed $event MUST be returned */ public function dispatch(object $event, string $eventName = null): object; } diff --git a/lib/symfony/event-dispatcher-contracts/LICENSE b/lib/symfony/event-dispatcher-contracts/LICENSE index 74cdc2dbf..7536caeae 100644 --- a/lib/symfony/event-dispatcher-contracts/LICENSE +++ b/lib/symfony/event-dispatcher-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2022 Fabien Potencier +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/event-dispatcher-contracts/README.md b/lib/symfony/event-dispatcher-contracts/README.md index b1ab4c00c..332b961cb 100644 --- a/lib/symfony/event-dispatcher-contracts/README.md +++ b/lib/symfony/event-dispatcher-contracts/README.md @@ -3,7 +3,7 @@ Symfony EventDispatcher Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/lib/symfony/event-dispatcher-contracts/composer.json b/lib/symfony/event-dispatcher-contracts/composer.json index 660df81a0..3618d53e9 100644 --- a/lib/symfony/event-dispatcher-contracts/composer.json +++ b/lib/symfony/event-dispatcher-contracts/composer.json @@ -16,19 +16,16 @@ } ], "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/lib/symfony/event-dispatcher/CHANGELOG.md b/lib/symfony/event-dispatcher/CHANGELOG.md index 0f9859895..76b2eab6b 100644 --- a/lib/symfony/event-dispatcher/CHANGELOG.md +++ b/lib/symfony/event-dispatcher/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.0 +--- + + * Remove `LegacyEventDispatcherProxy` + 5.4 --- diff --git a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php index acfbf619c..e25a664d2 100644 --- a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php +++ b/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -13,6 +13,7 @@ namespace Symfony\Component\EventDispatcher\Debug; use Psr\EventDispatcher\StoppableEventInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; @@ -33,35 +34,33 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa protected $stopwatch; /** - * @var \SplObjectStorage + * @var \SplObjectStorage|null */ - private $callStack; - private $dispatcher; - private $wrappedListeners; - private $orphanedEvents; - private $requestStack; - private $currentRequestHash = ''; + private ?\SplObjectStorage $callStack = null; + private EventDispatcherInterface $dispatcher; + private array $wrappedListeners = []; + private array $orphanedEvents = []; + private ?RequestStack $requestStack; + private string $currentRequestHash = ''; public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null) { $this->dispatcher = $dispatcher; $this->stopwatch = $stopwatch; $this->logger = $logger; - $this->wrappedListeners = []; - $this->orphanedEvents = []; $this->requestStack = $requestStack; } /** - * {@inheritdoc} + * @return void */ - public function addListener(string $eventName, $listener, int $priority = 0) + public function addListener(string $eventName, callable|array $listener, int $priority = 0) { $this->dispatcher->addListener($eventName, $listener, $priority); } /** - * {@inheritdoc} + * @return void */ public function addSubscriber(EventSubscriberInterface $subscriber) { @@ -69,9 +68,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa } /** - * {@inheritdoc} + * @return void */ - public function removeListener(string $eventName, $listener) + public function removeListener(string $eventName, callable|array $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { @@ -83,29 +82,23 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa } } - return $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); } /** - * {@inheritdoc} + * @return void */ public function removeSubscriber(EventSubscriberInterface $subscriber) { - return $this->dispatcher->removeSubscriber($subscriber); + $this->dispatcher->removeSubscriber($subscriber); } - /** - * {@inheritdoc} - */ - public function getListeners(string $eventName = null) + public function getListeners(string $eventName = null): array { return $this->dispatcher->getListeners($eventName); } - /** - * {@inheritdoc} - */ - public function getListenerPriority(string $eventName, $listener) + public function getListenerPriority(string $eventName, callable|array $listener): ?int { // we might have wrapped listeners for the event (if called while dispatching) // in that case get the priority by wrapper @@ -120,24 +113,16 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa return $this->dispatcher->getListenerPriority($eventName, $listener); } - /** - * {@inheritdoc} - */ - public function hasListeners(string $eventName = null) + public function hasListeners(string $eventName = null): bool { return $this->dispatcher->hasListeners($eventName); } - /** - * {@inheritdoc} - */ public function dispatch(object $event, string $eventName = null): object { - $eventName = $eventName ?? \get_class($event); + $eventName ??= $event::class; - if (null === $this->callStack) { - $this->callStack = new \SplObjectStorage(); - } + $this->callStack ??= new \SplObjectStorage(); $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; @@ -168,10 +153,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa return $event; } - /** - * @return array - */ - public function getCalledListeners(Request $request = null) + public function getCalledListeners(Request $request = null): array { if (null === $this->callStack) { return []; @@ -189,17 +171,12 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa return $called; } - /** - * @return array - */ - public function getNotCalledListeners(Request $request = null) + public function getNotCalledListeners(Request $request = null): array { try { - $allListeners = $this->getListeners(); + $allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority(); } catch (\Exception $e) { - if (null !== $this->logger) { - $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); - } + $this->logger?->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); // unable to retrieve the uncalled listeners return []; @@ -219,18 +196,19 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa } $notCalled = []; + foreach ($allListeners as $eventName => $listeners) { - foreach ($listeners as $listener) { + foreach ($listeners as [$listener, $priority]) { if (!\in_array($listener, $calledListeners, true)) { if (!$listener instanceof WrappedListener) { - $listener = new WrappedListener($listener, null, $this->stopwatch, $this); + $listener = new WrappedListener($listener, null, $this->stopwatch, $this, $priority); } $notCalled[] = $listener->getInfo($eventName); } } } - uasort($notCalled, [$this, 'sortNotCalledListeners']); + uasort($notCalled, $this->sortNotCalledListeners(...)); return $notCalled; } @@ -248,6 +226,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa return array_merge(...array_values($this->orphanedEvents)); } + /** + * @return void + */ public function reset() { $this->callStack = null; @@ -260,16 +241,16 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa * * @param string $method The method name * @param array $arguments The method arguments - * - * @return mixed */ - public function __call(string $method, array $arguments) + public function __call(string $method, array $arguments): mixed { return $this->dispatcher->{$method}(...$arguments); } /** * Called before dispatching the event. + * + * @return void */ protected function beforeDispatch(string $eventName, object $event) { @@ -277,6 +258,8 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa /** * Called after dispatching the event. + * + * @return void */ protected function afterDispatch(string $eventName, object $event) { @@ -318,9 +301,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa } if ($listener->wasCalled()) { - if (null !== $this->logger) { - $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); - } + $this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context); } else { $this->callStack->detach($listener); } @@ -330,16 +311,14 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa } if ($listener->stoppedPropagation()) { - if (null !== $this->logger) { - $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); - } + $this->logger?->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); $skipped = true; } } } - private function sortNotCalledListeners(array $a, array $b) + private function sortNotCalledListeners(array $a, array $b): int { if (0 !== $cmp = strcmp($a['event'], $b['event'])) { return $cmp; @@ -363,4 +342,34 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa return 1; } + + private function getListenersWithPriority(): array + { + $result = []; + + $allListeners = new \ReflectionProperty(EventDispatcher::class, 'listeners'); + + foreach ($allListeners->getValue($this->dispatcher) as $eventName => $listenersByPriority) { + foreach ($listenersByPriority as $priority => $listeners) { + foreach ($listeners as $listener) { + $result[$eventName][] = [$listener, $priority]; + } + } + } + + return $result; + } + + private function getListenersWithoutPriority(): array + { + $result = []; + + foreach ($this->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + $result[$eventName][] = [$listener, null]; + } + } + + return $result; + } } diff --git a/lib/symfony/event-dispatcher/Debug/WrappedListener.php b/lib/symfony/event-dispatcher/Debug/WrappedListener.php index 3916716ec..6e0de1dff 100644 --- a/lib/symfony/event-dispatcher/Debug/WrappedListener.php +++ b/lib/symfony/event-dispatcher/Debug/WrappedListener.php @@ -21,35 +21,36 @@ use Symfony\Component\VarDumper\Caster\ClassStub; */ final class WrappedListener { - private $listener; - private $optimizedListener; - private $name; - private $called; - private $stoppedPropagation; - private $stopwatch; - private $dispatcher; - private $pretty; - private $stub; - private $priority; - private static $hasClassStub; + private string|array|object $listener; + private ?\Closure $optimizedListener; + private string $name; + private bool $called = false; + private bool $stoppedPropagation = false; + private Stopwatch $stopwatch; + private ?EventDispatcherInterface $dispatcher; + private string $pretty; + private string $callableRef; + private ClassStub|string $stub; + private ?int $priority = null; + private static bool $hasClassStub; - public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null, int $priority = null) { $this->listener = $listener; - $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null); $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; - $this->called = false; - $this->stoppedPropagation = false; + $this->priority = $priority; if (\is_array($listener)) { - $this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0]; + [$this->name, $this->callableRef] = $this->parseListener($listener); $this->pretty = $this->name.'::'.$listener[1]; + $this->callableRef .= '::'.$listener[1]; } elseif ($listener instanceof \Closure) { $r = new \ReflectionFunction($listener); if (str_contains($r->name, '{closure}')) { $this->pretty = $this->name = 'closure'; - } elseif ($class = $r->getClosureScopeClass()) { + } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $this->name = $class->name; $this->pretty = $this->name.'::'.$r->name; } else { @@ -60,18 +61,17 @@ final class WrappedListener } else { $this->name = get_debug_type($listener); $this->pretty = $this->name.'::__invoke'; + $this->callableRef = $listener::class.'::__invoke'; } if (null !== $name) { $this->name = $name; } - if (null === self::$hasClassStub) { - self::$hasClassStub = class_exists(ClassStub::class); - } + self::$hasClassStub ??= class_exists(ClassStub::class); } - public function getWrappedListener() + public function getWrappedListener(): callable|array { return $this->listener; } @@ -93,13 +93,11 @@ final class WrappedListener public function getInfo(string $eventName): array { - if (null === $this->stub) { - $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()'; - } + $this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->callableRef ?? $this->listener) : $this->pretty.'()'; return [ 'event' => $eventName, - 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null), + 'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener), 'pretty' => $this->pretty, 'stub' => $this->stub, ]; @@ -110,18 +108,37 @@ final class WrappedListener $dispatcher = $this->dispatcher ?: $dispatcher; $this->called = true; - $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener); + $this->priority ??= $dispatcher->getListenerPriority($eventName, $this->listener); $e = $this->stopwatch->start($this->name, 'event_listener'); - ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); - - if ($e->isStarted()) { - $e->stop(); + try { + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); + } finally { + if ($e->isStarted()) { + $e->stop(); + } } if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { $this->stoppedPropagation = true; } } + + private function parseListener(array $listener): array + { + if ($listener[0] instanceof \Closure) { + foreach ((new \ReflectionFunction($listener[0]))->getAttributes(\Closure::class) as $attribute) { + if ($name = $attribute->getArguments()['name'] ?? false) { + return [$name, $attribute->getArguments()['class'] ?? $name]; + } + } + } + + if (\is_object($listener[0])) { + return [get_debug_type($listener[0]), $listener[0]::class]; + } + + return [$listener[0], $listener[0]]; + } } diff --git a/lib/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/lib/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php index 6e7292b4a..13b4336aa 100644 --- a/lib/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php +++ b/lib/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php @@ -21,25 +21,19 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class AddEventAliasesPass implements CompilerPassInterface { - private $eventAliases; - private $eventAliasesParameter; + private array $eventAliases; - public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') + public function __construct(array $eventAliases) { - if (1 < \func_num_args()) { - trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - $this->eventAliases = $eventAliases; - $this->eventAliasesParameter = $eventAliasesParameter; } public function process(ContainerBuilder $container): void { - $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; + $eventAliases = $container->hasParameter('event_dispatcher.event_aliases') ? $container->getParameter('event_dispatcher.event_aliases') : []; $container->setParameter( - $this->eventAliasesParameter, + 'event_dispatcher.event_aliases', array_merge($eventAliases, $this->eventAliases) ); } diff --git a/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php index 8eabe7d74..866f4e64f 100644 --- a/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php +++ b/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -25,84 +25,58 @@ use Symfony\Contracts\EventDispatcher\Event; */ class RegisterListenersPass implements CompilerPassInterface { - protected $dispatcherService; - protected $listenerTag; - protected $subscriberTag; - protected $eventAliasesParameter; - - private $hotPathEvents = []; - private $hotPathTagName = 'container.hot_path'; - private $noPreloadEvents = []; - private $noPreloadTagName = 'container.no_preload'; - - public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->dispatcherService = $dispatcherService; - $this->listenerTag = $listenerTag; - $this->subscriberTag = $subscriberTag; - $this->eventAliasesParameter = $eventAliasesParameter; - } + private array $hotPathEvents = []; + private array $noPreloadEvents = []; /** * @return $this */ - public function setHotPathEvents(array $hotPathEvents) + public function setHotPathEvents(array $hotPathEvents): static { $this->hotPathEvents = array_flip($hotPathEvents); - if (1 < \func_num_args()) { - trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); - $this->hotPathTagName = func_get_arg(1); - } - return $this; } /** * @return $this */ - public function setNoPreloadEvents(array $noPreloadEvents): self + public function setNoPreloadEvents(array $noPreloadEvents): static { $this->noPreloadEvents = array_flip($noPreloadEvents); - if (1 < \func_num_args()) { - trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); - $this->noPreloadTagName = func_get_arg(1); - } - return $this; } + /** + * @return void + */ public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) { return; } $aliases = []; - if ($container->hasParameter($this->eventAliasesParameter)) { - $aliases = $container->getParameter($this->eventAliasesParameter); + if ($container->hasParameter('event_dispatcher.event_aliases')) { + $aliases = $container->getParameter('event_dispatcher.event_aliases'); } - $globalDispatcherDefinition = $container->findDefinition($this->dispatcherService); + $globalDispatcherDefinition = $container->findDefinition('event_dispatcher'); - foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) { $noPreload = 0; foreach ($events as $event) { $priority = $event['priority'] ?? 0; if (!isset($event['event'])) { - if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { + if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) { continue; } - $event['method'] = $event['method'] ?? '__invoke'; + $event['method'] ??= '__invoke'; $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); } @@ -112,36 +86,40 @@ class RegisterListenersPass implements CompilerPassInterface $event['method'] = 'on'.preg_replace_callback([ '/(?<=\b|_)[a-z]/i', '/[^a-z0-9]/i', - ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); + ], fn ($matches) => strtoupper($matches[0]), $event['event']); $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); - if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { + if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method'])) { + if (!$r->hasMethod('__invoke')) { + throw new InvalidArgumentException(sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id)); + } + $event['method'] = '__invoke'; } } $dispatcherDefinition = $globalDispatcherDefinition; if (isset($event['dispatcher'])) { - $dispatcherDefinition = $container->getDefinition($event['dispatcher']); + $dispatcherDefinition = $container->findDefinition($event['dispatcher']); } $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); if (isset($this->hotPathEvents[$event['event']])) { - $container->getDefinition($id)->addTag($this->hotPathTagName); + $container->getDefinition($id)->addTag('container.hot_path'); } elseif (isset($this->noPreloadEvents[$event['event']])) { ++$noPreload; } } if ($noPreload && \count($events) === $noPreload) { - $container->getDefinition($id)->addTag($this->noPreloadTagName); + $container->getDefinition($id)->addTag('container.no_preload'); } } $extractingDispatcher = new ExtractingEventDispatcher(); - foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $tags) { + foreach ($container->findTaggedServiceIds('kernel.event_subscriber', true) as $id => $tags) { $def = $container->getDefinition($id); // We must assume that the class value has been correctly filled, even if the service is created by a factory @@ -161,7 +139,7 @@ class RegisterListenersPass implements CompilerPassInterface continue; } - $dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']); + $dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($attributes['dispatcher']); } if (!$dispatcherDefinitions) { @@ -179,13 +157,13 @@ class RegisterListenersPass implements CompilerPassInterface } if (isset($this->hotPathEvents[$args[0]])) { - $container->getDefinition($id)->addTag($this->hotPathTagName); + $container->getDefinition($id)->addTag('container.hot_path'); } elseif (isset($this->noPreloadEvents[$args[0]])) { ++$noPreload; } } if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { - $container->getDefinition($id)->addTag($this->noPreloadTagName); + $container->getDefinition($id)->addTag('container.no_preload'); } $extractingDispatcher->listeners = []; ExtractingEventDispatcher::$aliases = []; @@ -203,7 +181,7 @@ class RegisterListenersPass implements CompilerPassInterface || $type->isBuiltin() || Event::class === ($name = $type->getName()) ) { - throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); } return $name; @@ -215,12 +193,12 @@ class RegisterListenersPass implements CompilerPassInterface */ class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface { - public $listeners = []; + public array $listeners = []; - public static $aliases = []; - public static $subscriber; + public static array $aliases = []; + public static string $subscriber; - public function addListener(string $eventName, $listener, int $priority = 0) + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void { $this->listeners[] = [$eventName, $listener[1], $priority]; } diff --git a/lib/symfony/event-dispatcher/EventDispatcher.php b/lib/symfony/event-dispatcher/EventDispatcher.php index 8fe8fb5c2..327803af6 100644 --- a/lib/symfony/event-dispatcher/EventDispatcher.php +++ b/lib/symfony/event-dispatcher/EventDispatcher.php @@ -31,9 +31,9 @@ use Symfony\Component\EventDispatcher\Debug\WrappedListener; */ class EventDispatcher implements EventDispatcherInterface { - private $listeners = []; - private $sorted = []; - private $optimized; + private array $listeners = []; + private array $sorted = []; + private array $optimized; public function __construct() { @@ -42,14 +42,11 @@ class EventDispatcher implements EventDispatcherInterface } } - /** - * {@inheritdoc} - */ public function dispatch(object $event, string $eventName = null): object { - $eventName = $eventName ?? \get_class($event); + $eventName ??= $event::class; - if (null !== $this->optimized) { + if (isset($this->optimized)) { $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); } else { $listeners = $this->getListeners($eventName); @@ -62,10 +59,7 @@ class EventDispatcher implements EventDispatcherInterface return $event; } - /** - * {@inheritdoc} - */ - public function getListeners(string $eventName = null) + public function getListeners(string $eventName = null): array { if (null !== $eventName) { if (empty($this->listeners[$eventName])) { @@ -88,10 +82,7 @@ class EventDispatcher implements EventDispatcherInterface return array_filter($this->sorted); } - /** - * {@inheritdoc} - */ - public function getListenerPriority(string $eventName, $listener) + public function getListenerPriority(string $eventName, callable|array $listener): ?int { if (empty($this->listeners[$eventName])) { return null; @@ -99,14 +90,14 @@ class EventDispatcher implements EventDispatcherInterface if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); - $listener[1] = $listener[1] ?? '__invoke'; + $listener[1] ??= '__invoke'; } foreach ($this->listeners[$eventName] as $priority => &$listeners) { foreach ($listeners as &$v) { if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); - $v[1] = $v[1] ?? '__invoke'; + $v[1] ??= '__invoke'; } if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { return $priority; @@ -117,10 +108,7 @@ class EventDispatcher implements EventDispatcherInterface return null; } - /** - * {@inheritdoc} - */ - public function hasListeners(string $eventName = null) + public function hasListeners(string $eventName = null): bool { if (null !== $eventName) { return !empty($this->listeners[$eventName]); @@ -136,18 +124,18 @@ class EventDispatcher implements EventDispatcherInterface } /** - * {@inheritdoc} + * @return void */ - public function addListener(string $eventName, $listener, int $priority = 0) + public function addListener(string $eventName, callable|array $listener, int $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName], $this->optimized[$eventName]); } /** - * {@inheritdoc} + * @return void */ - public function removeListener(string $eventName, $listener) + public function removeListener(string $eventName, callable|array $listener) { if (empty($this->listeners[$eventName])) { return; @@ -155,14 +143,14 @@ class EventDispatcher implements EventDispatcherInterface if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); - $listener[1] = $listener[1] ?? '__invoke'; + $listener[1] ??= '__invoke'; } foreach ($this->listeners[$eventName] as $priority => &$listeners) { foreach ($listeners as $k => &$v) { if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); - $v[1] = $v[1] ?? '__invoke'; + $v[1] ??= '__invoke'; } if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); @@ -176,7 +164,7 @@ class EventDispatcher implements EventDispatcherInterface } /** - * {@inheritdoc} + * @return void */ public function addSubscriber(EventSubscriberInterface $subscriber) { @@ -194,7 +182,7 @@ class EventDispatcher implements EventDispatcherInterface } /** - * {@inheritdoc} + * @return void */ public function removeSubscriber(EventSubscriberInterface $subscriber) { @@ -218,6 +206,8 @@ class EventDispatcher implements EventDispatcherInterface * @param callable[] $listeners The event listeners * @param string $eventName The name of the event to dispatch * @param object $event The event object to pass to the event handlers/listeners + * + * @return void */ protected function callListeners(iterable $listeners, string $eventName, object $event) { @@ -234,16 +224,16 @@ class EventDispatcher implements EventDispatcherInterface /** * Sorts the internal list of listeners for the given event by priority. */ - private function sortListeners(string $eventName) + private function sortListeners(string $eventName): void { krsort($this->listeners[$eventName]); $this->sorted[$eventName] = []; foreach ($this->listeners[$eventName] as &$listeners) { - foreach ($listeners as $k => &$listener) { + foreach ($listeners as &$listener) { if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); - $listener[1] = $listener[1] ?? '__invoke'; + $listener[1] ??= '__invoke'; } $this->sorted[$eventName][] = $listener; } @@ -265,12 +255,12 @@ class EventDispatcher implements EventDispatcherInterface $closure = static function (...$args) use (&$listener, &$closure) { if ($listener[0] instanceof \Closure) { $listener[0] = $listener[0](); - $listener[1] = $listener[1] ?? '__invoke'; + $listener[1] ??= '__invoke'; } - ($closure = \Closure::fromCallable($listener))(...$args); + ($closure = $listener(...))(...$args); }; } else { - $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); + $closure = $listener instanceof WrappedListener ? $listener : $listener(...); } } } diff --git a/lib/symfony/event-dispatcher/EventDispatcherInterface.php b/lib/symfony/event-dispatcher/EventDispatcherInterface.php index cc324e1c6..3cd94c938 100644 --- a/lib/symfony/event-dispatcher/EventDispatcherInterface.php +++ b/lib/symfony/event-dispatcher/EventDispatcherInterface.php @@ -27,6 +27,8 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface * * @param int $priority The higher this value, the earlier an event * listener will be triggered in the chain (defaults to 0) + * + * @return void */ public function addListener(string $eventName, callable $listener, int $priority = 0); @@ -35,14 +37,21 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface * * The subscriber is asked for all the events it is * interested in and added as a listener for these events. + * + * @return void */ public function addSubscriber(EventSubscriberInterface $subscriber); /** * Removes an event listener from the specified events. + * + * @return void */ public function removeListener(string $eventName, callable $listener); + /** + * @return void + */ public function removeSubscriber(EventSubscriberInterface $subscriber); /** @@ -50,21 +59,17 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface * * @return array */ - public function getListeners(string $eventName = null); + public function getListeners(string $eventName = null): array; /** * Gets the listener priority for a specific event. * * Returns null if the event or the listener does not exist. - * - * @return int|null */ - public function getListenerPriority(string $eventName, callable $listener); + public function getListenerPriority(string $eventName, callable $listener): ?int; /** * Checks whether an event has any registered listeners. - * - * @return bool */ - public function hasListeners(string $eventName = null); + public function hasListeners(string $eventName = null): bool; } diff --git a/lib/symfony/event-dispatcher/GenericEvent.php b/lib/symfony/event-dispatcher/GenericEvent.php index b32a301ae..68a203063 100644 --- a/lib/symfony/event-dispatcher/GenericEvent.php +++ b/lib/symfony/event-dispatcher/GenericEvent.php @@ -34,7 +34,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * @param mixed $subject The subject of the event, usually an object or a callable * @param array $arguments Arguments to store in the event */ - public function __construct($subject = null, array $arguments = []) + public function __construct(mixed $subject = null, array $arguments = []) { $this->subject = $subject; $this->arguments = $arguments; @@ -42,10 +42,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Getter for subject property. - * - * @return mixed */ - public function getSubject() + public function getSubject(): mixed { return $this->subject; } @@ -53,11 +51,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Get argument by key. * - * @return mixed - * * @throws \InvalidArgumentException if key is not found */ - public function getArgument(string $key) + public function getArgument(string $key): mixed { if ($this->hasArgument($key)) { return $this->arguments[$key]; @@ -69,11 +65,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Add argument to event. * - * @param mixed $value Value - * * @return $this */ - public function setArgument(string $key, $value) + public function setArgument(string $key, mixed $value): static { $this->arguments[$key] = $value; @@ -82,10 +76,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Getter for all arguments. - * - * @return array */ - public function getArguments() + public function getArguments(): array { return $this->arguments; } @@ -95,7 +87,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * * @return $this */ - public function setArguments(array $args = []) + public function setArguments(array $args = []): static { $this->arguments = $args; @@ -104,10 +96,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Has argument. - * - * @return bool */ - public function hasArgument(string $key) + public function hasArgument(string $key): bool { return \array_key_exists($key, $this->arguments); } @@ -117,12 +107,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * * @param string $key Array key * - * @return mixed - * * @throws \InvalidArgumentException if key does not exist in $this->args */ - #[\ReturnTypeWillChange] - public function offsetGet($key) + public function offsetGet(mixed $key): mixed { return $this->getArgument($key); } @@ -130,13 +117,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * ArrayAccess for argument setter. * - * @param string $key Array key to set - * @param mixed $value Value - * - * @return void + * @param string $key Array key to set */ - #[\ReturnTypeWillChange] - public function offsetSet($key, $value) + public function offsetSet(mixed $key, mixed $value): void { $this->setArgument($key, $value); } @@ -145,11 +128,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * ArrayAccess for unset argument. * * @param string $key Array key - * - * @return void */ - #[\ReturnTypeWillChange] - public function offsetUnset($key) + public function offsetUnset(mixed $key): void { if ($this->hasArgument($key)) { unset($this->arguments[$key]); @@ -160,11 +140,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * ArrayAccess has argument. * * @param string $key Array key - * - * @return bool */ - #[\ReturnTypeWillChange] - public function offsetExists($key) + public function offsetExists(mixed $key): bool { return $this->hasArgument($key); } @@ -174,8 +151,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * * @return \ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->arguments); } diff --git a/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php b/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php index 568d79c3a..d385d3f83 100644 --- a/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php +++ b/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -18,31 +18,28 @@ namespace Symfony\Component\EventDispatcher; */ class ImmutableEventDispatcher implements EventDispatcherInterface { - private $dispatcher; + private EventDispatcherInterface $dispatcher; public function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } - /** - * {@inheritdoc} - */ public function dispatch(object $event, string $eventName = null): object { return $this->dispatcher->dispatch($event, $eventName); } /** - * {@inheritdoc} + * @return never */ - public function addListener(string $eventName, $listener, int $priority = 0) + public function addListener(string $eventName, callable|array $listener, int $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } /** - * {@inheritdoc} + * @return never */ public function addSubscriber(EventSubscriberInterface $subscriber) { @@ -50,41 +47,32 @@ class ImmutableEventDispatcher implements EventDispatcherInterface } /** - * {@inheritdoc} + * @return never */ - public function removeListener(string $eventName, $listener) + public function removeListener(string $eventName, callable|array $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } /** - * {@inheritdoc} + * @return never */ public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } - /** - * {@inheritdoc} - */ - public function getListeners(string $eventName = null) + public function getListeners(string $eventName = null): array { return $this->dispatcher->getListeners($eventName); } - /** - * {@inheritdoc} - */ - public function getListenerPriority(string $eventName, $listener) + public function getListenerPriority(string $eventName, callable|array $listener): ?int { return $this->dispatcher->getListenerPriority($eventName, $listener); } - /** - * {@inheritdoc} - */ - public function hasListeners(string $eventName = null) + public function hasListeners(string $eventName = null): bool { return $this->dispatcher->hasListeners($eventName); } diff --git a/lib/symfony/event-dispatcher/LICENSE b/lib/symfony/event-dispatcher/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/lib/symfony/event-dispatcher/LICENSE +++ b/lib/symfony/event-dispatcher/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/lib/symfony/event-dispatcher/LegacyEventDispatcherProxy.php deleted file mode 100644 index 6e17c8fcc..000000000 --- a/lib/symfony/event-dispatcher/LegacyEventDispatcherProxy.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\EventDispatcher; - -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; - -trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class); - -/** - * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). - * - * @author Nicolas Grekas - * - * @deprecated since Symfony 5.1 - */ -final class LegacyEventDispatcherProxy -{ - public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface - { - return $dispatcher; - } -} diff --git a/lib/symfony/event-dispatcher/composer.json b/lib/symfony/event-dispatcher/composer.json index 32b42e408..ff281afd6 100644 --- a/lib/symfony/event-dispatcher/composer.json +++ b/lib/symfony/event-dispatcher/composer.json @@ -16,31 +16,26 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "require-dev": { - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0", "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, diff --git a/lib/symfony/filesystem/Exception/IOException.php b/lib/symfony/filesystem/Exception/IOException.php index fea26e4dd..a3c544553 100644 --- a/lib/symfony/filesystem/Exception/IOException.php +++ b/lib/symfony/filesystem/Exception/IOException.php @@ -20,7 +20,7 @@ namespace Symfony\Component\Filesystem\Exception; */ class IOException extends \RuntimeException implements IOExceptionInterface { - private $path; + private ?string $path; public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null) { @@ -29,10 +29,7 @@ class IOException extends \RuntimeException implements IOExceptionInterface parent::__construct($message, $code, $previous); } - /** - * {@inheritdoc} - */ - public function getPath() + public function getPath(): ?string { return $this->path; } diff --git a/lib/symfony/filesystem/Exception/IOExceptionInterface.php b/lib/symfony/filesystem/Exception/IOExceptionInterface.php index 42829ab6c..90c71db88 100644 --- a/lib/symfony/filesystem/Exception/IOExceptionInterface.php +++ b/lib/symfony/filesystem/Exception/IOExceptionInterface.php @@ -20,8 +20,6 @@ interface IOExceptionInterface extends ExceptionInterface { /** * Returns the associated path for the exception. - * - * @return string|null */ - public function getPath(); + public function getPath(): ?string; } diff --git a/lib/symfony/filesystem/Filesystem.php b/lib/symfony/filesystem/Filesystem.php index fafd36492..78458d5b9 100644 --- a/lib/symfony/filesystem/Filesystem.php +++ b/lib/symfony/filesystem/Filesystem.php @@ -22,7 +22,7 @@ use Symfony\Component\Filesystem\Exception\IOException; */ class Filesystem { - private static $lastError; + private static ?string $lastError = null; /** * Copies a file. @@ -31,6 +31,8 @@ class Filesystem * If the target file is newer, it is overwritten only when the * $overwriteNewerFiles option is set to true. * + * @return void + * * @throws FileNotFoundException When originFile doesn't exist * @throws IOException When copy fails */ @@ -82,11 +84,11 @@ class Filesystem /** * Creates a directory recursively. * - * @param string|iterable $dirs The directory path + * @return void * * @throws IOException On any directory creation failure */ - public function mkdir($dirs, int $mode = 0777) + public function mkdir(string|iterable $dirs, int $mode = 0777) { foreach ($this->toIterable($dirs) as $dir) { if (is_dir($dir)) { @@ -101,12 +103,8 @@ class Filesystem /** * Checks the existence of files or directories. - * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check - * - * @return bool */ - public function exists($files) + public function exists(string|iterable $files): bool { $maxPathLength = \PHP_MAXPATHLEN - 2; @@ -126,13 +124,14 @@ class Filesystem /** * Sets access and modification time of file. * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create - * @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used - * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used + * @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used + * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used + * + * @return void * * @throws IOException When touch fails */ - public function touch($files, int $time = null, int $atime = null) + public function touch(string|iterable $files, int $time = null, int $atime = null) { foreach ($this->toIterable($files) as $file) { if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) { @@ -144,11 +143,11 @@ class Filesystem /** * Removes files or directories. * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove + * @return void * * @throws IOException When removal fails */ - public function remove($files) + public function remove(string|iterable $files) { if ($files instanceof \Traversable) { $files = iterator_to_array($files, false); @@ -170,12 +169,12 @@ class Filesystem } } elseif (is_dir($file)) { if (!$isRecursive) { - $tmpName = \dirname(realpath($file)).'/.'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-.')); + $tmpName = \dirname(realpath($file)).'/.'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-_')); if (file_exists($tmpName)) { try { self::doRemove([$tmpName], true); - } catch (IOException $e) { + } catch (IOException) { } } @@ -187,8 +186,8 @@ class Filesystem } } - $files = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS); - self::doRemove(iterator_to_array($files, true), true); + $filesystemIterator = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS); + self::doRemove(iterator_to_array($filesystemIterator, true), true); if (!self::box('rmdir', $file) && file_exists($file) && !$isRecursive) { $lastError = self::$lastError; @@ -208,17 +207,18 @@ class Filesystem /** * Change mode for an array of files or directories. * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode - * @param int $mode The new mode (octal) - * @param int $umask The mode mask (octal) - * @param bool $recursive Whether change the mod recursively or not + * @param int $mode The new mode (octal) + * @param int $umask The mode mask (octal) + * @param bool $recursive Whether change the mod recursively or not + * + * @return void * * @throws IOException When the change fails */ - public function chmod($files, int $mode, int $umask = 0000, bool $recursive = false) + public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { - if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && !self::box('chmod', $file, $mode & ~$umask)) { + if (!self::box('chmod', $file, $mode & ~$umask)) { throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file); } if ($recursive && is_dir($file) && !is_link($file)) { @@ -230,13 +230,14 @@ class Filesystem /** * Change the owner of an array of files or directories. * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner - * @param string|int $user A user name or number - * @param bool $recursive Whether change the owner recursively or not + * @param string|int $user A user name or number + * @param bool $recursive Whether change the owner recursively or not + * + * @return void * * @throws IOException When the change fails */ - public function chown($files, $user, bool $recursive = false) + public function chown(string|iterable $files, string|int $user, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { @@ -257,13 +258,14 @@ class Filesystem /** * Change the group of an array of files or directories. * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group - * @param string|int $group A group name or number - * @param bool $recursive Whether change the group recursively or not + * @param string|int $group A group name or number + * @param bool $recursive Whether change the group recursively or not + * + * @return void * * @throws IOException When the change fails */ - public function chgrp($files, $group, bool $recursive = false) + public function chgrp(string|iterable $files, string|int $group, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { @@ -284,6 +286,8 @@ class Filesystem /** * Renames a file or a directory. * + * @return void + * * @throws IOException When target file or directory already exists * @throws IOException When origin cannot be renamed */ @@ -325,6 +329,8 @@ class Filesystem /** * Creates a symbolic link or copy a directory. * + * @return void + * * @throws IOException When symlink fails */ public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false) @@ -361,10 +367,12 @@ class Filesystem * * @param string|string[] $targetFiles The target file(s) * + * @return void + * * @throws FileNotFoundException When original file is missing or not a file * @throws IOException When link fails, including if link already exists */ - public function hardlink(string $originFile, $targetFiles) + public function hardlink(string $originFile, string|iterable $targetFiles) { self::assertFunctionExists('link'); @@ -393,7 +401,7 @@ class Filesystem /** * @param string $linkType Name of the link type, typically 'symbolic' or 'hard' */ - private function linkException(string $origin, string $target, string $linkType) + private function linkException(string $origin, string $target, string $linkType): never { if (self::$lastError) { if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) { @@ -413,10 +421,8 @@ class Filesystem * With $canonicalize = true * - if $path does not exist, returns null * - if $path exists, returns its absolute fully resolved final version - * - * @return string|null */ - public function readlink(string $path, bool $canonicalize = false) + public function readlink(string $path, bool $canonicalize = false): ?string { if (!$canonicalize && !is_link($path)) { return null; @@ -427,14 +433,6 @@ class Filesystem return null; } - if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410) { - $path = readlink($path); - } - - return realpath($path); - } - - if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400) { return realpath($path); } @@ -443,10 +441,8 @@ class Filesystem /** * Given an existing path, convert it to a path relative to a given starting path. - * - * @return string */ - public function makePathRelative(string $endPath, string $startPath) + public function makePathRelative(string $endPath, string $startPath): string { if (!$this->isAbsolutePath($startPath)) { throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath)); @@ -462,11 +458,9 @@ class Filesystem $startPath = str_replace('\\', '/', $startPath); } - $splitDriveLetter = function ($path) { - return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) - ? [substr($path, 2), strtoupper($path[0])] - : [$path, null]; - }; + $splitDriveLetter = fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) + ? [substr($path, 2), strtoupper($path[0])] + : [$path, null]; $splitPath = function ($path) { $result = []; @@ -532,6 +526,8 @@ class Filesystem * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) * + * @return void + * * @throws IOException When file type is unknown */ public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = []) @@ -592,10 +588,8 @@ class Filesystem /** * Returns whether the file path is an absolute path. - * - * @return bool */ - public function isAbsolutePath(string $file) + public function isAbsolutePath(string $file): bool { return '' !== $file && (strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) @@ -615,9 +609,8 @@ class Filesystem * * @return string The new temporary filename (with path), or throw an exception on failure */ - public function tempnam(string $dir, string $prefix/* , string $suffix = '' */) + public function tempnam(string $dir, string $prefix, string $suffix = ''): string { - $suffix = \func_num_args() > 2 ? func_get_arg(2) : ''; [$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir); // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem @@ -659,6 +652,8 @@ class Filesystem * * @param string|resource $content The data to write into the file * + * @return void + * * @throws IOException if the file cannot be written to */ public function dumpFile(string $filename, $content) @@ -669,6 +664,12 @@ class Filesystem $dir = \dirname($filename); + if (is_link($filename) && $linkTarget = $this->readlink($filename)) { + $this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content); + + return; + } + if (!is_dir($dir)) { $this->mkdir($dir); } @@ -698,6 +699,8 @@ class Filesystem * @param string|resource $content The content to append * @param bool $lock Whether the file should be locked when writing to it * + * @return void + * * @throws IOException If the file is not writable */ public function appendToFile(string $filename, $content/* , bool $lock = false */) @@ -719,7 +722,7 @@ class Filesystem } } - private function toIterable($files): iterable + private function toIterable(string|iterable $files): iterable { return is_iterable($files) ? $files : [$files]; } @@ -741,17 +744,12 @@ class Filesystem } } - /** - * @param mixed ...$args - * - * @return mixed - */ - private static function box(string $func, ...$args) + private static function box(string $func, mixed ...$args): mixed { self::assertFunctionExists($func); self::$lastError = null; - set_error_handler(__CLASS__.'::handleError'); + set_error_handler(self::handleError(...)); try { return $func(...$args); } finally { @@ -762,7 +760,7 @@ class Filesystem /** * @internal */ - public static function handleError(int $type, string $msg) + public static function handleError(int $type, string $msg): void { self::$lastError = $msg; } diff --git a/lib/symfony/filesystem/LICENSE b/lib/symfony/filesystem/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/lib/symfony/filesystem/LICENSE +++ b/lib/symfony/filesystem/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/filesystem/Path.php b/lib/symfony/filesystem/Path.php index 0bbd5b477..664396235 100644 --- a/lib/symfony/filesystem/Path.php +++ b/lib/symfony/filesystem/Path.php @@ -42,12 +42,9 @@ final class Path * * @var array */ - private static $buffer = []; + private static array $buffer = []; - /** - * @var int - */ - private static $bufferSize = 0; + private static int $bufferSize = 0; /** * Canonicalizes the given path. @@ -81,7 +78,7 @@ final class Path // Replace "~" with user's home directory. if ('~' === $path[0]) { - $path = self::getHomeDirectory().mb_substr($path, 1); + $path = self::getHomeDirectory().substr($path, 1); } $path = self::normalize($path); @@ -151,14 +148,14 @@ final class Path $path = self::canonicalize($path); // Maintain scheme - if (false !== ($schemeSeparatorPosition = mb_strpos($path, '://'))) { - $scheme = mb_substr($path, 0, $schemeSeparatorPosition + 3); - $path = mb_substr($path, $schemeSeparatorPosition + 3); + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $scheme = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); } else { $scheme = ''; } - if (false === ($dirSeparatorPosition = strrpos($path, '/'))) { + if (false === $dirSeparatorPosition = strrpos($path, '/')) { return ''; } @@ -169,10 +166,10 @@ final class Path // Directory equals Windows root "C:/" if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) { - return $scheme.mb_substr($path, 0, 3); + return $scheme.substr($path, 0, 3); } - return $scheme.mb_substr($path, 0, $dirSeparatorPosition); + return $scheme.substr($path, 0, $dirSeparatorPosition); } /** @@ -219,7 +216,7 @@ final class Path } // Maintain scheme - if (false !== ($schemeSeparatorPosition = strpos($path, '://'))) { + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { $scheme = substr($path, 0, $schemeSeparatorPosition + 3); $path = substr($path, $schemeSeparatorPosition + 3); } else { @@ -233,7 +230,7 @@ final class Path return $scheme.'/'; } - $length = mb_strlen($path); + $length = \strlen($path); // Windows root if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) { @@ -349,16 +346,16 @@ final class Path $extension = ltrim($extension, '.'); // No extension for paths - if ('/' === mb_substr($path, -1)) { + if ('/' === substr($path, -1)) { return $path; } // No actual extension in path if (empty($actualExtension)) { - return $path.('.' === mb_substr($path, -1) ? '' : '.').$extension; + return $path.('.' === substr($path, -1) ? '' : '.').$extension; } - return mb_substr($path, 0, -mb_strlen($actualExtension)).$extension; + return substr($path, 0, -\strlen($actualExtension)).$extension; } public static function isAbsolute(string $path): bool @@ -368,8 +365,8 @@ final class Path } // Strip scheme - if (false !== ($schemeSeparatorPosition = mb_strpos($path, '://'))) { - $path = mb_substr($path, $schemeSeparatorPosition + 3); + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $path = substr($path, $schemeSeparatorPosition + 3); } $firstCharacter = $path[0]; @@ -380,9 +377,9 @@ final class Path } // Windows root - if (mb_strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) { + if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) { // Special case: "C:" - if (2 === mb_strlen($path)) { + if (2 === \strlen($path)) { return true; } @@ -451,9 +448,9 @@ final class Path return self::canonicalize($path); } - if (false !== ($schemeSeparatorPosition = mb_strpos($basePath, '://'))) { - $scheme = mb_substr($basePath, 0, $schemeSeparatorPosition + 3); - $basePath = mb_substr($basePath, $schemeSeparatorPosition + 3); + if (false !== $schemeSeparatorPosition = strpos($basePath, '://')) { + $scheme = substr($basePath, 0, $schemeSeparatorPosition + 3); + $basePath = substr($basePath, $schemeSeparatorPosition + 3); } else { $scheme = ''; } @@ -574,7 +571,7 @@ final class Path */ public static function isLocal(string $path): bool { - return '' !== $path && false === mb_strpos($path, '://'); + return '' !== $path && !str_contains($path, '://'); } /** @@ -638,7 +635,7 @@ final class Path // Prevent false positives for common prefixes // see isBasePath() - if (0 === mb_strpos($path.'/', $basePath.'/')) { + if (str_starts_with($path.'/', $basePath.'/')) { // next path continue 2; } @@ -666,12 +663,12 @@ final class Path if (null === $finalPath) { // For first part we keep slashes, like '/top', 'C:\' or 'phar://' $finalPath = $path; - $wasScheme = (false !== mb_strpos($path, '://')); + $wasScheme = str_contains($path, '://'); continue; } // Only add slash if previous part didn't end with '/' or '\' - if (!\in_array(mb_substr($finalPath, -1), ['/', '\\'])) { + if (!\in_array(substr($finalPath, -1), ['/', '\\'])) { $finalPath .= '/'; } @@ -717,11 +714,11 @@ final class Path // Don't append a slash for the root "/", because then that root // won't be discovered as common prefix ("//" is not a prefix of // "/foobar/"). - return 0 === mb_strpos($ofPath.'/', rtrim($basePath, '/').'/'); + return str_starts_with($ofPath.'/', rtrim($basePath, '/').'/'); } /** - * @return non-empty-string[] + * @return string[] */ private static function findCanonicalParts(string $root, string $pathWithoutRoot): array { @@ -776,19 +773,19 @@ final class Path } // Remember scheme as part of the root, if any - if (false !== ($schemeSeparatorPosition = mb_strpos($path, '://'))) { - $root = mb_substr($path, 0, $schemeSeparatorPosition + 3); - $path = mb_substr($path, $schemeSeparatorPosition + 3); + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $root = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); } else { $root = ''; } - $length = mb_strlen($path); + $length = \strlen($path); // Remove and remember root directory - if (0 === mb_strpos($path, '/')) { + if (str_starts_with($path, '/')) { $root .= '/'; - $path = $length > 1 ? mb_substr($path, 1) : ''; + $path = $length > 1 ? substr($path, 1) : ''; } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { if (2 === $length) { // Windows special case: "C:" @@ -796,8 +793,8 @@ final class Path $path = ''; } elseif ('/' === $path[2]) { // Windows normal case: "C:/".. - $root .= mb_substr($path, 0, 3); - $path = $length > 3 ? mb_substr($path, 3) : ''; + $root .= substr($path, 0, 3); + $path = $length > 3 ? substr($path, 3) : ''; } } @@ -806,11 +803,11 @@ final class Path private static function toLower(string $string): string { - if (false !== $encoding = mb_detect_encoding($string)) { + if (false !== $encoding = mb_detect_encoding($string, null, true)) { return mb_strtolower($string, $encoding); } - return strtolower($string, $encoding); + return strtolower($string); } private function __construct() diff --git a/lib/symfony/filesystem/composer.json b/lib/symfony/filesystem/composer.json index e756104cd..10a7a531c 100644 --- a/lib/symfony/filesystem/composer.json +++ b/lib/symfony/filesystem/composer.json @@ -16,10 +16,9 @@ } ], "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-mbstring": "~1.8" }, "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, diff --git a/lib/symfony/finder/CHANGELOG.md b/lib/symfony/finder/CHANGELOG.md index 6a44e87c2..e83830247 100644 --- a/lib/symfony/finder/CHANGELOG.md +++ b/lib/symfony/finder/CHANGELOG.md @@ -1,6 +1,22 @@ CHANGELOG ========= +6.4 +--- + + * Add early directory pruning to `Finder::filter()` + +6.2 +--- + + * Add `Finder::sortByExtension()` and `Finder::sortBySize()` + * Add `Finder::sortByCaseInsensitiveName()` to sort by name with case insensitive sorting methods + +6.0 +--- + + * Remove `Comparator::setTarget()` and `Comparator::setOperator()` + 5.4.0 ----- diff --git a/lib/symfony/finder/Comparator/Comparator.php b/lib/symfony/finder/Comparator/Comparator.php index 3af551f4c..bd6858347 100644 --- a/lib/symfony/finder/Comparator/Comparator.php +++ b/lib/symfony/finder/Comparator/Comparator.php @@ -16,102 +16,47 @@ namespace Symfony\Component\Finder\Comparator; */ class Comparator { - private $target; - private $operator = '=='; + private string $target; + private string $operator; - public function __construct(string $target = null, string $operator = '==') - { - if (null === $target) { - trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__); - } - - $this->target = $target; - $this->doSetOperator($operator); - } - - /** - * Gets the target value. - * - * @return string - */ - public function getTarget() - { - if (null === $this->target) { - trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); - } - - return $this->target; - } - - /** - * @deprecated set the target via the constructor instead - */ - public function setTarget(string $target) - { - trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__); - - $this->target = $target; - } - - /** - * Gets the comparison operator. - * - * @return string - */ - public function getOperator() - { - return $this->operator; - } - - /** - * Sets the comparison operator. - * - * @throws \InvalidArgumentException - * - * @deprecated set the operator via the constructor instead - */ - public function setOperator(string $operator) - { - trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__); - - $this->doSetOperator('' === $operator ? '==' : $operator); - } - - /** - * Tests against the target. - * - * @param mixed $test A test value - * - * @return bool - */ - public function test($test) - { - if (null === $this->target) { - trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); - } - - switch ($this->operator) { - case '>': - return $test > $this->target; - case '>=': - return $test >= $this->target; - case '<': - return $test < $this->target; - case '<=': - return $test <= $this->target; - case '!=': - return $test != $this->target; - } - - return $test == $this->target; - } - - private function doSetOperator(string $operator): void + public function __construct(string $target, string $operator = '==') { if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } + $this->target = $target; $this->operator = $operator; } + + /** + * Gets the target value. + */ + public function getTarget(): string + { + return $this->target; + } + + /** + * Gets the comparison operator. + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Tests against the target. + */ + public function test(mixed $test): bool + { + return match ($this->operator) { + '>' => $test > $this->target, + '>=' => $test >= $this->target, + '<' => $test < $this->target, + '<=' => $test <= $this->target, + '!=' => $test != $this->target, + default => $test == $this->target, + }; + } } diff --git a/lib/symfony/finder/Comparator/DateComparator.php b/lib/symfony/finder/Comparator/DateComparator.php index 8f651e148..e0c523d05 100644 --- a/lib/symfony/finder/Comparator/DateComparator.php +++ b/lib/symfony/finder/Comparator/DateComparator.php @@ -30,9 +30,9 @@ class DateComparator extends Comparator } try { - $date = new \DateTime($matches[2]); + $date = new \DateTimeImmutable($matches[2]); $target = $date->format('U'); - } catch (\Exception $e) { + } catch (\Exception) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } diff --git a/lib/symfony/finder/Comparator/NumberComparator.php b/lib/symfony/finder/Comparator/NumberComparator.php index ff85d9677..dd3082077 100644 --- a/lib/symfony/finder/Comparator/NumberComparator.php +++ b/lib/symfony/finder/Comparator/NumberComparator.php @@ -35,7 +35,7 @@ namespace Symfony\Component\Finder\Comparator; class NumberComparator extends Comparator { /** - * @param string|int $test A comparison string or an integer + * @param string|null $test A comparison string or null * * @throws \InvalidArgumentException If the test is not understood */ diff --git a/lib/symfony/finder/Finder.php b/lib/symfony/finder/Finder.php index 8cc564cd6..0fd283c12 100644 --- a/lib/symfony/finder/Finder.php +++ b/lib/symfony/finder/Finder.php @@ -45,27 +45,28 @@ class Finder implements \IteratorAggregate, \Countable public const IGNORE_DOT_FILES = 2; public const IGNORE_VCS_IGNORED_FILES = 4; - private $mode = 0; - private $names = []; - private $notNames = []; - private $exclude = []; - private $filters = []; - private $depths = []; - private $sizes = []; - private $followLinks = false; - private $reverseSorting = false; - private $sort = false; - private $ignore = 0; - private $dirs = []; - private $dates = []; - private $iterators = []; - private $contains = []; - private $notContains = []; - private $paths = []; - private $notPaths = []; - private $ignoreUnreadableDirs = false; + private int $mode = 0; + private array $names = []; + private array $notNames = []; + private array $exclude = []; + private array $filters = []; + private array $pruneFilters = []; + private array $depths = []; + private array $sizes = []; + private bool $followLinks = false; + private bool $reverseSorting = false; + private \Closure|int|false $sort = false; + private int $ignore = 0; + private array $dirs = []; + private array $dates = []; + private array $iterators = []; + private array $contains = []; + private array $notContains = []; + private array $paths = []; + private array $notPaths = []; + private bool $ignoreUnreadableDirs = false; - private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; public function __construct() { @@ -74,10 +75,8 @@ class Finder implements \IteratorAggregate, \Countable /** * Creates a new Finder. - * - * @return static */ - public static function create() + public static function create(): static { return new static(); } @@ -87,7 +86,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function directories() + public function directories(): static { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; @@ -99,7 +98,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function files() + public function files(): static { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; @@ -122,7 +121,7 @@ class Finder implements \IteratorAggregate, \Countable * @see DepthRangeFilterIterator * @see NumberComparator */ - public function depth($levels) + public function depth(string|int|array $levels): static { foreach ((array) $levels as $level) { $this->depths[] = new Comparator\NumberComparator($level); @@ -150,7 +149,7 @@ class Finder implements \IteratorAggregate, \Countable * @see DateRangeFilterIterator * @see DateComparator */ - public function date($dates) + public function date(string|array $dates): static { foreach ((array) $dates as $date) { $this->dates[] = new Comparator\DateComparator($date); @@ -164,8 +163,8 @@ class Finder implements \IteratorAggregate, \Countable * * You can use patterns (delimited with / sign), globs or simple strings. * - * $finder->name('*.php') - * $finder->name('/\.php$/') // same as above + * $finder->name('/\.php$/') + * $finder->name('*.php') // same as above, without dot files * $finder->name('test.php') * $finder->name(['test.py', 'test.php']) * @@ -175,7 +174,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function name($patterns) + public function name(string|array $patterns): static { $this->names = array_merge($this->names, (array) $patterns); @@ -191,7 +190,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function notName($patterns) + public function notName(string|array $patterns): static { $this->notNames = array_merge($this->notNames, (array) $patterns); @@ -213,7 +212,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilecontentFilterIterator */ - public function contains($patterns) + public function contains(string|array $patterns): static { $this->contains = array_merge($this->contains, (array) $patterns); @@ -235,7 +234,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilecontentFilterIterator */ - public function notContains($patterns) + public function notContains(string|array $patterns): static { $this->notContains = array_merge($this->notContains, (array) $patterns); @@ -259,7 +258,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function path($patterns) + public function path(string|array $patterns): static { $this->paths = array_merge($this->paths, (array) $patterns); @@ -283,7 +282,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function notPath($patterns) + public function notPath(string|array $patterns): static { $this->notPaths = array_merge($this->notPaths, (array) $patterns); @@ -305,7 +304,7 @@ class Finder implements \IteratorAggregate, \Countable * @see SizeRangeFilterIterator * @see NumberComparator */ - public function size($sizes) + public function size(string|int|array $sizes): static { foreach ((array) $sizes as $size) { $this->sizes[] = new Comparator\NumberComparator($size); @@ -327,7 +326,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function exclude($dirs) + public function exclude(string|array $dirs): static { $this->exclude = array_merge($this->exclude, (array) $dirs); @@ -343,7 +342,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function ignoreDotFiles(bool $ignoreDotFiles) + public function ignoreDotFiles(bool $ignoreDotFiles): static { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; @@ -363,7 +362,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function ignoreVCS(bool $ignoreVCS) + public function ignoreVCS(bool $ignoreVCS): static { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; @@ -381,7 +380,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function ignoreVCSIgnored(bool $ignoreVCSIgnored) + public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static { if ($ignoreVCSIgnored) { $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; @@ -398,8 +397,10 @@ class Finder implements \IteratorAggregate, \Countable * @see ignoreVCS() * * @param string|string[] $pattern VCS patterns to ignore + * + * @return void */ - public static function addVCSPattern($pattern) + public static function addVCSPattern(string|array $pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; @@ -419,13 +420,29 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sort(\Closure $closure) + public function sort(\Closure $closure): static { $this->sort = $closure; return $this; } + /** + * Sorts files and directories by extension. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByExtension(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_EXTENSION; + + return $this; + } + /** * Sorts files and directories by name. * @@ -435,13 +452,45 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByName(bool $useNaturalSort = false) + public function sortByName(bool $useNaturalSort = false): static { $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; return $this; } + /** + * Sorts files and directories by name case insensitive. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : Iterator\SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE; + + return $this; + } + + /** + * Sorts files and directories by size. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortBySize(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_SIZE; + + return $this; + } + /** * Sorts files and directories by type (directories before files), then by name. * @@ -451,7 +500,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByType() + public function sortByType(): static { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; @@ -469,7 +518,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByAccessedTime() + public function sortByAccessedTime(): static { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; @@ -481,7 +530,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function reverseSorting() + public function reverseSorting(): static { $this->reverseSorting = true; @@ -501,7 +550,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByChangedTime() + public function sortByChangedTime(): static { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; @@ -519,7 +568,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByModifiedTime() + public function sortByModifiedTime(): static { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; @@ -532,14 +581,22 @@ class Finder implements \IteratorAggregate, \Countable * The anonymous function receives a \SplFileInfo and must return false * to remove files. * + * @param \Closure(SplFileInfo): bool $closure + * @param bool $prune Whether to skip traversing directories further + * * @return $this * * @see CustomFilterIterator */ - public function filter(\Closure $closure) + public function filter(\Closure $closure /* , bool $prune = false */): static { + $prune = 1 < \func_num_args() ? func_get_arg(1) : false; $this->filters[] = $closure; + if ($prune) { + $this->pruneFilters[] = $closure; + } + return $this; } @@ -548,7 +605,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function followLinks() + public function followLinks(): static { $this->followLinks = true; @@ -562,7 +619,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function ignoreUnreadableDirs(bool $ignore = true) + public function ignoreUnreadableDirs(bool $ignore = true): static { $this->ignoreUnreadableDirs = $ignore; @@ -578,7 +635,7 @@ class Finder implements \IteratorAggregate, \Countable * * @throws DirectoryNotFoundException if one of the directories does not exist */ - public function in($dirs) + public function in(string|array $dirs): static { $resolvedDirs = []; @@ -587,7 +644,7 @@ class Finder implements \IteratorAggregate, \Countable $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); - $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob); + $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); } else { throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); } @@ -607,8 +664,7 @@ class Finder implements \IteratorAggregate, \Countable * * @throws \LogicException if the in() method has not been called */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \Iterator { if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); @@ -626,9 +682,7 @@ class Finder implements \IteratorAggregate, \Countable $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { - $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { - return $this->searchInDirectory($dir); - }))); + $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); } foreach ($this->iterators as $it) { @@ -651,7 +705,7 @@ class Finder implements \IteratorAggregate, \Countable * * @throws \InvalidArgumentException when the given argument is not iterable */ - public function append(iterable $iterator) + public function append(iterable $iterator): static { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); @@ -673,10 +727,8 @@ class Finder implements \IteratorAggregate, \Countable /** * Check if any results were found. - * - * @return bool */ - public function hasResults() + public function hasResults(): bool { foreach ($this->getIterator() as $_) { return true; @@ -687,11 +739,8 @@ class Finder implements \IteratorAggregate, \Countable /** * Counts all the results collected by the iterators. - * - * @return int */ - #[\ReturnTypeWillChange] - public function count() + public function count(): int { return iterator_count($this->getIterator()); } @@ -701,6 +750,10 @@ class Finder implements \IteratorAggregate, \Countable $exclude = $this->exclude; $notPaths = $this->notPaths; + if ($this->pruneFilters) { + $exclude = array_merge($exclude, $this->pruneFilters); + } + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $exclude = array_merge($exclude, self::$vcsPatterns); } diff --git a/lib/symfony/finder/Gitignore.php b/lib/symfony/finder/Gitignore.php index d42cca1dc..bf05c5b37 100644 --- a/lib/symfony/finder/Gitignore.php +++ b/lib/symfony/finder/Gitignore.php @@ -43,7 +43,7 @@ class Gitignore foreach ($gitignoreLines as $line) { $line = preg_replace('~(? '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex); $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(? $iterator The Iterator to filter @@ -45,11 +45,8 @@ class CustomFilterIterator extends \FilterIterator /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { $fileinfo = $this->current(); diff --git a/lib/symfony/finder/Iterator/DateRangeFilterIterator.php b/lib/symfony/finder/Iterator/DateRangeFilterIterator.php index f592e1913..718d42b16 100644 --- a/lib/symfony/finder/Iterator/DateRangeFilterIterator.php +++ b/lib/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -22,7 +22,7 @@ use Symfony\Component\Finder\Comparator\DateComparator; */ class DateRangeFilterIterator extends \FilterIterator { - private $comparators = []; + private array $comparators = []; /** * @param \Iterator $iterator @@ -37,11 +37,8 @@ class DateRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { $fileinfo = $this->current(); diff --git a/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php b/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php index f593a3f08..1cddb5fa8 100644 --- a/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php +++ b/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -23,7 +23,7 @@ namespace Symfony\Component\Finder\Iterator; */ class DepthRangeFilterIterator extends \FilterIterator { - private $minDepth = 0; + private int $minDepth = 0; /** * @param \RecursiveIteratorIterator<\RecursiveIterator> $iterator The Iterator to filter @@ -40,11 +40,8 @@ class DepthRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } diff --git a/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php index d9e182c17..ebbc76ec7 100644 --- a/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php +++ b/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -11,24 +11,31 @@ namespace Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\SplFileInfo; + /** * ExcludeDirectoryFilterIterator filters out directories. * * @author Fabien Potencier * - * @extends \FilterIterator - * @implements \RecursiveIterator + * @extends \FilterIterator + * + * @implements \RecursiveIterator */ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator { - private $iterator; - private $isRecursive; - private $excludedDirs = []; - private $excludedPattern; + /** @var \Iterator */ + private \Iterator $iterator; + private bool $isRecursive; + /** @var array */ + private array $excludedDirs = []; + private ?string $excludedPattern = null; + /** @var list */ + private array $pruneFilters = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param string[] $directories An array of directories to exclude + * @param \Iterator $iterator The Iterator to filter + * @param list $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { @@ -36,6 +43,16 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi $this->isRecursive = $iterator instanceof \RecursiveIterator; $patterns = []; foreach ($directories as $directory) { + if (!\is_string($directory)) { + if (!\is_callable($directory)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + + $this->pruneFilters[] = $directory; + + continue; + } + $directory = rtrim($directory, '/'); if (!$this->isRecursive || str_contains($directory, '/')) { $patterns[] = preg_quote($directory, '#'); @@ -52,11 +69,8 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { return false; @@ -69,23 +83,23 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi return !preg_match($this->excludedPattern, $path); } + if ($this->pruneFilters && $this->hasChildren()) { + foreach ($this->pruneFilters as $pruneFilter) { + if (!$pruneFilter($this->current())) { + return false; + } + } + } + return true; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function hasChildren() + public function hasChildren(): bool { return $this->isRecursive && $this->iterator->hasChildren(); } - /** - * @return self - */ - #[\ReturnTypeWillChange] - public function getChildren() + public function getChildren(): self { $children = new self($this->iterator->getChildren(), []); $children->excludedDirs = $this->excludedDirs; diff --git a/lib/symfony/finder/Iterator/FileTypeFilterIterator.php b/lib/symfony/finder/Iterator/FileTypeFilterIterator.php index 793ae3509..21303781e 100644 --- a/lib/symfony/finder/Iterator/FileTypeFilterIterator.php +++ b/lib/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -23,11 +23,11 @@ class FileTypeFilterIterator extends \FilterIterator public const ONLY_FILES = 1; public const ONLY_DIRECTORIES = 2; - private $mode; + private int $mode; /** - * @param \Iterator $iterator The Iterator to filter - * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ public function __construct(\Iterator $iterator, int $mode) { @@ -38,11 +38,8 @@ class FileTypeFilterIterator extends \FilterIterator /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { $fileinfo = $this->current(); if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { diff --git a/lib/symfony/finder/Iterator/FilecontentFilterIterator.php b/lib/symfony/finder/Iterator/FilecontentFilterIterator.php index 79f8c29d3..bdc71ffdd 100644 --- a/lib/symfony/finder/Iterator/FilecontentFilterIterator.php +++ b/lib/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -11,23 +11,22 @@ namespace Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\SplFileInfo; + /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier * @author WÅ‚odzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { if (!$this->matchRegexps && !$this->noMatchRegexps) { return true; @@ -51,10 +50,8 @@ class FilecontentFilterIterator extends MultiplePcreFilterIterator * Converts string to regexp if necessary. * * @param string $str Pattern: string or regexp - * - * @return string */ - protected function toRegex(string $str) + protected function toRegex(string $str): string { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/lib/symfony/finder/Iterator/FilenameFilterIterator.php b/lib/symfony/finder/Iterator/FilenameFilterIterator.php index 77b3b2419..05d953581 100644 --- a/lib/symfony/finder/Iterator/FilenameFilterIterator.php +++ b/lib/symfony/finder/Iterator/FilenameFilterIterator.php @@ -24,11 +24,8 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { return $this->isAccepted($this->current()->getFilename()); } @@ -40,10 +37,8 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator * Glob strings are transformed with Glob::toRegex(). * * @param string $str Pattern: glob or regexp - * - * @return string */ - protected function toRegex(string $str) + protected function toRegex(string $str): string { return $this->isRegex($str) ? $str : Glob::toRegex($str); } diff --git a/lib/symfony/finder/Iterator/LazyIterator.php b/lib/symfony/finder/Iterator/LazyIterator.php index 32cc37ff1..5b5806be9 100644 --- a/lib/symfony/finder/Iterator/LazyIterator.php +++ b/lib/symfony/finder/Iterator/LazyIterator.php @@ -18,11 +18,11 @@ namespace Symfony\Component\Finder\Iterator; */ class LazyIterator implements \IteratorAggregate { - private $iteratorFactory; + private \Closure $iteratorFactory; public function __construct(callable $iteratorFactory) { - $this->iteratorFactory = $iteratorFactory; + $this->iteratorFactory = $iteratorFactory(...); } public function getIterator(): \Traversable diff --git a/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php index 564765d8f..82a9df301 100644 --- a/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php +++ b/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -27,9 +27,9 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator protected $noMatchRegexps = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param string[] $matchPatterns An array of patterns that need to match - * @param string[] $noMatchPatterns An array of patterns that need to not match + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { @@ -50,10 +50,8 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator * If there is no regexps defined in the class, this method will accept the string. * Such case can be handled by child classes before calling the method if they want to * apply a different behavior. - * - * @return bool */ - protected function isAccepted(string $string) + protected function isAccepted(string $string): bool { // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { @@ -79,10 +77,8 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator /** * Checks whether the string is a regex. - * - * @return bool */ - protected function isRegex(string $str) + protected function isRegex(string $str): bool { $availableModifiers = 'imsxuADU'; @@ -110,8 +106,6 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator /** * Converts string into regexp. - * - * @return string */ - abstract protected function toRegex(string $str); + abstract protected function toRegex(string $str): string; } diff --git a/lib/symfony/finder/Iterator/PathFilterIterator.php b/lib/symfony/finder/Iterator/PathFilterIterator.php index 7974c4ee3..c6d58139f 100644 --- a/lib/symfony/finder/Iterator/PathFilterIterator.php +++ b/lib/symfony/finder/Iterator/PathFilterIterator.php @@ -11,23 +11,22 @@ namespace Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\SplFileInfo; + /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier * @author WÅ‚odzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class PathFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { $filename = $this->current()->getRelativePathname(); @@ -49,10 +48,8 @@ class PathFilterIterator extends MultiplePcreFilterIterator * Use only / as directory separator (on Windows also). * * @param string $str Pattern: regexp or dirname - * - * @return string */ - protected function toRegex(string $str) + protected function toRegex(string $str): string { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php index 27589cdd5..34cced688 100644 --- a/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php +++ b/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -18,23 +18,18 @@ use Symfony\Component\Finder\SplFileInfo; * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet + * + * @extends \RecursiveDirectoryIterator */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { - /** - * @var bool - */ - private $ignoreUnreadableDirs; - - /** - * @var bool - */ - private $rewindable; + private bool $ignoreUnreadableDirs; + private bool $ignoreFirstRewind = true; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations - private $rootPath; - private $subPath; - private $directorySeparator = '/'; + private string $rootPath; + private string $subPath; + private string $directorySeparator = '/'; /** * @throws \RuntimeException @@ -55,17 +50,15 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Return an instance of SplFileInfo with support for relative paths. - * - * @return SplFileInfo */ - #[\ReturnTypeWillChange] - public function current() + public function current(): SplFileInfo { // the logic here avoids redoing the same work in all iterations - if (null === $subPathname = $this->subPath) { - $subPathname = $this->subPath = $this->getSubPath(); + if (!isset($this->subPath)) { + $this->subPath = $this->getSubPath(); } + $subPathname = $this->subPath; if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; } @@ -78,13 +71,7 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); } - /** - * @param bool $allowLinks - * - * @return bool - */ - #[\ReturnTypeWillChange] - public function hasChildren($allowLinks = false) + public function hasChildren(bool $allowLinks = false): bool { $hasChildren = parent::hasChildren($allowLinks); @@ -96,19 +83,16 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator parent::getChildren(); return true; - } catch (\UnexpectedValueException $e) { + } catch (\UnexpectedValueException) { // If directory is unreadable and finder is set to ignore it, skip children return false; } } /** - * @return \RecursiveDirectoryIterator - * * @throws AccessDeniedException */ - #[\ReturnTypeWillChange] - public function getChildren() + public function getChildren(): \RecursiveDirectoryIterator { try { $children = parent::getChildren(); @@ -118,7 +102,6 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; // performance optimization to avoid redoing the same work in all children - $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } @@ -128,41 +111,23 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator } } - /** - * Do nothing for non rewindable stream. - * - * @return void - */ - #[\ReturnTypeWillChange] - public function rewind() + public function next(): void { - if (false === $this->isRewindable()) { + $this->ignoreFirstRewind = false; + + parent::next(); + } + + public function rewind(): void + { + // some streams like FTP are not rewindable, ignore the first rewind after creation, + // as newly created DirectoryIterator does not need to be rewound + if ($this->ignoreFirstRewind) { + $this->ignoreFirstRewind = false; + return; } parent::rewind(); } - - /** - * Checks if the stream is rewindable. - * - * @return bool - */ - public function isRewindable() - { - if (null !== $this->rewindable) { - return $this->rewindable; - } - - if (false !== $stream = @opendir($this->getPath())) { - $infos = stream_get_meta_data($stream); - closedir($stream); - - if ($infos['seekable']) { - return $this->rewindable = true; - } - } - - return $this->rewindable = false; - } } diff --git a/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php b/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php index 575bf29b7..925830a2e 100644 --- a/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php +++ b/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -22,7 +22,7 @@ use Symfony\Component\Finder\Comparator\NumberComparator; */ class SizeRangeFilterIterator extends \FilterIterator { - private $comparators = []; + private array $comparators = []; /** * @param \Iterator $iterator @@ -37,11 +37,8 @@ class SizeRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. - * - * @return bool */ - #[\ReturnTypeWillChange] - public function accept() + public function accept(): bool { $fileinfo = $this->current(); if (!$fileinfo->isFile()) { diff --git a/lib/symfony/finder/Iterator/SortableIterator.php b/lib/symfony/finder/Iterator/SortableIterator.php index 9afde5c25..177cd0b60 100644 --- a/lib/symfony/finder/Iterator/SortableIterator.php +++ b/lib/symfony/finder/Iterator/SortableIterator.php @@ -27,9 +27,14 @@ class SortableIterator implements \IteratorAggregate public const SORT_BY_CHANGED_TIME = 4; public const SORT_BY_MODIFIED_TIME = 5; public const SORT_BY_NAME_NATURAL = 6; + public const SORT_BY_NAME_CASE_INSENSITIVE = 7; + public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8; + public const SORT_BY_EXTENSION = 9; + public const SORT_BY_SIZE = 10; - private $iterator; - private $sort; + /** @var \Traversable */ + private \Traversable $iterator; + private \Closure|int $sort; /** * @param \Traversable $iterator @@ -37,19 +42,19 @@ class SortableIterator implements \IteratorAggregate * * @throws \InvalidArgumentException */ - public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) + public function __construct(\Traversable $iterator, int|callable $sort, bool $reverseOrder = false) { $this->iterator = $iterator; $order = $reverseOrder ? -1 : 1; if (self::SORT_BY_NAME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_NATURAL === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { if ($a->isDir() && $b->isFile()) { @@ -61,31 +66,25 @@ class SortableIterator implements \IteratorAggregate return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getATime() - $b->getATime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime()); } elseif (self::SORT_BY_CHANGED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getCTime() - $b->getCTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime()); } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getMTime() - $b->getMTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime()); + } elseif (self::SORT_BY_EXTENSION === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension()); + } elseif (self::SORT_BY_SIZE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize()); } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort; + $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...); } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } - /** - * @return \Traversable - */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \Traversable { if (1 === $this->sort) { return $this->iterator; diff --git a/lib/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/lib/symfony/finder/Iterator/VcsIgnoredFilterIterator.php index e27158cbd..ddd700772 100644 --- a/lib/symfony/finder/Iterator/VcsIgnoredFilterIterator.php +++ b/lib/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -13,27 +13,37 @@ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Gitignore; +/** + * @extends \FilterIterator + */ final class VcsIgnoredFilterIterator extends \FilterIterator { - /** - * @var string - */ - private $baseDir; + private string $baseDir; /** * @var array */ - private $gitignoreFilesCache = []; + private array $gitignoreFilesCache = []; /** * @var array */ - private $ignoredPathsCache = []; + private array $ignoredPathsCache = []; + /** + * @param \Iterator $iterator + */ public function __construct(\Iterator $iterator, string $baseDir) { $this->baseDir = $this->normalizePath($baseDir); + foreach ($this->parentDirectoriesUpwards($this->baseDir) as $parentDirectory) { + if (@is_dir("{$parentDirectory}/.git")) { + $this->baseDir = $parentDirectory; + break; + } + } + parent::__construct($iterator); } @@ -58,7 +68,7 @@ final class VcsIgnoredFilterIterator extends \FilterIterator $ignored = false; - foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { + foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { if ($this->isIgnored($parentDirectory)) { // rules in ignored directories are ignored, no need to check further. break; @@ -89,11 +99,11 @@ final class VcsIgnoredFilterIterator extends \FilterIterator /** * @return list */ - private function parentsDirectoryDownward(string $fileRealPath): array + private function parentDirectoriesUpwards(string $from): array { $parentDirectories = []; - $parentDirectory = $fileRealPath; + $parentDirectory = $from; while (true) { $newParentDirectory = \dirname($parentDirectory); @@ -103,16 +113,28 @@ final class VcsIgnoredFilterIterator extends \FilterIterator break; } - $parentDirectory = $newParentDirectory; - - if (0 !== strpos($parentDirectory, $this->baseDir)) { - break; - } - - $parentDirectories[] = $parentDirectory; + $parentDirectories[] = $parentDirectory = $newParentDirectory; } - return array_reverse($parentDirectories); + return $parentDirectories; + } + + private function parentDirectoriesUpTo(string $from, string $upTo): array + { + return array_filter( + $this->parentDirectoriesUpwards($from), + static fn (string $directory): bool => str_starts_with($directory, $upTo) + ); + } + + /** + * @return list + */ + private function parentDirectoriesDownwards(string $fileRealPath): array + { + return array_reverse( + $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) + ); } /** diff --git a/lib/symfony/finder/LICENSE b/lib/symfony/finder/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/lib/symfony/finder/LICENSE +++ b/lib/symfony/finder/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/finder/SplFileInfo.php b/lib/symfony/finder/SplFileInfo.php index 11604a2ef..867e8e81a 100644 --- a/lib/symfony/finder/SplFileInfo.php +++ b/lib/symfony/finder/SplFileInfo.php @@ -18,8 +18,8 @@ namespace Symfony\Component\Finder; */ class SplFileInfo extends \SplFileInfo { - private $relativePath; - private $relativePathname; + private string $relativePath; + private string $relativePathname; /** * @param string $file The file name @@ -37,10 +37,8 @@ class SplFileInfo extends \SplFileInfo * Returns the relative path. * * This path does not contain the file name. - * - * @return string */ - public function getRelativePath() + public function getRelativePath(): string { return $this->relativePath; } @@ -49,10 +47,8 @@ class SplFileInfo extends \SplFileInfo * Returns the relative path name. * * This path contains the file name. - * - * @return string */ - public function getRelativePathname() + public function getRelativePathname(): string { return $this->relativePathname; } @@ -67,11 +63,9 @@ class SplFileInfo extends \SplFileInfo /** * Returns the contents of the file. * - * @return string - * * @throws \RuntimeException */ - public function getContents() + public function getContents(): string { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { diff --git a/lib/symfony/finder/composer.json b/lib/symfony/finder/composer.json index ef19911da..bbc9d7f28 100644 --- a/lib/symfony/finder/composer.json +++ b/lib/symfony/finder/composer.json @@ -16,9 +16,10 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, diff --git a/lib/symfony/framework-bundle/CHANGELOG.md b/lib/symfony/framework-bundle/CHANGELOG.md index ea913ef98..949aad31c 100644 --- a/lib/symfony/framework-bundle/CHANGELOG.md +++ b/lib/symfony/framework-bundle/CHANGELOG.md @@ -1,6 +1,132 @@ CHANGELOG ========= +6.4 +--- + + * Add `HttpClientAssertionsTrait` + * Add `AbstractController::renderBlock()` and `renderBlockView()` + * Add native return type to `Translator` and to `Application::reset()` + * Deprecate the integration of Doctrine annotations, either uninstall the `doctrine/annotations` package or disable the integration by setting `framework.annotations` to `false` + * Enable `json_decode_detailed_errors` context for Serializer by default if `kernel.debug` is true and the `seld/jsonlint` package is installed + * Add `DomCrawlerAssertionsTrait::assertAnySelectorTextContains(string $selector, string $text)` + * Add `DomCrawlerAssertionsTrait::assertAnySelectorTextSame(string $selector, string $text)` + * Add `DomCrawlerAssertionsTrait::assertAnySelectorTextNotContains(string $selector, string $text)` + * Deprecate `EnableLoggerDebugModePass`, use argument `$debug` of HttpKernel's `Logger` instead + * Deprecate `AddDebugLogProcessorPass::configureLogger()`, use HttpKernel's `DebugLoggerConfigurator` instead + * Deprecate not setting the `framework.handle_all_throwables` config option; it will default to `true` in 7.0 + * Deprecate not setting the `framework.php_errors.log` config option; it will default to `true` in 7.0 + * Deprecate not setting the `framework.session.cookie_secure` config option; it will default to `auto` in 7.0 + * Deprecate not setting the `framework.session.cookie_samesite` config option; it will default to `lax` in 7.0 + * Deprecate not setting either `framework.session.handler_id` or `save_path` config options; `handler_id` will + default to null in 7.0 if `save_path` is not set and to `session.handler.native_file` otherwise + * Deprecate not setting the `framework.uid.default_uuid_version` config option; it will default to `7` in 7.0 + * Deprecate not setting the `framework.uid.time_based_uuid_version` config option; it will default to `7` in 7.0 + * Deprecate not setting the `framework.validation.email_validation_mode` config option; it will default to `html5` in 7.0 + * Deprecate `framework.validation.enable_annotations`, use `framework.validation.enable_attributes` instead + * Deprecate `framework.serializer.enable_annotations`, use `framework.serializer.enable_attributes` instead + * Add `array $tokenAttributes = []` optional parameter to `KernelBrowser::loginUser()` + * Add support for relative URLs in BrowserKit's redirect assertion + * Change BrowserKitAssertionsTrait::getClient() to be protected + * Deprecate the `framework.asset_mapper.provider` config option + * Add `--exclude` option to the `cache:pool:clear` command + * Add parameters deprecations to the output of `debug:container` command + * Change `framework.asset_mapper.importmap_polyfill` from a URL to the name of an item in the importmap + * Provide `$buildDir` when running `CacheWarmer` to build read-only resources + * Add the global `--profile` option to the console to enable profiling commands + * Deprecate the `routing.loader.annotation` service, use the `routing.loader.attribute` service instead + * Deprecate the `routing.loader.annotation.directory` service, use the `routing.loader.attribute.directory` service instead + * Deprecate the `routing.loader.annotation.file` service, use the `routing.loader.attribute.file` service instead + * Deprecate `AnnotatedRouteControllerLoader`, use `AttributeRouteControllerLoader` instead + * Deprecate `AddExpressionLanguageProvidersPass`, use `Symfony\Component\Routing\DependencyInjection\AddExpressionLanguageProvidersPass` instead + * Deprecate `DataCollectorTranslatorPass`, use `Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass` instead + * Deprecate `LoggingTranslatorPass`, use `Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass` instead + * Deprecate `WorkflowGuardListenerPass`, use `Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass` instead + +6.3 +--- + + * Add `extra` option for `http_client.default_options` and `http_client.scoped_client` + * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` + * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy + * Add `--format` option to the `debug:config` command + * Add support to pass namespace wildcard in `framework.messenger.routing` + * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` + * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead + * Allow setting private services with the test container + * Register alias for argument for workflow services with workflow name only + * Configure the `ErrorHandler` on `FrameworkBundle::boot()` + * Allow setting `debug.container.dump` to `false` to disable dumping the container to XML + * Add `framework.http_cache.skip_response_headers` option + * Display warmers duration on debug verbosity for `cache:clear` command + * Add `AbstractController::sendEarlyHints()` to send HTTP Early Hints + * Add autowiring aliases for `Http\Client\HttpAsyncClient` + * Deprecate the `Http\Client\HttpClient` service, use `Psr\Http\Client\ClientInterface` instead + * Add `stop_worker_on_signals` configuration option to `messenger` to define signals which would stop a worker + * Add support for `--all` option to clear all cache pools with `cache:pool:clear` command + * Add `--show-aliases` option to `debug:router` command + +6.2 +--- + + * Add `resolve-env` option to `debug:config` command to display actual values of environment variables in dumped configuration + * Add `NotificationAssertionsTrait` + * Add option `framework.handle_all_throwables` to allow `Symfony\Component\HttpKernel\HttpKernel` to handle all kinds of `Throwable` + * Make `AbstractController::render()` able to deal with forms and deprecate `renderForm()` + * Deprecate the `Symfony\Component\Serializer\Normalizer\ObjectNormalizer` and + `Symfony\Component\Serializer\Normalizer\PropertyNormalizer` autowiring aliases, type-hint against + `Symfony\Component\Serializer\Normalizer\NormalizerInterface` or implement `NormalizerAwareInterface` instead + * Add service usages list to the `debug:container` command output + * Add service and alias deprecation message to `debug:container []` output + * Tag all workflows services with `workflow`, those with type=workflow are + tagged with `workflow.workflow`, and those with type=state_machine with + `workflow.state_machine` + * Add `rate_limiter` configuration option to `messenger.transport` to allow rate limited transports using the RateLimiter component + * Remove `@internal` tag from secret vaults to allow them to be used directly outside the framework bundle and custom vaults to be added + * Deprecate `framework.form.legacy_error_messages` config node + * Add a `framework.router.cache_dir` configuration option to configure the default `Router` `cache_dir` option + * Add option `framework.messenger.buses.*.default_middleware.allow_no_senders` to enable throwing when a message doesn't have a sender + * Deprecate `AbstractController::renderForm()`, use `render()` instead + * Deprecate `FrameworkExtension::registerRateLimiter()` + +6.1 +--- + + * Add support for configuring semaphores + * Environment variable `SYMFONY_IDE` is read by default when `framework.ide` config is not set + * Load PHP configuration files by default in the `MicroKernelTrait` + * Add `cache:pool:invalidate-tags` command + * Add `xliff` support in addition to `xlf` for `XliffFileDumper` + * Deprecate the `reset_on_message` config option. It can be set to `true` only and does nothing now + * Add `trust_x_sendfile_type_header` option + * Add support for first-class callable route controller in `MicroKernelTrait` + * Add tag `routing.condition_service` to autoconfigure routing condition services + * Automatically register kernel methods marked with the `Symfony\Component\Routing\Annotation\Route` attribute or annotation as controllers in `MicroKernelTrait` + * Deprecate not setting the `http_method_override` config option. The default value will change to `false` in 7.0. + * Add `framework.profiler.collect_serializer_data` config option, set it to `true` to enable the serializer data collector and profiler panel + +6.0 +--- + + * Remove the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead + * Remove `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead + * Remove the `session` service and the `SessionInterface` alias, use the `\Symfony\Component\HttpFoundation\Request::getSession()` or the new `\Symfony\Component\HttpFoundation\RequestStack::getSession()` methods instead + * Remove the `session.attribute_bag` service and `session.flash_bag` service + * Remove the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead + * The `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`, + `cache_clearer`, `filesystem` and `validator` services are now private + * Remove the `output-format` and `xliff-version` options from `TranslationUpdateCommand` + * Remove `has()`, `get()`, `getDoctrine()`n and `dispatchMessage()` from `AbstractController`, use method/constructor injection instead + * Make the "framework.router.utf8" configuration option default to `true` + * Remove the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead + * Make the `profiler` service private + * Remove all other values than "none", "php_array" and "file" for `framework.annotation.cache` + * Register workflow services as private + * Remove support for passing a `RouteCollectionBuilder` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead + * Remove the `cache.adapter.doctrine` service + * Remove the `framework.translator.enabled_locales` config option, use `framework.enabled_locales` instead + * Make the `framework.messenger.reset_on_message` configuration option default to `true` + 5.4 --- diff --git a/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php index 17e066045..98c281276 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -19,7 +19,7 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface { - private $phpArrayFile; + private string $phpArrayFile; /** * @param string $phpArrayFile The PHP file where metadata are cached @@ -29,26 +29,22 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface $this->phpArrayFile = $phpArrayFile; } - /** - * {@inheritdoc} - */ - public function isOptional() + public function isOptional(): bool { return true; } /** - * {@inheritdoc} - * - * @return string[] A list of classes to preload on PHP 7.4+ + * @param string|null $buildDir */ - public function warmUp(string $cacheDir) + public function warmUp(string $cacheDir /* , string $buildDir = null */): array { + $buildDir = 1 < \func_num_args() ? func_get_arg(1) : null; $arrayAdapter = new ArrayAdapter(); spl_autoload_register([ClassExistenceResource::class, 'throwOnRequiredClass']); try { - if (!$this->doWarmUp($cacheDir, $arrayAdapter)) { + if (!$this->doWarmUp($cacheDir, $arrayAdapter, $buildDir)) { return []; } } finally { @@ -58,7 +54,7 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface // the ArrayAdapter stores the values serialized // to avoid mutation of the data after it was written to the cache // so here we un-serialize the values first - $values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues()); + $values = array_map(fn ($val) => null !== $val ? unserialize($val) : null, $arrayAdapter->getValues()); return $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()), $values); } @@ -66,7 +62,7 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface /** * @return string[] A list of classes to preload on PHP 7.4+ */ - protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) + protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array { return (array) $phpArrayAdapter->warmUp($values); } @@ -78,12 +74,14 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface { try { ClassExistenceResource::throwOnRequiredClass($class, $exception); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { } } /** + * @param string|null $buildDir + * * @return bool false if there is nothing to warm-up */ - abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter); + abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter /* , string $buildDir = null */): bool; } diff --git a/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php index 550440017..20533bb60 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -22,28 +22,32 @@ use Symfony\Component\Cache\Adapter\PhpArrayAdapter; * and declared in DI bundle extensions using the addAnnotatedClassesToCache method. * * @author Titouan Galopin + * + * @deprecated since Symfony 6.4 without replacement */ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer { - private $annotationReader; - private $excludeRegexp; - private $debug; - /** * @param string $phpArrayFile The PHP file where annotations are cached */ - public function __construct(Reader $annotationReader, string $phpArrayFile, string $excludeRegexp = null, bool $debug = false) - { + public function __construct( + private readonly Reader $annotationReader, + string $phpArrayFile, + private readonly ?string $excludeRegexp = null, + private readonly bool $debug = false, + /* bool $triggerDeprecation = true, */ + ) { + if (\func_num_args() < 5 || func_get_arg(4)) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s" class is deprecated without replacement.', __CLASS__); + } + parent::__construct($phpArrayFile); - $this->annotationReader = $annotationReader; - $this->excludeRegexp = $excludeRegexp; - $this->debug = $debug; } /** - * {@inheritdoc} + * @param string|null $buildDir */ - protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter /* , string $buildDir = null */): bool { $annotatedClassPatterns = $cacheDir.'/annotations.map'; @@ -71,21 +75,21 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer /** * @return string[] A list of classes to preload on PHP 7.4+ */ - protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) + protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array { // make sure we don't cache null values - $values = array_filter($values, function ($val) { return null !== $val; }); + $values = array_filter($values, fn ($val) => null !== $val); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } - private function readAllComponents(Reader $reader, string $class) + private function readAllComponents(Reader $reader, string $class): void { $reflectionClass = new \ReflectionClass($class); try { $reader->getClassAnnotations($reflectionClass); - } catch (AnnotationException $e) { + } catch (AnnotationException) { /* * Ignore any AnnotationException to not break the cache warming process if an Annotation is badly * configured or could not be found / read / etc. @@ -99,14 +103,14 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer foreach ($reflectionClass->getMethods() as $reflectionMethod) { try { $reader->getMethodAnnotations($reflectionMethod); - } catch (AnnotationException $e) { + } catch (AnnotationException) { } } foreach ($reflectionClass->getProperties() as $reflectionProperty) { try { $reader->getPropertyAnnotations($reflectionProperty); - } catch (AnnotationException $e) { + } catch (AnnotationException) { } } } diff --git a/lib/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php index 79bca2403..7498a82d1 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php @@ -25,8 +25,8 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; */ final class CachePoolClearerCacheWarmer implements CacheWarmerInterface { - private $poolClearer; - private $pools; + private Psr6CacheClearer $poolClearer; + private array $pools; /** * @param string[] $pools @@ -37,12 +37,7 @@ final class CachePoolClearerCacheWarmer implements CacheWarmerInterface $this->pools = $pools; } - /** - * {@inheritdoc} - * - * @return string[] - */ - public function warmUp(string $cacheDirectory): array + public function warmUp(string $cacheDir, string $buildDir = null): array { foreach ($this->pools as $pool) { if ($this->poolClearer->hasPool($pool)) { @@ -53,9 +48,6 @@ final class CachePoolClearerCacheWarmer implements CacheWarmerInterface return []; } - /** - * {@inheritdoc} - */ public function isOptional(): bool { // optional cache warmers are not run when handling the request diff --git a/lib/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php index ed20bbcb6..7e039ae2f 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -28,8 +28,8 @@ use Symfony\Component\HttpKernel\KernelInterface; */ class ConfigBuilderCacheWarmer implements CacheWarmerInterface { - private $kernel; - private $logger; + private KernelInterface $kernel; + private ?LoggerInterface $logger; public function __construct(KernelInterface $kernel, LoggerInterface $logger = null) { @@ -38,13 +38,17 @@ class ConfigBuilderCacheWarmer implements CacheWarmerInterface } /** - * {@inheritdoc} - * - * @return string[] + * @param string|null $buildDir */ - public function warmUp(string $cacheDir) + public function warmUp(string $cacheDir /* , string $buildDir = null */): array { - $generator = new ConfigBuilderGenerator($cacheDir); + $buildDir = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (!$buildDir) { + return []; + } + + $generator = new ConfigBuilderGenerator($buildDir); foreach ($this->kernel->getBundles() as $bundle) { $extension = $bundle->getContainerExtension(); @@ -55,9 +59,7 @@ class ConfigBuilderCacheWarmer implements CacheWarmerInterface try { $this->dumpExtension($extension, $generator); } catch (\Exception $e) { - if ($this->logger) { - $this->logger->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => \get_class($extension)]); - } + $this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}: '.$e->getMessage(), ['exception' => $e, 'extensionClass' => $extension::class]); } } @@ -81,10 +83,7 @@ class ConfigBuilderCacheWarmer implements CacheWarmerInterface $generator->build($configuration); } - /** - * {@inheritdoc} - */ - public function isOptional() + public function isOptional(): bool { return true; } diff --git a/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php index 6cdf176bb..2af9a2fe8 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php @@ -26,7 +26,7 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; */ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { - private $container; + private ContainerInterface $container; public function __construct(ContainerInterface $container) { @@ -34,31 +34,22 @@ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterf $this->container = $container; } - /** - * {@inheritdoc} - */ - public function warmUp(string $cacheDir): array + public function warmUp(string $cacheDir, string $buildDir = null): array { $router = $this->container->get('router'); if ($router instanceof WarmableInterface) { - return (array) $router->warmUp($cacheDir); + return (array) $router->warmUp($cacheDir, $buildDir); } throw new \LogicException(sprintf('The router "%s" cannot be warmed up because it does not implement "%s".', get_debug_type($router), WarmableInterface::class)); } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return true; } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php index 0ada0ffc9..b47a48ce6 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php @@ -27,7 +27,7 @@ use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; */ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer { - private $loaders; + private array $loaders; /** * @param LoaderInterface[] $loaders The serializer metadata loaders @@ -40,12 +40,12 @@ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer } /** - * {@inheritdoc} + * @param string|null $buildDir */ - protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter /* , string $buildDir = null */): bool { - if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) { - return false; + if (!$this->loaders) { + return true; } $metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayAdapter); @@ -54,7 +54,7 @@ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer foreach ($loader->getMappedClasses() as $mappedClass) { try { $metadataFactory->getMetadataFor($mappedClass); - } catch (AnnotationException $e) { + } catch (AnnotationException) { // ignore failing annotations } catch (\Exception $e) { $this->ignoreAutoloadException($mappedClass, $e); diff --git a/lib/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php index e3efc8090..39b1444b0 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php @@ -24,8 +24,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { - private $container; - private $translator; + private ContainerInterface $container; + private TranslatorInterface $translator; public function __construct(ContainerInterface $container) { @@ -34,35 +34,27 @@ class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriber } /** - * {@inheritdoc} - * - * @return string[] + * @param string|null $buildDir */ - public function warmUp(string $cacheDir) + public function warmUp(string $cacheDir /* , string $buildDir = null */): array { - if (null === $this->translator) { - $this->translator = $this->container->get('translator'); - } + $this->translator ??= $this->container->get('translator'); if ($this->translator instanceof WarmableInterface) { - return (array) $this->translator->warmUp($cacheDir); + $buildDir = 1 < \func_num_args() ? func_get_arg(1) : null; + + return (array) $this->translator->warmUp($cacheDir, $buildDir); } return []; } - /** - * {@inheritdoc} - */ - public function isOptional() + public function isOptional(): bool { return true; } - /** - * {@inheritdoc} - */ - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ 'translator' => TranslatorInterface::class, diff --git a/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php index 3c6d582c4..224e90985 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php @@ -28,7 +28,7 @@ use Symfony\Component\Validator\ValidatorBuilder; */ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer { - private $validatorBuilder; + private ValidatorBuilder $validatorBuilder; /** * @param string $phpArrayFile The PHP file where metadata are cached @@ -40,14 +40,10 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer } /** - * {@inheritdoc} + * @param string|null $buildDir */ - protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter /* , string $buildDir = null */): bool { - if (!method_exists($this->validatorBuilder, 'getLoaders')) { - return false; - } - $loaders = $this->validatorBuilder->getLoaders(); $metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), $arrayAdapter); @@ -57,7 +53,7 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer if ($metadataFactory->hasMetadataFor($mappedClass)) { $metadataFactory->getMetadataFor($mappedClass); } - } catch (AnnotationException $e) { + } catch (AnnotationException) { // ignore failing annotations } catch (\Exception $e) { $this->ignoreAutoloadException($mappedClass, $e); @@ -71,10 +67,10 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer /** * @return string[] A list of classes to preload on PHP 7.4+ */ - protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) + protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array { // make sure we don't cache null values - $values = array_filter($values, function ($val) { return null !== $val; }); + $values = array_filter($values, fn ($val) => null !== $val); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } diff --git a/lib/symfony/framework-bundle/Command/AboutCommand.php b/lib/symfony/framework-bundle/Command/AboutCommand.php index e9660e55b..2c6cb440f 100644 --- a/lib/symfony/framework-bundle/Command/AboutCommand.php +++ b/lib/symfony/framework-bundle/Command/AboutCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\TableSeparator; @@ -27,18 +28,12 @@ use Symfony\Component\HttpKernel\KernelInterface; * * @final */ +#[AsCommand(name: 'about', description: 'Display information about the current project')] class AboutCommand extends Command { - protected static $defaultName = 'about'; - protected static $defaultDescription = 'Display information about the current project'; - - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOT' The %command.name% command displays information about the current Symfony project. @@ -49,9 +44,6 @@ EOT ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -75,7 +67,7 @@ EOT new TableSeparator(), ['Kernel'], new TableSeparator(), - ['Type', \get_class($kernel)], + ['Type', $kernel::class], ['Environment', $kernel->getEnvironment()], ['Debug', $kernel->isDebug() ? 'true' : 'false'], ['Charset', $kernel->getCharset()], @@ -88,9 +80,9 @@ EOT ['Version', \PHP_VERSION], ['Architecture', (\PHP_INT_SIZE * 8).' bits'], ['Intl locale', class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'], - ['Timezone', date_default_timezone_get().' ('.(new \DateTime())->format(\DateTime::W3C).')'], - ['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], - ['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], + ['Timezone', date_default_timezone_get().' ('.(new \DateTimeImmutable())->format(\DateTimeInterface::W3C).')'], + ['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'], + ['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'], ['Xdebug', \extension_loaded('xdebug') ? 'true' : 'false'], ]; @@ -109,6 +101,10 @@ EOT if (is_file($path)) { $size = filesize($path) ?: 0; } else { + if (!is_dir($path)) { + return 'n/a'; + } + $size = 0; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)) as $file) { if ($file->isReadable()) { @@ -122,15 +118,15 @@ EOT private static function isExpired(string $date): bool { - $date = \DateTime::createFromFormat('d/m/Y', '01/'.$date); + $date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date); - return false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59'); + return false !== $date && new \DateTimeImmutable() > $date->modify('last day of this month 23:59:59'); } private static function daysBeforeExpiration(string $date): string { - $date = \DateTime::createFromFormat('d/m/Y', '01/'.$date); + $date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date); - return (new \DateTime())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days'); + return (new \DateTimeImmutable())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days'); } } diff --git a/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php b/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php index f0d5a9814..94b95e502 100644 --- a/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php +++ b/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php @@ -29,18 +29,16 @@ use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; abstract class AbstractConfigCommand extends ContainerDebugCommand { /** - * @param OutputInterface|StyleInterface $output + * @return void */ - protected function listBundles($output) + protected function listBundles(OutputInterface|StyleInterface $output) { $title = 'Available registered bundles with their extension alias if available'; $headers = ['Bundle name', 'Extension alias']; $rows = []; $bundles = $this->getApplication()->getKernel()->getBundles(); - usort($bundles, function ($bundleA, $bundleB) { - return strcmp($bundleA->getName(), $bundleB->getName()); - }); + usort($bundles, fn ($bundleA, $bundleB) => strcmp($bundleA->getName(), $bundleB->getName())); foreach ($bundles as $bundle) { $extension = $bundle->getContainerExtension(); @@ -57,10 +55,45 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand } } - /** - * @return ExtensionInterface - */ - protected function findExtension(string $name) + protected function listNonBundleExtensions(OutputInterface|StyleInterface $output): void + { + $title = 'Available registered non-bundle extension aliases'; + $headers = ['Extension alias']; + $rows = []; + + $kernel = $this->getApplication()->getKernel(); + + $bundleExtensions = []; + foreach ($kernel->getBundles() as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $bundleExtensions[$extension::class] = true; + } + } + + $extensions = $this->getContainerBuilder($kernel)->getExtensions(); + + foreach ($extensions as $alias => $extension) { + if (isset($bundleExtensions[$extension::class])) { + continue; + } + $rows[] = [$alias]; + } + + if (!$rows) { + return; + } + + if ($output instanceof StyleInterface) { + $output->title($title); + $output->table($headers, $rows); + } else { + $output->writeln($title); + $table = new Table($output); + $table->setHeaders($headers)->setRows($rows)->render(); + } + } + + protected function findExtension(string $name): ExtensionInterface { $bundles = $this->initializeBundles(); $minScore = \INF; @@ -126,7 +159,10 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand throw new LogicException($message); } - public function validateConfiguration(ExtensionInterface $extension, $configuration) + /** + * @return void + */ + public function validateConfiguration(ExtensionInterface $extension, mixed $configuration) { if (!$configuration) { throw new \LogicException(sprintf('The extension with alias "%s" does not have its getConfiguration() method setup.', $extension->getAlias())); @@ -137,7 +173,7 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand } } - private function initializeBundles() + private function initializeBundles(): array { // Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method // as this method is not called when the container is loaded from the cache. diff --git a/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php b/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php index 870e686de..264955d79 100644 --- a/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php +++ b/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; @@ -33,17 +34,15 @@ use Symfony\Component\HttpKernel\KernelInterface; * * @final */ +#[AsCommand(name: 'assets:install', description: 'Install bundle\'s web assets under a public directory')] class AssetsInstallCommand extends Command { public const METHOD_COPY = 'copy'; public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; public const METHOD_RELATIVE_SYMLINK = 'relative symlink'; - protected static $defaultName = 'assets:install'; - protected static $defaultDescription = 'Install bundle\'s web assets under a public directory'; - - private $filesystem; - private $projectDir; + private Filesystem $filesystem; + private string $projectDir; public function __construct(Filesystem $filesystem, string $projectDir) { @@ -53,10 +52,7 @@ class AssetsInstallCommand extends Command $this->projectDir = $projectDir; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -65,7 +61,6 @@ class AssetsInstallCommand extends Command ->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them') ->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks') ->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist') - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOT' The %command.name% command installs bundle assets into a given directory (e.g. the public directory). @@ -89,9 +84,6 @@ EOT ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { /** @var KernelInterface $kernel */ @@ -204,7 +196,7 @@ EOT try { $this->symlink($originDir, $targetDir, true); $method = self::METHOD_RELATIVE_SYMLINK; - } catch (IOException $e) { + } catch (IOException) { $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); } @@ -221,7 +213,7 @@ EOT try { $this->symlink($originDir, $targetDir); $method = self::METHOD_ABSOLUTE_SYMLINK; - } catch (IOException $e) { + } catch (IOException) { // fall back to copy $method = $this->hardCopy($originDir, $targetDir); } @@ -234,7 +226,7 @@ EOT * * @throws IOException if link cannot be created */ - private function symlink(string $originDir, string $targetDir, bool $relative = false) + private function symlink(string $originDir, string $targetDir, bool $relative = false): void { if ($relative) { $this->filesystem->mkdir(\dirname($targetDir)); diff --git a/lib/symfony/framework-bundle/Command/BuildDebugContainerTrait.php b/lib/symfony/framework-bundle/Command/BuildDebugContainerTrait.php index 785027dbc..2f625e9e3 100644 --- a/lib/symfony/framework-bundle/Command/BuildDebugContainerTrait.php +++ b/lib/symfony/framework-bundle/Command/BuildDebugContainerTrait.php @@ -26,7 +26,7 @@ use Symfony\Component\HttpKernel\KernelInterface; */ trait BuildDebugContainerTrait { - protected $containerBuilder; + protected ContainerBuilder $container; /** * Loads the ContainerBuilder from the cache. @@ -35,22 +35,29 @@ trait BuildDebugContainerTrait */ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilder { - if ($this->containerBuilder) { - return $this->containerBuilder; + if (isset($this->container)) { + return $this->container; } - if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { + if (!$kernel->isDebug() || !$kernel->getContainer()->getParameter('debug.container.dump') || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { $buildContainer = \Closure::bind(function () { $this->initializeBundles(); return $this->buildContainer(); - }, $kernel, \get_class($kernel)); + }, $kernel, $kernel::class); $container = $buildContainer(); $container->getCompilerPassConfig()->setRemovingPasses([]); $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); } else { - (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); + $buildContainer = \Closure::bind(function () { + $containerBuilder = $this->getContainerBuilder(); + $this->prepareContainer($containerBuilder); + + return $containerBuilder; + }, $kernel, $kernel::class); + $container = $buildContainer(); + (new XmlFileLoader($container, new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); $locatorPass = new ServiceLocatorTagPass(); $locatorPass->process($container); @@ -59,6 +66,6 @@ trait BuildDebugContainerTrait $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } - return $this->containerBuilder = $container; + return $this->container = $container; } } diff --git a/lib/symfony/framework-bundle/Command/CacheClearCommand.php b/lib/symfony/framework-bundle/Command/CacheClearCommand.php index b0d556539..878157e87 100644 --- a/lib/symfony/framework-bundle/Command/CacheClearCommand.php +++ b/lib/symfony/framework-bundle/Command/CacheClearCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; @@ -33,13 +34,11 @@ use Symfony\Component\HttpKernel\RebootableInterface; * * @final */ +#[AsCommand(name: 'cache:clear', description: 'Clear the cache')] class CacheClearCommand extends Command { - protected static $defaultName = 'cache:clear'; - protected static $defaultDescription = 'Clear the cache'; - - private $cacheClearer; - private $filesystem; + private CacheClearerInterface $cacheClearer; + private Filesystem $filesystem; public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null) { @@ -49,17 +48,13 @@ class CacheClearCommand extends Command $this->filesystem = $filesystem ?? new Filesystem(); } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'), new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command clears and warms up the application cache for a given environment and debug mode: @@ -71,9 +66,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $fs = $this->filesystem; @@ -92,7 +84,7 @@ EOF } $useBuildDir = $realBuildDir !== $realCacheDir; - $oldBuildDir = substr($realBuildDir, 0, -1).('~' === substr($realBuildDir, -1) ? '+' : '~'); + $oldBuildDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '~') ? '+' : '~'); if ($useBuildDir) { $fs->remove($oldBuildDir); @@ -122,7 +114,7 @@ EOF // the warmup cache dir name must have the same length as the real one // to avoid the many problems in serialized resources files - $warmupDir = substr($realBuildDir, 0, -1).('_' === substr($realBuildDir, -1) ? '-' : '_'); + $warmupDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '_') ? '-' : '_'); if ($output->isVerbose() && $fs->exists($warmupDir)) { $io->comment('Clearing outdated warmup directory...'); @@ -137,14 +129,7 @@ EOF if ($output->isVerbose()) { $io->comment('Warming up optional cache...'); } - $warmer = $kernel->getContainer()->get('cache_warmer'); - // non optional warmers already ran during container compilation - $warmer->enableOnlyOptionalWarmers(); - $preload = (array) $warmer->warmUp($realCacheDir); - - if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { - Preloader::append($preloadFile, $preload); - } + $this->warmupOptionals($realCacheDir, $realBuildDir, $io); } } else { $fs->mkdir($warmupDir); @@ -153,7 +138,14 @@ EOF if ($output->isVerbose()) { $io->comment('Warming up cache...'); } - $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); + $this->warmup($warmupDir, $realBuildDir); + + if (!$input->getOption('no-optional-warmers')) { + if ($output->isVerbose()) { + $io->comment('Warming up optional cache...'); + } + $this->warmupOptionals($useBuildDir ? $realCacheDir : $warmupDir, $warmupDir, $io); + } } if (!$fs->exists($warmupDir.'/'.$containerDir)) { @@ -219,7 +211,7 @@ EOF } } foreach ($mounts as $mount) { - if (0 === strpos($dir, $mount)) { + if (str_starts_with($dir, $mount)) { return true; } } @@ -227,7 +219,7 @@ EOF return false; } - private function warmup(string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true) + private function warmup(string $warmupDir, string $realBuildDir): void { // create a temporary kernel $kernel = $this->getApplication()->getKernel(); @@ -236,18 +228,6 @@ EOF } $kernel->reboot($warmupDir); - // warmup temporary dir - if ($enableOptionalWarmers) { - $warmer = $kernel->getContainer()->get('cache_warmer'); - // non optional warmers already ran during container compilation - $warmer->enableOnlyOptionalWarmers(); - $preload = (array) $warmer->warmUp($warmupDir); - - if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { - Preloader::append($preloadFile, $preload); - } - } - // fix references to cached files with the real cache directory name $search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)]; $replace = str_replace('\\', '/', $realBuildDir); @@ -258,4 +238,17 @@ EOF } } } + + private function warmupOptionals(string $cacheDir, string $warmupDir, SymfonyStyle $io): void + { + $kernel = $this->getApplication()->getKernel(); + $warmer = $kernel->getContainer()->get('cache_warmer'); + // non optional warmers already ran during container compilation + $warmer->enableOnlyOptionalWarmers(); + $preload = (array) $warmer->warmUp($cacheDir, $warmupDir, $io); + + if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + Preloader::append($preloadFile, $preload); + } + } } diff --git a/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php b/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php index b72924dfa..fcd70ca0e 100644 --- a/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php +++ b/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php @@ -12,12 +12,14 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; @@ -27,13 +29,11 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; * * @author Nicolas Grekas */ +#[AsCommand(name: 'cache:pool:clear', description: 'Clear cache pools')] final class CachePoolClearCommand extends Command { - protected static $defaultName = 'cache:pool:clear'; - protected static $defaultDescription = 'Clear cache pools'; - - private $poolClearer; - private $poolNames; + private Psr6CacheClearer $poolClearer; + private ?array $poolNames; /** * @param string[]|null $poolNames @@ -46,16 +46,14 @@ final class CachePoolClearCommand extends Command $this->poolNames = $poolNames; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ - new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers'), + new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'A list of cache pools or cache pool clearers'), ]) - ->setDescription(self::$defaultDescription) + ->addOption('all', null, InputOption::VALUE_NONE, 'Clear all cache pools') + ->addOption('exclude', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'A list of cache pools or cache pool clearers to exclude') ->setHelp(<<<'EOF' The %command.name% command clears the given cache pools or cache pool clearers. @@ -65,9 +63,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -75,7 +70,25 @@ EOF $pools = []; $clearers = []; - foreach ($input->getArgument('pools') as $id) { + $poolNames = $input->getArgument('pools'); + $excludedPoolNames = $input->getOption('exclude'); + if ($input->getOption('all')) { + if (!$this->poolNames) { + throw new InvalidArgumentException('Could not clear all cache pools, try specifying a specific pool or cache clearer.'); + } + + if (!$excludedPoolNames) { + $io->comment('Clearing all cache pools...'); + } + + $poolNames = $this->poolNames; + } elseif (!$poolNames) { + throw new InvalidArgumentException('Either specify at least one pool name, or provide the --all option to clear all pools.'); + } + + $poolNames = array_diff($poolNames, $excludedPoolNames); + + foreach ($poolNames as $id) { if ($this->poolClearer->hasPool($id)) { $pools[$id] = $id; } else { diff --git a/lib/symfony/framework-bundle/Command/CachePoolDeleteCommand.php b/lib/symfony/framework-bundle/Command/CachePoolDeleteCommand.php index b36d48cfe..7b53ceb8b 100644 --- a/lib/symfony/framework-bundle/Command/CachePoolDeleteCommand.php +++ b/lib/symfony/framework-bundle/Command/CachePoolDeleteCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -25,13 +26,11 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; * * @author Pierre du Plessis */ +#[AsCommand(name: 'cache:pool:delete', description: 'Delete an item from a cache pool')] final class CachePoolDeleteCommand extends Command { - protected static $defaultName = 'cache:pool:delete'; - protected static $defaultDescription = 'Delete an item from a cache pool'; - - private $poolClearer; - private $poolNames; + private Psr6CacheClearer $poolClearer; + private ?array $poolNames; /** * @param string[]|null $poolNames @@ -44,17 +43,13 @@ final class CachePoolDeleteCommand extends Command $this->poolNames = $poolNames; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('pool', InputArgument::REQUIRED, 'The cache pool from which to delete an item'), new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% deletes an item from a given cache pool. @@ -64,9 +59,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/lib/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php b/lib/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php new file mode 100644 index 000000000..9e6ef9330 --- /dev/null +++ b/lib/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * @author Kevin Bond + */ +#[AsCommand(name: 'cache:pool:invalidate-tags', description: 'Invalidate cache tags for all or a specific pool')] +final class CachePoolInvalidateTagsCommand extends Command +{ + private ServiceProviderInterface $pools; + private array $poolNames; + + public function __construct(ServiceProviderInterface $pools) + { + parent::__construct(); + + $this->pools = $pools; + $this->poolNames = array_keys($pools->getProvidedServices()); + } + + protected function configure(): void + { + $this + ->addArgument('tags', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The tags to invalidate') + ->addOption('pool', 'p', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The pools to invalidate on') + ->setHelp(<<<'EOF' + The %command.name% command invalidates tags from taggable pools. By default, all pools + have the passed tags invalidated. Pass --pool=my_pool to invalidate tags on a specific pool. + + php %command.full_name% tag1 tag2 + php %command.full_name% tag1 tag2 --pool=cache2 --pool=cache1 + EOF) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $pools = $input->getOption('pool') ?: $this->poolNames; + $tags = $input->getArgument('tags'); + $tagList = implode(', ', $tags); + $errors = false; + + foreach ($pools as $name) { + $io->comment(sprintf('Invalidating tag(s): %s from pool %s.', $tagList, $name)); + + try { + $pool = $this->pools->get($name); + } catch (ServiceNotFoundException) { + $io->error(sprintf('Pool "%s" not found.', $name)); + $errors = true; + + continue; + } + + if (!$pool instanceof TagAwareCacheInterface) { + $io->error(sprintf('Pool "%s" is not taggable.', $name)); + $errors = true; + + continue; + } + + if (!$pool->invalidateTags($tags)) { + $io->error(sprintf('Cache tag(s) "%s" could not be invalidated for pool "%s".', $tagList, $name)); + $errors = true; + } + } + + if ($errors) { + $io->error('Done but with errors.'); + + return 1; + } + + $io->success('Successfully invalidated cache tags.'); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('pool')) { + $suggestions->suggestValues($this->poolNames); + } + } +} diff --git a/lib/symfony/framework-bundle/Command/CachePoolListCommand.php b/lib/symfony/framework-bundle/Command/CachePoolListCommand.php index 0ad33241d..2659ad8fe 100644 --- a/lib/symfony/framework-bundle/Command/CachePoolListCommand.php +++ b/lib/symfony/framework-bundle/Command/CachePoolListCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -21,12 +22,10 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @author Tobias Nyholm */ +#[AsCommand(name: 'cache:pool:list', description: 'List available cache pools')] final class CachePoolListCommand extends Command { - protected static $defaultName = 'cache:pool:list'; - protected static $defaultDescription = 'List available cache pools'; - - private $poolNames; + private array $poolNames; /** * @param string[] $poolNames @@ -38,13 +37,9 @@ final class CachePoolListCommand extends Command $this->poolNames = $poolNames; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command lists all available cache pools. EOF @@ -52,16 +47,11 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $io->table(['Pool name'], array_map(function ($pool) { - return [$pool]; - }, $this->poolNames)); + $io->table(['Pool name'], array_map(fn ($pool) => [$pool], $this->poolNames)); return 0; } diff --git a/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php b/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php index 8d1035294..fc0dc6d79 100644 --- a/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php +++ b/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -22,12 +23,10 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @author Rob Frawley 2nd */ +#[AsCommand(name: 'cache:pool:prune', description: 'Prune cache pools')] final class CachePoolPruneCommand extends Command { - protected static $defaultName = 'cache:pool:prune'; - protected static $defaultDescription = 'Prune cache pools'; - - private $pools; + private iterable $pools; /** * @param iterable $pools @@ -39,13 +38,9 @@ final class CachePoolPruneCommand extends Command $this->pools = $pools; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command deletes all expired items from all pruneable pools. @@ -55,9 +50,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php b/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php index ddaf9eb63..6f1073de4 100644 --- a/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php +++ b/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -18,6 +19,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Dumper\Preloader; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; /** * Warmup the cache. @@ -26,12 +28,10 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; * * @final */ +#[AsCommand(name: 'cache:warmup', description: 'Warm up an empty cache')] class CacheWarmupCommand extends Command { - protected static $defaultName = 'cache:warmup'; - protected static $defaultDescription = 'Warm up an empty cache'; - - private $cacheWarmer; + private CacheWarmerAggregate $cacheWarmer; public function __construct(CacheWarmerAggregate $cacheWarmer) { @@ -40,16 +40,12 @@ class CacheWarmupCommand extends Command $this->cacheWarmer = $cacheWarmer; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command warms up the cache. @@ -60,9 +56,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -73,10 +66,16 @@ EOF if (!$input->getOption('no-optional-warmers')) { $this->cacheWarmer->enableOptionalWarmers(); } + $cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'); - $preload = $this->cacheWarmer->warmUp($cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir')); + if ($kernel instanceof WarmableInterface) { + $kernel->warmUp($cacheDir); + } - if ($preload && file_exists($preloadFile = $cacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + $preload = $this->cacheWarmer->warmUp($cacheDir); + + $buildDir = $kernel->getContainer()->getParameter('kernel.build_dir'); + if ($preload && $cacheDir === $buildDir && file_exists($preloadFile = $buildDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { Preloader::append($preloadFile, $preload); } diff --git a/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php b/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php index 12e501baa..cc116fc68 100644 --- a/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php @@ -13,11 +13,14 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; @@ -33,23 +36,22 @@ use Symfony\Component\Yaml\Yaml; * * @final */ +#[AsCommand(name: 'debug:config', description: 'Dump the current configuration for an extension')] class ConfigDebugCommand extends AbstractConfigCommand { - protected static $defaultName = 'debug:config'; - protected static $defaultDescription = 'Dump the current configuration for an extension'; - - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { + $commentedHelpFormats = array_map(fn ($format) => sprintf('%s', $format), $this->getAvailableFormatOptions()); + $helpFormats = implode('", "', $commentedHelpFormats); + $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), + new InputOption('resolve-env', null, InputOption::VALUE_NONE, 'Display resolved environment variable values instead of placeholders'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'txt' : 'json'), ]) - ->setDescription(self::$defaultDescription) - ->setHelp(<<<'EOF' + ->setHelp(<<%command.name% command dumps the current configuration for an extension/bundle. @@ -58,6 +60,11 @@ Either the extension alias or bundle name can be used: php %command.full_name% framework php %command.full_name% FrameworkBundle +The --format option specifies the format of the configuration, +these are "{$helpFormats}". + + php %command.full_name% framework --format=json + For dumping a specific option, add its path as second argument: php %command.full_name% framework serializer.enabled @@ -67,9 +74,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -77,14 +81,7 @@ EOF if (null === $name = $input->getArgument('name')) { $this->listBundles($errorIo); - - $kernel = $this->getApplication()->getKernel(); - if ($kernel instanceof ExtensionInterface - && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface) - && $kernel->getAlias() - ) { - $errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]); - } + $this->listNonBundleExtensions($errorIo); $errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. debug:config FrameworkBundle)'); $errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. debug:config FrameworkBundle serializer to dump the framework.serializer configuration)'); @@ -96,14 +93,24 @@ EOF $extensionAlias = $extension->getAlias(); $container = $this->compileContainer(); - $config = $this->getConfig($extension, $container); + $config = $this->getConfig($extension, $container, $input->getOption('resolve-env')); + + $format = $input->getOption('format'); + + if (\in_array($format, ['txt', 'yml'], true) && !class_exists(Yaml::class)) { + $errorIo->error('Setting the "format" option to "txt" or "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=json" instead.'); + + return 1; + } if (null === $path = $input->getArgument('path')) { - $io->title( - sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) - ); + if ('txt' === $input->getOption('format')) { + $io->title( + sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) + ); + } - $io->writeln(Yaml::dump([$extensionAlias => $config], 10)); + $io->writeln($this->convertToFormat([$extensionAlias => $config], $format)); return 0; } @@ -118,18 +125,26 @@ EOF $io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path)); - $io->writeln(Yaml::dump($config, 10)); + $io->writeln($this->convertToFormat($config, $format)); return 0; } + private function convertToFormat(mixed $config, string $format): string + { + return match ($format) { + 'txt', 'yaml' => Yaml::dump($config, 10), + 'json' => json_encode($config, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + } + private function compileContainer(): ContainerBuilder { $kernel = clone $this->getApplication()->getKernel(); $kernel->boot(); $method = new \ReflectionMethod($kernel, 'buildContainer'); - $method->setAccessible(true); $container = $method->invoke($kernel); $container->getCompiler()->compile($container); @@ -140,10 +155,8 @@ EOF * Iterate over configuration until the last step of the given path. * * @throws LogicException If the configuration does not exist - * - * @return mixed */ - private function getConfigForPath(array $config, string $path, string $alias) + private function getConfigForPath(array $config, string $path, string $alias): mixed { $steps = explode('.', $path); @@ -176,12 +189,12 @@ EOF // Fall back to default config if the extension has one - if (!$extension instanceof ConfigurationExtensionInterface) { + if (!$extension instanceof ConfigurationExtensionInterface && !$extension instanceof ConfigurationInterface) { throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); } $configs = $container->getExtensionConfig($extensionAlias); - $configuration = $extension->getConfiguration($configs, $container); + $configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($configs, $container); $this->validateConfiguration($extension, $configuration); return (new Processor())->processConfiguration($configuration, $configs); @@ -190,7 +203,8 @@ EOF public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('name')) { - $suggestions->suggestValues($this->getAvailableBundles(!preg_match('/^[A-Z]/', $input->getCompletionValue()))); + $suggestions->suggestValues($this->getAvailableExtensions()); + $suggestions->suggestValues($this->getAvailableBundles()); return; } @@ -200,27 +214,43 @@ EOF $config = $this->getConfig($this->findExtension($name), $this->compileContainer()); $paths = array_keys(self::buildPathsCompletion($config)); $suggestions->suggestValues($paths); - } catch (LogicException $e) { + } catch (LogicException) { } } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } } - private function getAvailableBundles(bool $alias): array + private function getAvailableExtensions(): array + { + $kernel = $this->getApplication()->getKernel(); + + $extensions = []; + foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { + $extensions[] = $alias; + } + + return $extensions; + } + + private function getAvailableBundles(): array { $availableBundles = []; foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { - $availableBundles[] = $alias ? $bundle->getContainerExtension()->getAlias() : $bundle->getName(); + $availableBundles[] = $bundle->getName(); } return $availableBundles; } - private function getConfig(ExtensionInterface $extension, ContainerBuilder $container) + private function getConfig(ExtensionInterface $extension, ContainerBuilder $container, bool $resolveEnvs = false): mixed { return $container->resolveEnvPlaceholders( $container->getParameterBag()->resolveValue( $this->getConfigForExtension($extension, $container) - ) + ), $resolveEnvs ?: null ); } @@ -229,7 +259,7 @@ EOF $completionPaths = []; foreach ($paths as $key => $values) { if (\is_array($values)) { - $completionPaths = $completionPaths + self::buildPathsCompletion($values, $prefix.$key.'.'); + $completionPaths += self::buildPathsCompletion($values, $prefix.$key.'.'); } else { $completionPaths[$prefix.$key] = null; } @@ -237,4 +267,9 @@ EOF return $completionPaths; } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'yaml', 'json']; + } } diff --git a/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php b/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php index 63e6496bd..3231e5a47 100644 --- a/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php +++ b/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php @@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper; use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -22,8 +23,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; -use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\Yaml\Yaml; /** @@ -35,24 +34,21 @@ use Symfony\Component\Yaml\Yaml; * * @final */ +#[AsCommand(name: 'config:dump-reference', description: 'Dump the default configuration for an extension')] class ConfigDumpReferenceCommand extends AbstractConfigCommand { - protected static $defaultName = 'config:dump-reference'; - protected static $defaultDescription = 'Dump the default configuration for an extension'; - - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { + $commentedHelpFormats = array_map(fn ($format) => sprintf('%s', $format), $this->getAvailableFormatOptions()); + $helpFormats = implode('", "', $commentedHelpFormats); + $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'yaml'), ]) - ->setDescription(self::$defaultDescription) - ->setHelp(<<<'EOF' + ->setHelp(<<%command.name% command dumps the default configuration for an extension/bundle. @@ -61,9 +57,8 @@ Either the extension alias or bundle name can be used: php %command.full_name% framework php %command.full_name% FrameworkBundle -With the --format option specifies the format of the configuration, -this is either yaml or xml. -When the option is not provided, yaml is used. +The --format option specifies the format of the configuration, +these are "{$helpFormats}". php %command.full_name% FrameworkBundle --format=xml @@ -77,8 +72,6 @@ EOF } /** - * {@inheritdoc} - * * @throws \LogicException */ protected function execute(InputInterface $input, OutputInterface $output): int @@ -88,14 +81,7 @@ EOF if (null === $name = $input->getArgument('name')) { $this->listBundles($errorIo); - - $kernel = $this->getApplication()->getKernel(); - if ($kernel instanceof ExtensionInterface - && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface) - && $kernel->getAlias() - ) { - $errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]); - } + $this->listNonBundleExtensions($errorIo); $errorIo->comment([ 'Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. config:dump-reference FrameworkBundle)', @@ -152,7 +138,7 @@ EOF break; default: $io->writeln($message); - throw new InvalidArgumentException('Only the yaml and xml formats are supported.'); + throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))); } $io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path)); @@ -163,6 +149,7 @@ EOF public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues($this->getAvailableExtensions()); $suggestions->suggestValues($this->getAvailableBundles()); } @@ -171,13 +158,24 @@ EOF } } + private function getAvailableExtensions(): array + { + $kernel = $this->getApplication()->getKernel(); + + $extensions = []; + foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { + $extensions[] = $alias; + } + + return $extensions; + } + private function getAvailableBundles(): array { $bundles = []; foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { $bundles[] = $bundle->getName(); - $bundles[] = $bundle->getContainerExtension()->getAlias(); } return $bundles; diff --git a/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php b/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php index 8dfebe4ae..df6aef5dd 100644 --- a/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -32,17 +33,12 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; * * @internal */ +#[AsCommand(name: 'debug:container', description: 'Display current services for an application')] class ContainerDebugCommand extends Command { use BuildDebugContainerTrait; - protected static $defaultName = 'debug:container'; - protected static $defaultDescription = 'Display current services for an application'; - - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -56,11 +52,10 @@ class ContainerDebugCommand extends Command new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'), new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'), new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays all configured public services: @@ -116,9 +111,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -134,19 +126,26 @@ EOF $options = ['env-vars' => true, 'name' => $envVar]; } elseif ($input->getOption('types')) { $options = []; - $options['filter'] = [$this, 'filterToServiceTypes']; + $options['filter'] = $this->filterToServiceTypes(...); } elseif ($input->getOption('parameters')) { $parameters = []; - foreach ($object->getParameterBag()->all() as $k => $v) { + $parameterBag = $object->getParameterBag(); + foreach ($parameterBag->all() as $k => $v) { $parameters[$k] = $object->resolveEnvPlaceholders($v); } $object = new ParameterBag($parameters); + if ($parameterBag instanceof ParameterBag) { + foreach ($parameterBag->allDeprecated() as $k => $deprecation) { + $object->deprecate($k, ...$deprecation); + } + } $options = []; } elseif ($parameter = $input->getOption('parameter')) { $options = ['parameter' => $parameter]; } elseif ($input->getOption('tags')) { $options = ['group_by' => 'tags']; } elseif ($tag = $input->getOption('tag')) { + $tag = $this->findProperTagName($input, $errorIo, $object, $tag); $options = ['tag' => $tag]; } elseif ($name = $input->getArgument('name')) { $name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden')); @@ -168,6 +167,21 @@ EOF try { $helper->describe($io, $object, $options); + if ('txt' === $options['format'] && isset($options['id'])) { + if ($object->hasDefinition($options['id'])) { + $definition = $object->getDefinition($options['id']); + if ($definition->isDeprecated()) { + $errorIo->warning($definition->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" service is deprecated.', $options['id'])); + } + } + if ($object->hasAlias($options['id'])) { + $alias = $object->getAlias($options['id']); + if ($alias->isDeprecated()) { + $errorIo->warning($alias->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" alias is deprecated.', $options['id'])); + } + } + } + if (isset($options['id']) && isset($kernel->getContainer()->getRemovedIds()[$options['id']])) { $errorIo->note(sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id'])); } @@ -195,8 +209,7 @@ EOF public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestOptionValuesFor('format')) { - $helper = new DescriptorHelper(); - $suggestions->suggestValues($helper->getFormats()); + $suggestions->suggestValues($this->getAvailableFormatOptions()); return; } @@ -235,7 +248,7 @@ EOF * * @throws \InvalidArgumentException */ - protected function validateInput(InputInterface $input) + protected function validateInput(InputInterface $input): void { $options = ['tags', 'tag', 'parameters', 'parameter']; @@ -254,16 +267,16 @@ EOF } } - private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden): string + private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $container, string $name, bool $showHidden): string { $name = ltrim($name, '\\'); - if ($builder->has($name) || !$input->isInteractive()) { + if ($container->has($name) || !$input->isInteractive()) { return $name; } - $matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden); - if (empty($matchingServices)) { + $matchingServices = $this->findServiceIdsContaining($container, $name, $showHidden); + if (!$matchingServices) { throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name)); } @@ -274,14 +287,35 @@ EOF return $io->choice('Select one of the following services to display its information', $matchingServices); } - private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden): array + private function findProperTagName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $container, string $tagName): string { - $serviceIds = $builder->getServiceIds(); + if (\in_array($tagName, $container->findTags(), true) || !$input->isInteractive()) { + return $tagName; + } + + $matchingTags = $this->findTagsContaining($container, $tagName); + if (!$matchingTags) { + throw new InvalidArgumentException(sprintf('No tags found that match "%s".', $tagName)); + } + + if (1 === \count($matchingTags)) { + return $matchingTags[0]; + } + + return $io->choice('Select one of the following tags to display its information', $matchingTags); + } + + private function findServiceIdsContaining(ContainerBuilder $container, string $name, bool $showHidden): array + { + $serviceIds = $container->getServiceIds(); $foundServiceIds = $foundServiceIdsIgnoringBackslashes = []; foreach ($serviceIds as $serviceId) { if (!$showHidden && str_starts_with($serviceId, '.')) { continue; } + if (!$showHidden && $container->hasDefinition($serviceId) && $container->getDefinition($serviceId)->hasTag('container.excluded')) { + continue; + } if (false !== stripos(str_replace('\\', '', $serviceId), $name)) { $foundServiceIdsIgnoringBackslashes[] = $serviceId; } @@ -293,6 +327,19 @@ EOF return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes; } + private function findTagsContaining(ContainerBuilder $container, string $tagName): array + { + $tags = $container->findTags(); + $foundTags = []; + foreach ($tags as $tag) { + if (str_contains($tag, $tagName)) { + $foundTags[] = $tag; + } + } + + return $foundTags; + } + /** * @internal */ @@ -310,4 +357,9 @@ EOF return class_exists($serviceId) || interface_exists($serviceId, false); } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } } diff --git a/lib/symfony/framework-bundle/Command/ContainerLintCommand.php b/lib/symfony/framework-bundle/Command/ContainerLintCommand.php index 337f1f420..b63ebe431 100644 --- a/lib/symfony/framework-bundle/Command/ContainerLintCommand.php +++ b/lib/symfony/framework-bundle/Command/ContainerLintCommand.php @@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; @@ -20,6 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\ResolveFactoryClassPass; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -27,30 +29,18 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\HttpKernel\Kernel; +#[AsCommand(name: 'lint:container', description: 'Ensure that arguments injected into services match type declarations')] final class ContainerLintCommand extends Command { - protected static $defaultName = 'lint:container'; - protected static $defaultDescription = 'Ensure that arguments injected into services match type declarations'; + private ContainerBuilder $container; - /** - * @var ContainerBuilder - */ - private $containerBuilder; - - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.') ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -81,14 +71,14 @@ final class ContainerLintCommand extends Command private function getContainerBuilder(): ContainerBuilder { - if ($this->containerBuilder) { - return $this->containerBuilder; + if (isset($this->container)) { + return $this->container; } $kernel = $this->getApplication()->getKernel(); $kernelContainer = $kernel->getContainer(); - if (!$kernel->isDebug() || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) { + if (!$kernel->isDebug() || !$kernelContainer->getParameter('debug.container.dump') || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) { if (!$kernel instanceof Kernel) { throw new RuntimeException(sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class)); } @@ -97,10 +87,8 @@ final class ContainerLintCommand extends Command $this->initializeBundles(); return $this->buildContainer(); - }, $kernel, \get_class($kernel)); + }, $kernel, $kernel::class); $container = $buildContainer(); - - $skippedIds = []; } else { if (!$kernelContainer instanceof Container) { throw new RuntimeException(sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class)); @@ -109,26 +97,18 @@ final class ContainerLintCommand extends Command (new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump')); $refl = new \ReflectionProperty($parameterBag, 'resolved'); - $refl->setAccessible(true); $refl->setValue($parameterBag, true); - $skippedIds = []; - foreach ($container->getServiceIds() as $serviceId) { - if (str_starts_with($serviceId, '.errored.')) { - $skippedIds[$serviceId] = true; - } - } - $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); - $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([new ResolveFactoryClassPass()]); $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } $container->setParameter('container.build_hash', 'lint_container'); $container->setParameter('container.build_id', 'lint_container'); - $container->addCompilerPass(new CheckTypeDeclarationsPass(true, $skippedIds), PassConfig::TYPE_AFTER_REMOVING, -100); + $container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100); - return $this->containerBuilder = $container; + return $this->container = $container; } } diff --git a/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php b/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php index e1e3c9534..ab4793b68 100644 --- a/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php +++ b/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php @@ -12,15 +12,16 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; -use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\DependencyInjection\Attribute\Target; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; /** * A console command for autowiring information. @@ -29,32 +30,24 @@ use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; * * @internal */ +#[AsCommand(name: 'debug:autowiring', description: 'List classes/interfaces you can use for autowiring')] class DebugAutowiringCommand extends ContainerDebugCommand { - protected static $defaultName = 'debug:autowiring'; - protected static $defaultDescription = 'List classes/interfaces you can use for autowiring'; - - private $supportsHref; - private $fileLinkFormatter; + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(string $name = null, FileLinkFormatter $fileLinkFormatter = null) { - $this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref'); $this->fileLinkFormatter = $fileLinkFormatter; parent::__construct($name); } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'), new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays the classes and interfaces that you can use as type-hints for autowiring: @@ -70,32 +63,35 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); - $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); - $serviceIds = $builder->getServiceIds(); - $serviceIds = array_filter($serviceIds, [$this, 'filterToServiceTypes']); + $container = $this->getContainerBuilder($this->getApplication()->getKernel()); + $serviceIds = $container->getServiceIds(); + $serviceIds = array_filter($serviceIds, $this->filterToServiceTypes(...)); if ($search = $input->getArgument('search')) { $searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search); - $serviceIds = array_filter($serviceIds, function ($serviceId) use ($searchNormalized) { - return false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.'); - }); + $serviceIds = array_filter($serviceIds, fn ($serviceId) => false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.')); - if (empty($serviceIds)) { + if (!$serviceIds) { $errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search)); return 1; } } + $reverseAliases = []; + + foreach ($container->getAliases() as $id => $alias) { + if ('.' === ($id[0] ?? null)) { + $reverseAliases[(string) $alias][] = $id; + } + } + uasort($serviceIds, 'strnatcmp'); $io->title('Autowirable Types'); @@ -108,28 +104,53 @@ EOF $previousId = '-'; $serviceIdsNb = 0; foreach ($serviceIds as $serviceId) { + if ($container->hasDefinition($serviceId) && $container->getDefinition($serviceId)->hasTag('container.excluded')) { + continue; + } $text = []; $resolvedServiceId = $serviceId; - if (!str_starts_with($serviceId, $previousId)) { + if (!str_starts_with($serviceId, $previousId.' $')) { $text[] = ''; - if ('' !== $description = Descriptor::getClassDescription($serviceId, $resolvedServiceId)) { - if (isset($hasAlias[$serviceId])) { + $previousId = preg_replace('/ \$.*/', '', $serviceId); + if ('' !== $description = Descriptor::getClassDescription($previousId, $resolvedServiceId)) { + if (isset($hasAlias[$previousId])) { continue; } $text[] = $description; } - $previousId = $serviceId.' $'; } $serviceLine = sprintf('%s', $serviceId); - if ($this->supportsHref && '' !== $fileLink = $this->getFileLink($serviceId)) { - $serviceLine = sprintf('%s', $fileLink, $serviceId); + if ('' !== $fileLink = $this->getFileLink($previousId)) { + $serviceLine = substr($serviceId, \strlen($previousId)); + $serviceLine = sprintf('%s', $fileLink, $previousId).('' !== $serviceLine ? sprintf('%s', $serviceLine) : ''); } - if ($builder->hasAlias($serviceId)) { + if ($container->hasAlias($serviceId)) { $hasAlias[$serviceId] = true; - $serviceAlias = $builder->getAlias($serviceId); - $serviceLine .= ' ('.$serviceAlias.')'; + $serviceAlias = $container->getAlias($serviceId); + $alias = (string) $serviceAlias; + + $target = null; + foreach ($reverseAliases[(string) $serviceAlias] ?? [] as $id) { + if (!str_starts_with($id, '.'.$previousId.' $')) { + continue; + } + $target = substr($id, \strlen($previousId) + 3); + + if ($previousId.' $'.(new Target($target))->getParsedName() === $serviceId) { + $serviceLine .= ' - target:'.$target.''; + break; + } + } + + if ($container->hasDefinition($serviceAlias) && $decorated = $container->getDefinition($serviceAlias)->getTag('container.decorator')) { + $alias = $decorated[0]['id']; + } + + if ($alias !== $target) { + $serviceLine .= ' - alias:'.$alias.''; + } if ($serviceAlias->isDeprecated()) { $serviceLine .= ' - deprecated'; @@ -137,6 +158,8 @@ EOF } elseif (!$all) { ++$serviceIdsNb; continue; + } elseif ($container->getDefinition($serviceId)->isDeprecated()) { + $serviceLine .= ' - deprecated'; } $text[] = $serviceLine; $io->text($text); @@ -169,9 +192,9 @@ EOF public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('search')) { - $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); + $container = $this->getContainerBuilder($this->getApplication()->getKernel()); - $suggestions->suggestValues(array_filter($builder->getServiceIds(), [$this, 'filterToServiceTypes'])); + $suggestions->suggestValues(array_filter($container->getServiceIds(), $this->filterToServiceTypes(...))); } } } diff --git a/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php b/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php index cfc9ae2fb..1a74e8682 100644 --- a/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php @@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -31,13 +32,12 @@ use Symfony\Contracts\Service\ServiceProviderInterface; * * @final */ +#[AsCommand(name: 'debug:event-dispatcher', description: 'Display configured listeners for an application')] class EventDispatcherDebugCommand extends Command { private const DEFAULT_DISPATCHER = 'event_dispatcher'; - protected static $defaultName = 'debug:event-dispatcher'; - protected static $defaultDescription = 'Display configured listeners for an application'; - private $dispatchers; + private ContainerInterface $dispatchers; public function __construct(ContainerInterface $dispatchers) { @@ -46,19 +46,15 @@ class EventDispatcherDebugCommand extends Command $this->dispatchers = $dispatchers; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'), new InputOption('dispatcher', null, InputOption::VALUE_REQUIRED, 'To view events of a specific event dispatcher', self::DEFAULT_DISPATCHER), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays all configured listeners: @@ -73,8 +69,6 @@ EOF } /** - * {@inheritdoc} - * * @throws \LogicException */ protected function execute(InputInterface $input, OutputInterface $output): int @@ -144,7 +138,7 @@ EOF } if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues((new DescriptorHelper())->getFormats()); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } @@ -161,4 +155,9 @@ EOF return $output; } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } } diff --git a/lib/symfony/framework-bundle/Command/RouterDebugCommand.php b/lib/symfony/framework-bundle/Command/RouterDebugCommand.php index cf929f987..8d4e38a29 100644 --- a/lib/symfony/framework-bundle/Command/RouterDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/RouterDebugCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -21,7 +22,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouterInterface; @@ -33,14 +34,13 @@ use Symfony\Component\Routing\RouterInterface; * * @final */ +#[AsCommand(name: 'debug:router', description: 'Display current routes for an application')] class RouterDebugCommand extends Command { use BuildDebugContainerTrait; - protected static $defaultName = 'debug:router'; - protected static $defaultDescription = 'Display current routes for an application'; - private $router; - private $fileLinkFormatter; + private RouterInterface $router; + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(RouterInterface $router, FileLinkFormatter $fileLinkFormatter = null) { @@ -50,19 +50,16 @@ class RouterDebugCommand extends Command $this->fileLinkFormatter = $fileLinkFormatter; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'A route name'), new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('show-aliases', null, InputOption::VALUE_NONE, 'Show aliases in overview'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% displays the configured routes: @@ -74,8 +71,6 @@ EOF } /** - * {@inheritdoc} - * * @throws InvalidArgumentException When route does not exist */ protected function execute(InputInterface $input, OutputInterface $output): int @@ -86,9 +81,7 @@ EOF $routes = $this->router->getRouteCollection(); $container = null; if ($this->fileLinkFormatter) { - $container = function () { - return $this->getContainerBuilder($this->getApplication()->getKernel()); - }; + $container = fn () => $this->getContainerBuilder($this->getApplication()->getKernel()); } if ($name) { @@ -100,6 +93,7 @@ EOF 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), + 'show_aliases' => $input->getOption('show-aliases'), 'output' => $io, ]); @@ -128,6 +122,7 @@ EOF 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), + 'show_aliases' => $input->getOption('show-aliases'), 'output' => $io, 'container' => $container, ]); @@ -157,8 +152,7 @@ EOF } if ($input->mustSuggestOptionValuesFor('format')) { - $helper = new DescriptorHelper(); - $suggestions->suggestValues($helper->getFormats()); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } @@ -173,4 +167,9 @@ EOF return $foundRoutes; } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } } diff --git a/lib/symfony/framework-bundle/Command/RouterMatchCommand.php b/lib/symfony/framework-bundle/Command/RouterMatchCommand.php index 6cceb945d..7efd1f3ed 100644 --- a/lib/symfony/framework-bundle/Command/RouterMatchCommand.php +++ b/lib/symfony/framework-bundle/Command/RouterMatchCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; @@ -29,13 +30,11 @@ use Symfony\Component\Routing\RouterInterface; * * @final */ +#[AsCommand(name: 'router:match', description: 'Help debug routes by simulating a path info match')] class RouterMatchCommand extends Command { - protected static $defaultName = 'router:match'; - protected static $defaultDescription = 'Help debug routes by simulating a path info match'; - - private $router; - private $expressionLanguageProviders; + private RouterInterface $router; + private iterable $expressionLanguageProviders; /** * @param iterable $expressionLanguageProviders @@ -48,10 +47,7 @@ class RouterMatchCommand extends Command $this->expressionLanguageProviders = $expressionLanguageProviders; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -60,7 +56,6 @@ class RouterMatchCommand extends Command new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Set the URI scheme (usually http or https)'), new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% shows which routes match a given request and which don't and for what reason: @@ -75,9 +70,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/lib/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php b/lib/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php index 0e07d88fa..22be89502 100644 --- a/lib/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php +++ b/lib/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -24,13 +25,11 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @internal */ +#[AsCommand(name: 'secrets:decrypt-to-local', description: 'Decrypt all secrets and stores them in the local vault')] final class SecretsDecryptToLocalCommand extends Command { - protected static $defaultName = 'secrets:decrypt-to-local'; - protected static $defaultDescription = 'Decrypt all secrets and stores them in the local vault'; - - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { @@ -40,10 +39,9 @@ final class SecretsDecryptToLocalCommand extends Command parent::__construct(); } - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault') ->setHelp(<<<'EOF' The %command.name% command decrypts all secrets and copies them in the local vault. diff --git a/lib/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php b/lib/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php index 79f51c51a..4c613ef7b 100644 --- a/lib/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php +++ b/lib/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; @@ -23,13 +24,11 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @internal */ +#[AsCommand(name: 'secrets:encrypt-from-local', description: 'Encrypt all local secrets to the vault')] final class SecretsEncryptFromLocalCommand extends Command { - protected static $defaultName = 'secrets:encrypt-from-local'; - protected static $defaultDescription = 'Encrypt all local secrets to the vault'; - - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { @@ -39,10 +38,9 @@ final class SecretsEncryptFromLocalCommand extends Command parent::__construct(); } - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command encrypts all locally overridden secrets to the vault. diff --git a/lib/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php b/lib/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php index a9440b4c8..761f6c260 100644 --- a/lib/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php +++ b/lib/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -26,13 +27,11 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @internal */ +#[AsCommand(name: 'secrets:generate-keys', description: 'Generate new encryption keys')] final class SecretsGenerateKeysCommand extends Command { - protected static $defaultName = 'secrets:generate-keys'; - protected static $defaultDescription = 'Generate new encryption keys'; - - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { @@ -42,10 +41,9 @@ final class SecretsGenerateKeysCommand extends Command parent::__construct(); } - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') ->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.') ->setHelp(<<<'EOF' diff --git a/lib/symfony/framework-bundle/Command/SecretsListCommand.php b/lib/symfony/framework-bundle/Command/SecretsListCommand.php index 0b13e0cf2..de8a7e772 100644 --- a/lib/symfony/framework-bundle/Command/SecretsListCommand.php +++ b/lib/symfony/framework-bundle/Command/SecretsListCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Dumper; use Symfony\Component\Console\Input\InputInterface; @@ -27,13 +28,11 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @internal */ +#[AsCommand(name: 'secrets:list', description: 'List all secrets')] final class SecretsListCommand extends Command { - protected static $defaultName = 'secrets:list'; - protected static $defaultDescription = 'List all secrets'; - - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { @@ -43,10 +42,9 @@ final class SecretsListCommand extends Command parent::__construct(); } - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names') ->setHelp(<<<'EOF' The %command.name% command list all stored secrets. @@ -72,14 +70,12 @@ EOF } $secrets = $this->vault->list($reveal); - $localSecrets = null !== $this->localVault ? $this->localVault->list($reveal) : null; + $localSecrets = $this->localVault?->list($reveal); $rows = []; $dump = new Dumper($output); - $dump = static function (?string $v) use ($dump) { - return null === $v ? '******' : $dump($v); - }; + $dump = fn ($v) => null === $v ? '******' : $dump($v); foreach ($secrets as $name => $value) { $rows[$name] = [$name, $dump($value)]; diff --git a/lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php b/lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php index 0451ef300..e03afcd0c 100644 --- a/lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php +++ b/lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -28,13 +29,11 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @internal */ +#[AsCommand(name: 'secrets:remove', description: 'Remove a secret from the vault')] final class SecretsRemoveCommand extends Command { - protected static $defaultName = 'secrets:remove'; - protected static $defaultDescription = 'Remove a secret from the vault'; - - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { @@ -44,10 +43,9 @@ final class SecretsRemoveCommand extends Command parent::__construct(); } - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') ->setHelp(<<<'EOF' diff --git a/lib/symfony/framework-bundle/Command/SecretsSetCommand.php b/lib/symfony/framework-bundle/Command/SecretsSetCommand.php index 412247da7..0e831a343 100644 --- a/lib/symfony/framework-bundle/Command/SecretsSetCommand.php +++ b/lib/symfony/framework-bundle/Command/SecretsSetCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -29,13 +30,11 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @internal */ +#[AsCommand(name: 'secrets:set', description: 'Set a secret in the vault')] final class SecretsSetCommand extends Command { - protected static $defaultName = 'secrets:set'; - protected static $defaultDescription = 'Set a secret in the vault'; - - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { @@ -45,10 +44,9 @@ final class SecretsSetCommand extends Command parent::__construct(); } - protected function configure() + protected function configure(): void { $this - ->setDescription(self::$defaultDescription) ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') ->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN') ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') diff --git a/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php b/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php index 006fd2505..79a67847a 100644 --- a/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -38,6 +39,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; * * @final */ +#[AsCommand(name: 'debug:translation', description: 'Display translation messages information')] class TranslationDebugCommand extends Command { public const EXIT_CODE_GENERAL_ERROR = 64; @@ -48,17 +50,14 @@ class TranslationDebugCommand extends Command public const MESSAGE_UNUSED = 1; public const MESSAGE_EQUALS_FALLBACK = 2; - protected static $defaultName = 'debug:translation'; - protected static $defaultDescription = 'Display translation messages information'; - - private $translator; - private $reader; - private $extractor; - private $defaultTransPath; - private $defaultViewsPath; - private $transPaths; - private $codePaths; - private $enabledLocales; + private TranslatorInterface $translator; + private TranslationReaderInterface $reader; + private ExtractorInterface $extractor; + private ?string $defaultTransPath; + private ?string $defaultViewsPath; + private array $transPaths; + private array $codePaths; + private array $enabledLocales; public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { @@ -74,10 +73,7 @@ class TranslationDebugCommand extends Command $this->enabledLocales = $enabledLocales; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -88,7 +84,6 @@ class TranslationDebugCommand extends Command new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'), new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command helps finding unused or missing translation messages and comparing them with the fallback ones by inspecting the @@ -123,9 +118,6 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -155,7 +147,7 @@ EOF if ($this->defaultViewsPath) { $codePaths[] = $this->defaultViewsPath; } - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { // such a bundle does not exist, so treat the argument as path $path = $input->getArgument('bundle'); @@ -188,7 +180,7 @@ EOF } // No defined or extracted messages - if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) { + if (!$allMessages || null !== $domain && empty($allMessages[$domain])) { $outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale); if (null !== $domain) { @@ -220,14 +212,14 @@ EOF $states[] = self::MESSAGE_MISSING; if (!$input->getOption('only-unused')) { - $exitCode = $exitCode | self::EXIT_CODE_MISSING; + $exitCode |= self::EXIT_CODE_MISSING; } } } elseif ($currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_UNUSED; if (!$input->getOption('only-missing')) { - $exitCode = $exitCode | self::EXIT_CODE_UNUSED; + $exitCode |= self::EXIT_CODE_UNUSED; } } @@ -241,7 +233,7 @@ EOF if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) { $states[] = self::MESSAGE_EQUALS_FALLBACK; - $exitCode = $exitCode | self::EXIT_CODE_FALLBACK; + $exitCode |= self::EXIT_CODE_FALLBACK; break; } diff --git a/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php b/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php index f0e1dc887..4563779ba 100644 --- a/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php +++ b/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -38,6 +39,7 @@ use Symfony\Component\Translation\Writer\TranslationWriterInterface; * * @final */ +#[AsCommand(name: 'translation:extract', description: 'Extract missing translations keys from code to translation files')] class TranslationUpdateCommand extends Command { private const ASC = 'asc'; @@ -48,18 +50,15 @@ class TranslationUpdateCommand extends Command 'xlf20' => ['xlf', '2.0'], ]; - protected static $defaultName = 'translation:extract|translation:update'; - protected static $defaultDescription = 'Extract missing translations keys from code to translation files.'; - - private $writer; - private $reader; - private $extractor; - private $defaultLocale; - private $defaultTransPath; - private $defaultViewsPath; - private $transPaths; - private $codePaths; - private $enabledLocales; + private TranslationWriterInterface $writer; + private TranslationReaderInterface $reader; + private ExtractorInterface $extractor; + private string $defaultLocale; + private ?string $defaultTransPath; + private ?string $defaultViewsPath; + private array $transPaths; + private array $codePaths; + private array $enabledLocales; public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { @@ -76,27 +75,21 @@ class TranslationUpdateCommand extends Command $this->enabledLocales = $enabledLocales; } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'), new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'), - new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format (deprecated)'), new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf12'), new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'), new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'), new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'), new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'), - new InputOption('xliff-version', null, InputOption::VALUE_OPTIONAL, 'Override the default xliff version (deprecated)'), - new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically', 'asc'), + new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically (only works with --dump-messages)', 'asc'), new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command extracts translation strings from templates of a given bundle or the default translations directory. It can display them or merge @@ -123,16 +116,12 @@ You can sort the output with the --sort flag: You can dump a tree-like structure using the yaml format with --as-tree flag: php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle - php %command.full_name% --force --format=yaml --sort=asc --as-tree=3 fr EOF ) ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -152,18 +141,10 @@ EOF return 1; } - $format = $input->getOption('output-format') ?: $input->getOption('format'); - $xliffVersion = $input->getOption('xliff-version') ?? '1.2'; + $format = $input->getOption('format'); + $xliffVersion = '1.2'; - if ($input->getOption('xliff-version')) { - $errorIo->warning(sprintf('The "--xliff-version" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion)); - } - - if ($input->getOption('output-format')) { - $errorIo->warning(sprintf('The "--output-format" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion)); - } - - if (\in_array($format, array_keys(self::FORMATS), true)) { + if (\array_key_exists($format, self::FORMATS)) { [$format, $xliffVersion] = self::FORMATS[$format]; } @@ -198,7 +179,7 @@ EOF $codePaths[] = $this->defaultViewsPath; } $currentName = $foundBundle->getName(); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { // such a bundle does not exist, so treat the argument as path $path = $input->getArgument('bundle'); @@ -251,12 +232,8 @@ EOF $list = array_merge( array_diff($allKeys, $newKeys), - array_map(function ($id) { - return sprintf('%s', $id); - }, $newKeys), - array_map(function ($id) { - return sprintf('%s', $id); - }, array_keys($operation->getObsoleteMessages($domain))) + array_map(fn ($id) => sprintf('%s', $id), $newKeys), + array_map(fn ($id) => sprintf('%s', $id), array_keys($operation->getObsoleteMessages($domain))) ); $domainMessagesCount = \count($list); diff --git a/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php b/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php index 89d29b981..f84a560c6 100644 --- a/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php +++ b/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -19,28 +20,31 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Dumper\GraphvizDumper; use Symfony\Component\Workflow\Dumper\MermaidDumper; use Symfony\Component\Workflow\Dumper\PlantUmlDumper; use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\StateMachine; /** * @author GrĂ©goire Pineau * * @final */ +#[AsCommand(name: 'workflow:dump', description: 'Dump a workflow')] class WorkflowDumpCommand extends Command { - protected static $defaultName = 'workflow:dump'; - protected static $defaultDescription = 'Dump a workflow'; /** * string is the service id. * * @var array */ - private $workflows = []; + private array $definitions = []; + + private ServiceLocator $workflows; private const DUMP_FORMAT_OPTIONS = [ 'puml', @@ -48,26 +52,30 @@ class WorkflowDumpCommand extends Command 'dot', ]; - public function __construct(array $workflows) + public function __construct($workflows) { parent::__construct(); - $this->workflows = $workflows; + if ($workflows instanceof ServiceLocator) { + $this->workflows = $workflows; + } elseif (\is_array($workflows)) { + $this->definitions = $workflows; + trigger_deprecation('symfony/framework-bundle', '6.2', 'Passing an array of definitions in "%s()" is deprecated. Inject a ServiceLocator filled with all workflows instead.', __METHOD__); + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an array or a ServiceLocator, "%s" given.', __METHOD__, \gettype($workflows))); + } } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'), new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'), new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'), + new InputOption('with-metadata', null, InputOption::VALUE_NONE, 'Include the workflow\'s metadata in the dumped graph', null), new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command dumps the graphical representation of a workflow in different formats @@ -80,24 +88,26 @@ EOF ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $workflowName = $input->getArgument('name'); - $workflow = null; - - if (isset($this->workflows['workflow.'.$workflowName])) { - $workflow = $this->workflows['workflow.'.$workflowName]; + if (isset($this->workflows)) { + if (!$this->workflows->has($workflowName)) { + throw new InvalidArgumentException(sprintf('The workflow named "%s" cannot be found.', $workflowName)); + } + $workflow = $this->workflows->get($workflowName); + $type = $workflow instanceof StateMachine ? 'state_machine' : 'workflow'; + $definition = $workflow->getDefinition(); + } elseif (isset($this->definitions['workflow.'.$workflowName])) { + $definition = $this->definitions['workflow.'.$workflowName]; $type = 'workflow'; - } elseif (isset($this->workflows['state_machine.'.$workflowName])) { - $workflow = $this->workflows['state_machine.'.$workflowName]; + } elseif (isset($this->definitions['state_machine.'.$workflowName])) { + $definition = $this->definitions['state_machine.'.$workflowName]; $type = 'state_machine'; } - if (null === $workflow) { + if (null === $definition) { throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowName)); } @@ -125,12 +135,11 @@ EOF $options = [ 'name' => $workflowName, + 'with-metadata' => $input->getOption('with-metadata'), 'nofooter' => true, - 'graph' => [ - 'label' => $input->getOption('label'), - ], + 'label' => $input->getOption('label'), ]; - $output->writeln($dumper->dump($workflow, $marking, $options)); + $output->writeln($dumper->dump($definition, $marking, $options)); return 0; } @@ -138,7 +147,11 @@ EOF public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('name')) { - $suggestions->suggestValues(array_keys($this->workflows)); + if (isset($this->workflows)) { + $suggestions->suggestValues(array_keys($this->workflows->getProvidedServices())); + } else { + $suggestions->suggestValues(array_keys($this->definitions)); + } } if ($input->mustSuggestOptionValuesFor('dump-format')) { diff --git a/lib/symfony/framework-bundle/Command/XliffLintCommand.php b/lib/symfony/framework-bundle/Command/XliffLintCommand.php index b1f631739..5b094f165 100644 --- a/lib/symfony/framework-bundle/Command/XliffLintCommand.php +++ b/lib/symfony/framework-bundle/Command/XliffLintCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand; /** @@ -22,11 +23,9 @@ use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand; * * @final */ +#[AsCommand(name: 'lint:xliff', description: 'Lint an XLIFF file and outputs encountered errors')] class XliffLintCommand extends BaseLintCommand { - protected static $defaultName = 'lint:xliff'; - protected static $defaultDescription = 'Lints an XLIFF file and outputs encountered errors'; - public function __construct() { $directoryIteratorProvider = function ($directory, $default) { @@ -37,17 +36,12 @@ class XliffLintCommand extends BaseLintCommand return $default($directory); }; - $isReadableProvider = function ($fileOrDirectory, $default) { - return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); - }; + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); diff --git a/lib/symfony/framework-bundle/Command/YamlLintCommand.php b/lib/symfony/framework-bundle/Command/YamlLintCommand.php index 3a432f275..141390812 100644 --- a/lib/symfony/framework-bundle/Command/YamlLintCommand.php +++ b/lib/symfony/framework-bundle/Command/YamlLintCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand; /** @@ -21,11 +22,9 @@ use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand; * * @final */ +#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')] class YamlLintCommand extends BaseLintCommand { - protected static $defaultName = 'lint:yaml'; - protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors'; - public function __construct() { $directoryIteratorProvider = function ($directory, $default) { @@ -36,17 +35,12 @@ class YamlLintCommand extends BaseLintCommand return $default($directory); }; - $isReadableProvider = function ($fileOrDirectory, $default) { - return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); - }; + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); diff --git a/lib/symfony/framework-bundle/Console/Application.php b/lib/symfony/framework-bundle/Console/Application.php index 55510b9d5..b8bae8fc2 100644 --- a/lib/symfony/framework-bundle/Console/Application.php +++ b/lib/symfony/framework-bundle/Console/Application.php @@ -14,6 +14,8 @@ namespace Symfony\Bundle\FrameworkBundle\Console; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Command\TraceableCommand; +use Symfony\Component\Console\Debug\CliRequest; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutputInterface; @@ -29,9 +31,9 @@ use Symfony\Component\HttpKernel\KernelInterface; */ class Application extends BaseApplication { - private $kernel; - private $commandsRegistered = false; - private $registrationErrors = []; + private KernelInterface $kernel; + private bool $commandsRegistered = false; + private array $registrationErrors = []; public function __construct(KernelInterface $kernel) { @@ -42,22 +44,18 @@ class Application extends BaseApplication $inputDefinition = $this->getDefinition(); $inputDefinition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $kernel->getEnvironment())); $inputDefinition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switch off debug mode.')); + $inputDefinition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Enables profiling (requires debug).')); } /** * Gets the Kernel associated with this Console. - * - * @return KernelInterface */ - public function getKernel() + public function getKernel(): KernelInterface { return $this->kernel; } - /** - * {@inheritdoc} - */ - public function reset() + public function reset(): void { if ($this->kernel->getContainer()->has('services_resetter')) { $this->kernel->getContainer()->get('services_resetter')->reset(); @@ -69,7 +67,7 @@ class Application extends BaseApplication * * @return int 0 if everything went fine, or an error code */ - public function doRun(InputInterface $input, OutputInterface $output) + public function doRun(InputInterface $input, OutputInterface $output): int { $this->registerCommands(); @@ -82,23 +80,55 @@ class Application extends BaseApplication return parent::doRun($input, $output); } - /** - * {@inheritdoc} - */ - protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int { + $requestStack = null; + $renderRegistrationErrors = true; + if (!$command instanceof ListCommand) { if ($this->registrationErrors) { $this->renderRegistrationErrors($input, $output); $this->registrationErrors = []; + $renderRegistrationErrors = false; } - - return parent::doRunCommand($command, $input, $output); } - $returnCode = parent::doRunCommand($command, $input, $output); + if ($input->hasParameterOption('--profile')) { + $container = $this->kernel->getContainer(); - if ($this->registrationErrors) { + if (!$this->kernel->isDebug()) { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('Debug mode should be enabled when the "--profile" option is used.'); + } elseif (!$container->has('debug.stopwatch')) { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('The "--profile" option needs the Stopwatch component. Try running "composer require symfony/stopwatch".'); + } elseif (!$container->has('.virtual_request_stack')) { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('The "--profile" option needs the profiler integration. Try enabling the "framework.profiler" option.'); + } else { + $command = new TraceableCommand($command, $container->get('debug.stopwatch')); + + $requestStack = $container->get('.virtual_request_stack'); + $requestStack->push(new CliRequest($command)); + } + } + + try { + $returnCode = parent::doRunCommand($command, $input, $output); + } finally { + $requestStack?->pop(); + } + + if ($renderRegistrationErrors && $this->registrationErrors) { $this->renderRegistrationErrors($input, $output); $this->registrationErrors = []; } @@ -106,57 +136,49 @@ class Application extends BaseApplication return $returnCode; } - /** - * {@inheritdoc} - */ - public function find(string $name) + public function find(string $name): Command { $this->registerCommands(); return parent::find($name); } - /** - * {@inheritdoc} - */ - public function get(string $name) + public function get(string $name): Command { $this->registerCommands(); $command = parent::get($name); if ($command instanceof ContainerAwareInterface) { + trigger_deprecation('symfony/dependency-injection', '6.4', 'Relying on "%s" to get the container in "%s" is deprecated, register the command as a service and use dependency injection instead.', ContainerAwareInterface::class, get_debug_type($command)); $command->setContainer($this->kernel->getContainer()); } return $command; } - /** - * {@inheritdoc} - */ - public function all(string $namespace = null) + public function all(string $namespace = null): array { $this->registerCommands(); return parent::all($namespace); } - /** - * {@inheritdoc} - */ - public function getLongVersion() + public function getLongVersion(): string { return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } - public function add(Command $command) + public function add(Command $command): ?Command { $this->registerCommands(); return parent::add($command); } + /** + * @return void + */ protected function registerCommands() { if ($this->commandsRegistered) { @@ -197,7 +219,7 @@ class Application extends BaseApplication } } - private function renderRegistrationErrors(InputInterface $input, OutputInterface $output) + private function renderRegistrationErrors(InputInterface $input, OutputInterface $output): void { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); diff --git a/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php index 537d6d08c..ba500adb2 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php @@ -15,9 +15,12 @@ use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Routing\Route; @@ -30,60 +33,40 @@ use Symfony\Component\Routing\RouteCollection; */ abstract class Descriptor implements DescriptorInterface { - /** - * @var OutputInterface - */ - protected $output; + protected OutputInterface $output; - /** - * {@inheritdoc} - */ - public function describe(OutputInterface $output, $object, array $options = []) + public function describe(OutputInterface $output, mixed $object, array $options = []): void { $this->output = $output; - switch (true) { - case $object instanceof RouteCollection: - $this->describeRouteCollection($object, $options); - break; - case $object instanceof Route: - $this->describeRoute($object, $options); - break; - case $object instanceof ParameterBag: - $this->describeContainerParameters($object, $options); - break; - case $object instanceof ContainerBuilder && !empty($options['env-vars']): - $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options); - break; - case $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by']: - $this->describeContainerTags($object, $options); - break; - case $object instanceof ContainerBuilder && isset($options['id']): - $this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options, $object); - break; - case $object instanceof ContainerBuilder && isset($options['parameter']): - $this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $options); - break; - case $object instanceof ContainerBuilder && isset($options['deprecations']): - $this->describeContainerDeprecations($object, $options); - break; - case $object instanceof ContainerBuilder: - $this->describeContainerServices($object, $options); - break; - case $object instanceof Definition: - $this->describeContainerDefinition($object, $options); - break; - case $object instanceof Alias: - $this->describeContainerAlias($object, $options); - break; - case $object instanceof EventDispatcherInterface: - $this->describeEventDispatcherListeners($object, $options); - break; - case \is_callable($object): - $this->describeCallable($object, $options); - break; - default: - throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); + if ($object instanceof ContainerBuilder) { + (new AnalyzeServiceReferencesPass(false, false))->process($object); + } + + $deprecatedParameters = []; + if ($object instanceof ContainerBuilder && isset($options['parameter']) && ($parameterBag = $object->getParameterBag()) instanceof ParameterBag) { + $deprecatedParameters = $parameterBag->allDeprecated(); + } + + match (true) { + $object instanceof RouteCollection => $this->describeRouteCollection($object, $options), + $object instanceof Route => $this->describeRoute($object, $options), + $object instanceof ParameterBag => $this->describeContainerParameters($object, $options), + $object instanceof ContainerBuilder && !empty($options['env-vars']) => $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options), + $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by'] => $this->describeContainerTags($object, $options), + $object instanceof ContainerBuilder && isset($options['id']) => $this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options, $object), + $object instanceof ContainerBuilder && isset($options['parameter']) => $this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $deprecatedParameters[$options['parameter']] ?? null, $options), + $object instanceof ContainerBuilder && isset($options['deprecations']) => $this->describeContainerDeprecations($object, $options), + $object instanceof ContainerBuilder => $this->describeContainerServices($object, $options), + $object instanceof Definition => $this->describeContainerDefinition($object, $options), + $object instanceof Alias => $this->describeContainerAlias($object, $options), + $object instanceof EventDispatcherInterface => $this->describeEventDispatcherListeners($object, $options), + \is_callable($object) => $this->describeCallable($object, $options), + default => throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))), + }; + + if ($object instanceof ContainerBuilder) { + $object->getCompiler()->getServiceReferenceGraph()->clear(); } } @@ -92,18 +75,18 @@ abstract class Descriptor implements DescriptorInterface return $this->output; } - protected function write(string $content, bool $decorated = false) + protected function write(string $content, bool $decorated = false): void { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } - abstract protected function describeRouteCollection(RouteCollection $routes, array $options = []); + abstract protected function describeRouteCollection(RouteCollection $routes, array $options = []): void; - abstract protected function describeRoute(Route $route, array $options = []); + abstract protected function describeRoute(Route $route, array $options = []): void; - abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = []); + abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void; - abstract protected function describeContainerTags(ContainerBuilder $builder, array $options = []); + abstract protected function describeContainerTags(ContainerBuilder $container, array $options = []): void; /** * Describes a container service by its name. @@ -113,7 +96,7 @@ abstract class Descriptor implements DescriptorInterface * * @param Definition|Alias|object $service */ - abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null); + abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void; /** * Describes container services. @@ -121,17 +104,17 @@ abstract class Descriptor implements DescriptorInterface * Common options are: * * tag: filters described services by given tag */ - abstract protected function describeContainerServices(ContainerBuilder $builder, array $options = []); + abstract protected function describeContainerServices(ContainerBuilder $container, array $options = []): void; - abstract protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void; + abstract protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void; - abstract protected function describeContainerDefinition(Definition $definition, array $options = []); + abstract protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void; - abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null); + abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void; - abstract protected function describeContainerParameter($parameter, array $options = []); + abstract protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void; - abstract protected function describeContainerEnvVars(array $envs, array $options = []); + abstract protected function describeContainerEnvVars(array $envs, array $options = []): void; /** * Describes event dispatcher listeners. @@ -139,28 +122,18 @@ abstract class Descriptor implements DescriptorInterface * Common options are: * * name: name of listened event */ - abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []); + abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void; - /** - * Describes a callable. - * - * @param mixed $callable - */ - abstract protected function describeCallable($callable, array $options = []); + abstract protected function describeCallable(mixed $callable, array $options = []): void; - /** - * Formats a value as string. - * - * @param mixed $value - */ - protected function formatValue($value): string + protected function formatValue(mixed $value): string { if ($value instanceof \UnitEnum) { return ltrim(var_export($value, true), '\\'); } if (\is_object($value)) { - return sprintf('object(%s)', \get_class($value)); + return sprintf('object(%s)', $value::class); } if (\is_string($value)) { @@ -170,12 +143,7 @@ abstract class Descriptor implements DescriptorInterface return preg_replace("/\n\s*/s", '', var_export($value, true)); } - /** - * Formats a parameter. - * - * @param mixed $value - */ - protected function formatParameter($value): string + protected function formatParameter(mixed $value): string { if ($value instanceof \UnitEnum) { return ltrim(var_export($value, true), '\\'); @@ -204,18 +172,15 @@ abstract class Descriptor implements DescriptorInterface return (string) $value; } - /** - * @return mixed - */ - protected function resolveServiceDefinition(ContainerBuilder $builder, string $serviceId) + protected function resolveServiceDefinition(ContainerBuilder $container, string $serviceId): mixed { - if ($builder->hasDefinition($serviceId)) { - return $builder->getDefinition($serviceId); + if ($container->hasDefinition($serviceId)) { + return $container->getDefinition($serviceId); } // Some service IDs don't have a Definition, they're aliases - if ($builder->hasAlias($serviceId)) { - return $builder->getAlias($serviceId); + if ($container->hasAlias($serviceId)) { + return $container->getAlias($serviceId); } if ('service_container' === $serviceId) { @@ -223,18 +188,18 @@ abstract class Descriptor implements DescriptorInterface } // the service has been injected in some special way, just return the service - return $builder->get($serviceId); + return $container->get($serviceId); } - protected function findDefinitionsByTag(ContainerBuilder $builder, bool $showHidden): array + protected function findDefinitionsByTag(ContainerBuilder $container, bool $showHidden): array { $definitions = []; - $tags = $builder->findTags(); + $tags = $container->findTags(); asort($tags); foreach ($tags as $tag) { - foreach ($builder->findTaggedServiceIds($tag) as $serviceId => $attributes) { - $definition = $this->resolveServiceDefinition($builder, $serviceId); + foreach ($container->findTaggedServiceIds($tag) as $serviceId => $attributes) { + $definition = $this->resolveServiceDefinition($container, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; @@ -251,7 +216,7 @@ abstract class Descriptor implements DescriptorInterface return $definitions; } - protected function sortParameters(ParameterBag $parameters) + protected function sortParameters(ParameterBag $parameters): array { $parameters = $parameters->all(); ksort($parameters); @@ -259,7 +224,7 @@ abstract class Descriptor implements DescriptorInterface return $parameters; } - protected function sortServiceIds(array $serviceIds) + protected function sortServiceIds(array $serviceIds): array { asort($serviceIds); @@ -278,9 +243,7 @@ abstract class Descriptor implements DescriptorInterface } } } - uasort($maxPriority, function ($a, $b) { - return $b <=> $a; - }); + uasort($maxPriority, fn ($a, $b) => $b <=> $a); return array_keys($maxPriority); } @@ -297,13 +260,24 @@ abstract class Descriptor implements DescriptorInterface protected function sortByPriority(array $tag): array { - usort($tag, function ($a, $b) { - return ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0); - }); + usort($tag, fn ($a, $b) => ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0)); return $tag; } + /** + * @return array + */ + protected function getReverseAliases(RouteCollection $routes): array + { + $reverseAliases = []; + foreach ($routes->getAliases() as $name => $alias) { + $reverseAliases[$alias->getId()][] = $name; + } + + return $reverseAliases; + } + public static function getClassDescription(string $class, string &$resolvedClass = null): string { $resolvedClass = $class; @@ -321,7 +295,7 @@ abstract class Descriptor implements DescriptorInterface return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment)); } - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { } return ''; @@ -333,7 +307,7 @@ abstract class Descriptor implements DescriptorInterface return []; } - if (!is_file($container->getParameter('debug.container.dump'))) { + if (!$container->getParameter('debug.container.dump') || !is_file($container->getParameter('debug.container.dump'))) { return []; } @@ -342,13 +316,10 @@ abstract class Descriptor implements DescriptorInterface $envVars = array_unique($envVars[1]); $bag = $container->getParameterBag(); - $getDefaultParameter = function (string $name) { - return parent::get($name); - }; - $getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag)); + $getDefaultParameter = fn (string $name) => parent::get($name); + $getDefaultParameter = $getDefaultParameter->bindTo($bag, $bag::class); $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); - $getEnvReflection->setAccessible(true); $envs = []; @@ -377,4 +348,16 @@ abstract class Descriptor implements DescriptorInterface return array_values($envs); } + + protected function getServiceEdges(ContainerBuilder $container, string $serviceId): array + { + try { + return array_values(array_unique(array_map( + fn (ServiceReferenceGraphEdge $edge) => $edge->getSourceNode()->getId(), + $container->getCompiler()->getServiceReferenceGraph()->getNode($serviceId)->getInEdges() + ))); + } catch (InvalidArgumentException $exception) { + return []; + } + } } diff --git a/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php index 0ad063343..1dc567a3f 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php @@ -32,61 +32,64 @@ use Symfony\Component\Routing\RouteCollection; */ class JsonDescriptor extends Descriptor { - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { $data = []; foreach ($routes->all() as $name => $route) { $data[$name] = $this->getRouteData($route); - } - - $this->writeData($data, $options); - } - - protected function describeRoute(Route $route, array $options = []) - { - $this->writeData($this->getRouteData($route), $options); - } - - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) - { - $this->writeData($this->sortParameters($parameters), $options); - } - - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) - { - $showHidden = isset($options['show_hidden']) && $options['show_hidden']; - $data = []; - - foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { - $data[$tag] = []; - foreach ($definitions as $definition) { - $data[$tag][] = $this->getContainerDefinitionData($definition, true); + if (($showAliases ??= $options['show_aliases'] ?? false) && $aliases = ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []) { + $data[$name]['aliases'] = $aliases; } } $this->writeData($data, $options); } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeRoute(Route $route, array $options = []): void + { + $this->writeData($this->getRouteData($route), $options); + } + + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void + { + $this->writeData($this->sortParameters($parameters), $options); + } + + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void + { + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + $data = []; + + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { + $data[$tag] = []; + foreach ($definitions as $definition) { + $data[$tag][] = $this->getContainerDefinitionData($definition, true, false, $container, $options['id'] ?? null); + } + } + + $this->writeData($data, $options); + } + + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); } if ($service instanceof Alias) { - $this->describeContainerAlias($service, $options, $builder); + $this->describeContainerAlias($service, $options, $container); } elseif ($service instanceof Definition) { - $this->writeData($this->getContainerDefinitionData($service, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options); + $this->writeData($this->getContainerDefinitionData($service, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container, $options['id']), $options); } else { - $this->writeData(\get_class($service), $options); + $this->writeData($service::class, $options); } } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void { $serviceIds = isset($options['tag']) && $options['tag'] - ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) - : $this->sortServiceIds($builder->getServiceIds()); + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($container->getServiceIds()); $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $omitTags = isset($options['omit_tags']) && $options['omit_tags']; $showArguments = isset($options['show_arguments']) && $options['show_arguments']; @@ -97,7 +100,7 @@ class JsonDescriptor extends Descriptor } foreach ($serviceIds as $serviceId) { - $service = $this->resolveServiceDefinition($builder, $serviceId); + $service = $this->resolveServiceDefinition($container, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; @@ -106,59 +109,67 @@ class JsonDescriptor extends Descriptor if ($service instanceof Alias) { $data['aliases'][$serviceId] = $this->getContainerAliasData($service); } elseif ($service instanceof Definition) { - $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments); + if ($service->hasTag('container.excluded')) { + continue; + } + $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments, $container, $serviceId); } else { - $data['services'][$serviceId] = \get_class($service); + $data['services'][$serviceId] = $service::class; } } $this->writeData($data, $options); } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void { - $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options); + $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container, $options['id'] ?? null), $options); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void { - if (!$builder) { + if (!$container) { $this->writeData($this->getContainerAliasData($alias), $options); return; } $this->writeData( - [$this->getContainerAliasData($alias), $this->getContainerDefinitionData($builder->getDefinition((string) $alias), isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])], + [$this->getContainerAliasData($alias), $this->getContainerDefinitionData($container->getDefinition((string) $alias), isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container, (string) $alias)], array_merge($options, ['id' => (string) $alias]) ); } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options); } - protected function describeCallable($callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $this->writeData($this->getCallableData($callable), $options); } - protected function describeContainerParameter($parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void { $key = $options['parameter'] ?? ''; + $data = [$key => $parameter]; - $this->writeData([$key => $parameter], $options); + if ($deprecation) { + $data['_deprecation'] = sprintf('Since %s %s: %s', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))); + } + + $this->writeData($data, $options); } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { throw new LogicException('Using the JSON format to debug environment variables is not supported.'); } - protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void { - $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); if (!file_exists($containerDeprecationFilePath)) { throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); } @@ -180,7 +191,7 @@ class JsonDescriptor extends Descriptor $this->writeData(['remainingCount' => $remainingCount, 'deprecations' => $formattedLogs], $options); } - private function writeData(array $data, array $options) + private function writeData(array $data, array $options): void { $flags = $options['json_encoding'] ?? 0; @@ -204,7 +215,7 @@ class JsonDescriptor extends Descriptor 'hostRegex' => '' !== $route->getHost() ? $route->compile()->getHostRegex() : '', 'scheme' => $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY', 'method' => $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY', - 'class' => \get_class($route), + 'class' => $route::class, 'defaults' => $route->getDefaults(), 'requirements' => $route->getRequirements() ?: 'NO CUSTOM', 'options' => $route->getOptions(), @@ -217,7 +228,24 @@ class JsonDescriptor extends Descriptor return $data; } - private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false): array + protected function sortParameters(ParameterBag $parameters): array + { + $sortedParameters = parent::sortParameters($parameters); + + if ($deprecated = $parameters->allDeprecated()) { + $deprecations = []; + + foreach ($deprecated as $parameter => $deprecation) { + $deprecations[$parameter] = sprintf('Since %s %s: %s', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))); + } + + $sortedParameters['_deprecations'] = $deprecations; + } + + return $sortedParameters; + } + + private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false, ContainerBuilder $container = null, string $id = null): array { $data = [ 'class' => (string) $definition->getClass(), @@ -230,12 +258,19 @@ class JsonDescriptor extends Descriptor 'autoconfigure' => $definition->isAutoconfigured(), ]; + if ($definition->isDeprecated()) { + $data['deprecated'] = true; + $data['deprecation_message'] = $definition->getDeprecation($id)['message']; + } else { + $data['deprecated'] = false; + } + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { $data['description'] = $classDescription; } if ($showArguments) { - $data['arguments'] = $this->describeValue($definition->getArguments(), $omitTags, $showArguments); + $data['arguments'] = $this->describeValue($definition->getArguments(), $omitTags, $showArguments, $container, $id); } $data['file'] = $definition->getFile(); @@ -272,6 +307,8 @@ class JsonDescriptor extends Descriptor } } + $data['usages'] = null !== $container && null !== $id ? $this->getServiceEdges($container, $id) : []; + return $data; } @@ -295,7 +332,7 @@ class JsonDescriptor extends Descriptor $data[] = $l; } } else { - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { @@ -310,7 +347,7 @@ class JsonDescriptor extends Descriptor return $data; } - private function getCallableData($callable): array + private function getCallableData(mixed $callable): array { $data = []; @@ -319,7 +356,7 @@ class JsonDescriptor extends Descriptor if (\is_object($callable[0])) { $data['name'] = $callable[1]; - $data['class'] = \get_class($callable[0]); + $data['class'] = $callable[0]::class; } else { if (!str_starts_with($callable[1], 'parent::')) { $data['name'] = $callable[1]; @@ -373,7 +410,7 @@ class JsonDescriptor extends Descriptor if (method_exists($callable, '__invoke')) { $data['type'] = 'object'; - $data['name'] = \get_class($callable); + $data['name'] = $callable::class; return $data; } @@ -381,12 +418,12 @@ class JsonDescriptor extends Descriptor throw new \InvalidArgumentException('Callable is not describable.'); } - private function describeValue($value, bool $omitTags, bool $showArguments) + private function describeValue($value, bool $omitTags, bool $showArguments, ContainerBuilder $container = null, string $id = null): mixed { if (\is_array($value)) { $data = []; foreach ($value as $k => $v) { - $data[$k] = $this->describeValue($v, $omitTags, $showArguments); + $data[$k] = $this->describeValue($v, $omitTags, $showArguments, $container, $id); } return $data; @@ -408,11 +445,11 @@ class JsonDescriptor extends Descriptor } if ($value instanceof ArgumentInterface) { - return $this->describeValue($value->getValues(), $omitTags, $showArguments); + return $this->describeValue($value->getValues(), $omitTags, $showArguments, $container, $id); } if ($value instanceof Definition) { - return $this->getContainerDefinitionData($value, $omitTags, $showArguments); + return $this->getContainerDefinitionData($value, $omitTags, $showArguments, $container, $id); } return $value; diff --git a/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php index a3fbabc6d..275294d7e 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php @@ -29,7 +29,7 @@ use Symfony\Component\Routing\RouteCollection; */ class MarkdownDescriptor extends Descriptor { - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { $first = true; foreach ($routes->all() as $name => $route) { @@ -39,11 +39,14 @@ class MarkdownDescriptor extends Descriptor $this->write("\n\n"); } $this->describeRoute($route, ['name' => $name]); + if (($showAliases ??= $options['show_aliases'] ?? false) && $aliases = ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []) { + $this->write(sprintf("- Aliases: \n%s", implode("\n", array_map(static fn (string $alias): string => sprintf(' - %s', $alias), $aliases)))); + } } $this->write("\n"); } - protected function describeRoute(Route $route, array $options = []) + protected function describeRoute(Route $route, array $options = []): void { $output = '- Path: '.$route->getPath() ."\n".'- Path Regex: '.$route->compile()->getRegex() @@ -51,7 +54,7 @@ class MarkdownDescriptor extends Descriptor ."\n".'- Host Regex: '.('' !== $route->getHost() ? $route->compile()->getHostRegex() : '') ."\n".'- Scheme: '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY') ."\n".'- Method: '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY') - ."\n".'- Class: '.\get_class($route) + ."\n".'- Class: '.$route::class ."\n".'- Defaults: '.$this->formatRouterConfig($route->getDefaults()) ."\n".'- Requirements: '.($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM') ."\n".'- Options: '.$this->formatRouterConfig($route->getOptions()); @@ -66,29 +69,36 @@ class MarkdownDescriptor extends Descriptor $this->write("\n"); } - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void { + $deprecatedParameters = $parameters->allDeprecated(); + $this->write("Container parameters\n====================\n"); foreach ($this->sortParameters($parameters) as $key => $value) { - $this->write(sprintf("\n- `%s`: `%s`", $key, $this->formatParameter($value))); + $this->write(sprintf( + "\n- `%s`: `%s`%s", + $key, + $this->formatParameter($value), + isset($deprecatedParameters[$key]) ? sprintf(' *Since %s %s: %s*', $deprecatedParameters[$key][0], $deprecatedParameters[$key][1], sprintf(...\array_slice($deprecatedParameters[$key], 2))) : '' + )); } } - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $this->write("Container tags\n=============="); - foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { $this->write("\n\n".$tag."\n".str_repeat('-', \strlen($tag))); foreach ($definitions as $serviceId => $definition) { $this->write("\n\n"); - $this->describeContainerDefinition($definition, ['omit_tags' => true, 'id' => $serviceId]); + $this->describeContainerDefinition($definition, ['omit_tags' => true, 'id' => $serviceId], $container); } } } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -97,17 +107,17 @@ class MarkdownDescriptor extends Descriptor $childOptions = array_merge($options, ['id' => $options['id'], 'as_array' => true]); if ($service instanceof Alias) { - $this->describeContainerAlias($service, $childOptions, $builder); + $this->describeContainerAlias($service, $childOptions, $container); } elseif ($service instanceof Definition) { - $this->describeContainerDefinition($service, $childOptions); + $this->describeContainerDefinition($service, $childOptions, $container); } else { - $this->write(sprintf('**`%s`:** `%s`', $options['id'], \get_class($service))); + $this->write(sprintf('**`%s`:** `%s`', $options['id'], $service::class)); } } - protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void { - $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); if (!file_exists($containerDeprecationFilePath)) { throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); } @@ -132,7 +142,7 @@ class MarkdownDescriptor extends Descriptor } } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -143,8 +153,8 @@ class MarkdownDescriptor extends Descriptor $this->write($title."\n".str_repeat('=', \strlen($title))); $serviceIds = isset($options['tag']) && $options['tag'] - ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) - : $this->sortServiceIds($builder->getServiceIds()); + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($container->getServiceIds()); $showArguments = isset($options['show_arguments']) && $options['show_arguments']; $services = ['definitions' => [], 'aliases' => [], 'services' => []]; @@ -153,7 +163,7 @@ class MarkdownDescriptor extends Descriptor } foreach ($serviceIds as $serviceId) { - $service = $this->resolveServiceDefinition($builder, $serviceId); + $service = $this->resolveServiceDefinition($container, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; @@ -162,6 +172,9 @@ class MarkdownDescriptor extends Descriptor if ($service instanceof Alias) { $services['aliases'][$serviceId] = $service; } elseif ($service instanceof Definition) { + if ($service->hasTag('container.excluded')) { + continue; + } $services['definitions'][$serviceId] = $service; } else { $services['services'][$serviceId] = $service; @@ -172,7 +185,7 @@ class MarkdownDescriptor extends Descriptor $this->write("\n\nDefinitions\n-----------\n"); foreach ($services['definitions'] as $id => $service) { $this->write("\n"); - $this->describeContainerDefinition($service, ['id' => $id, 'show_arguments' => $showArguments]); + $this->describeContainerDefinition($service, ['id' => $id, 'show_arguments' => $showArguments], $container); } } @@ -188,12 +201,12 @@ class MarkdownDescriptor extends Descriptor $this->write("\n\nServices\n--------\n"); foreach ($services['services'] as $id => $service) { $this->write("\n"); - $this->write(sprintf('- `%s`: `%s`', $id, \get_class($service))); + $this->write(sprintf('- `%s`: `%s`', $id, $service::class)); } } } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void { $output = ''; @@ -211,6 +224,13 @@ class MarkdownDescriptor extends Descriptor ."\n".'- Autoconfigured: '.($definition->isAutoconfigured() ? 'yes' : 'no') ; + if ($definition->isDeprecated()) { + $output .= "\n".'- Deprecated: yes'; + $output .= "\n".'- Deprecation message: '.$definition->getDeprecation($options['id'])['message']; + } else { + $output .= "\n".'- Deprecated: no'; + } + if (isset($options['show_arguments']) && $options['show_arguments']) { $output .= "\n".'- Arguments: '.($definition->getArguments() ? 'yes' : 'no'); } @@ -244,16 +264,19 @@ class MarkdownDescriptor extends Descriptor foreach ($tagData as $parameters) { $output .= "\n".'- Tag: `'.$tagName.'`'; foreach ($parameters as $name => $value) { - $output .= "\n".' - '.ucfirst($name).': '.$value; + $output .= "\n".' - '.ucfirst($name).': '.(\is_array($value) ? $this->formatParameter($value) : $value); } } } } + $inEdges = null !== $container && isset($options['id']) ? $this->getServiceEdges($container, $options['id']) : []; + $output .= "\n".'- Usages: '.($inEdges ? implode(', ', $inEdges) : 'none'); + $this->write(isset($options['id']) ? sprintf("### %s\n\n%s\n", $options['id'], $output) : $output); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void { $output = '- Service: `'.$alias.'`' ."\n".'- Public: '.($alias->isPublic() && !$alias->isPrivate() ? 'yes' : 'no'); @@ -266,25 +289,29 @@ class MarkdownDescriptor extends Descriptor $this->write(sprintf("### %s\n\n%s\n", $options['id'], $output)); - if (!$builder) { + if (!$container) { return; } $this->write("\n"); - $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias])); + $this->describeContainerDefinition($container->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $container); } - protected function describeContainerParameter($parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void { - $this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter); + if (isset($options['parameter'])) { + $this->write(sprintf("%s\n%s\n\n%s%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter), $deprecation ? sprintf("\n\n*Since %s %s: %s*", $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))) : '')); + } else { + $this->write($parameter); + } } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { throw new LogicException('Using the markdown format to debug environment variables is not supported.'); } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $event = $options['event'] ?? null; $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; @@ -300,7 +327,7 @@ class MarkdownDescriptor extends Descriptor $registeredListeners = $eventDispatcher->getListeners($event); } else { // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); } $this->write(sprintf('# %s', $title)."\n"); @@ -326,7 +353,7 @@ class MarkdownDescriptor extends Descriptor } } - protected function describeCallable($callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $string = ''; @@ -335,7 +362,7 @@ class MarkdownDescriptor extends Descriptor if (\is_object($callable[0])) { $string .= "\n".sprintf('- Name: `%s`', $callable[1]); - $string .= "\n".sprintf('- Class: `%s`', \get_class($callable[0])); + $string .= "\n".sprintf('- Class: `%s`', $callable[0]::class); } else { if (!str_starts_with($callable[1], 'parent::')) { $string .= "\n".sprintf('- Name: `%s`', $callable[1]); @@ -349,7 +376,9 @@ class MarkdownDescriptor extends Descriptor } } - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } if (\is_string($callable)) { @@ -365,7 +394,9 @@ class MarkdownDescriptor extends Descriptor $string .= "\n- Static: yes"; } - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } if ($callable instanceof \Closure) { @@ -373,7 +404,9 @@ class MarkdownDescriptor extends Descriptor $r = new \ReflectionFunction($callable); if (str_contains($r->name, '{closure}')) { - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } $string .= "\n".sprintf('- Name: `%s`', $r->name); @@ -384,14 +417,18 @@ class MarkdownDescriptor extends Descriptor } } - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } if (method_exists($callable, '__invoke')) { $string .= "\n- Type: `object`"; - $string .= "\n".sprintf('- Name: `%s`', \get_class($callable)); + $string .= "\n".sprintf('- Name: `%s`', $callable::class); - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } throw new \InvalidArgumentException('Callable is not describable.'); diff --git a/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php index e7eb18762..b0fe7f2ac 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php @@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Dumper; use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; @@ -25,8 +26,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -37,14 +38,14 @@ use Symfony\Component\Routing\RouteCollection; */ class TextDescriptor extends Descriptor { - private $fileLinkFormatter; + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(FileLinkFormatter $fileLinkFormatter = null) { $this->fileLinkFormatter = $fileLinkFormatter; } - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; @@ -53,6 +54,10 @@ class TextDescriptor extends Descriptor $tableHeaders[] = 'Controller'; } + if ($showAliases = $options['show_aliases'] ?? false) { + $tableHeaders[] = 'Aliases'; + } + $tableRows = []; foreach ($routes->all() as $name => $route) { $controller = $route->getDefault('_controller'); @@ -69,6 +74,10 @@ class TextDescriptor extends Descriptor $row[] = $controller ? $this->formatControllerLink($controller, $this->formatCallable($controller), $options['container'] ?? null) : ''; } + if ($showAliases) { + $row[] = implode('|', ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []); + } + $tableRows[] = $row; } @@ -81,7 +90,7 @@ class TextDescriptor extends Descriptor } } - protected function describeRoute(Route $route, array $options = []) + protected function describeRoute(Route $route, array $options = []): void { $defaults = $route->getDefaults(); if (isset($defaults['_controller'])) { @@ -98,7 +107,7 @@ class TextDescriptor extends Descriptor ['Scheme', $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'], ['Method', $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'], ['Requirements', $route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM'], - ['Class', \get_class($route)], + ['Class', $route::class], ['Defaults', $this->formatRouterConfig($defaults)], ['Options', $this->formatRouterConfig($route->getOptions())], ]; @@ -112,20 +121,29 @@ class TextDescriptor extends Descriptor $table->render(); } - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void { $tableHeaders = ['Parameter', 'Value']; + $deprecatedParameters = $parameters->allDeprecated(); + $tableRows = []; foreach ($this->sortParameters($parameters) as $parameter => $value) { $tableRows[] = [$parameter, $this->formatParameter($value)]; + + if (isset($deprecatedParameters[$parameter])) { + $tableRows[] = [new TableCell( + sprintf('(Since %s %s: %s)', $deprecatedParameters[$parameter][0], $deprecatedParameters[$parameter][1], sprintf(...\array_slice($deprecatedParameters[$parameter], 2))), + ['colspan' => 2] + )]; + } } $options['output']->title('Symfony Container Parameters'); $options['output']->table($tableHeaders, $tableRows); } - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -135,34 +153,34 @@ class TextDescriptor extends Descriptor $options['output']->title('Symfony Container Tags'); } - foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { $options['output']->section(sprintf('"%s" tag', $tag)); $options['output']->listing(array_keys($definitions)); } } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); } if ($service instanceof Alias) { - $this->describeContainerAlias($service, $options, $builder); + $this->describeContainerAlias($service, $options, $container); } elseif ($service instanceof Definition) { - $this->describeContainerDefinition($service, $options); + $this->describeContainerDefinition($service, $options, $container); } else { $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); $options['output']->table( ['Service ID', 'Class'], [ - [$options['id'] ?? '-', \get_class($service)], + [$options['id'] ?? '-', $service::class], ] ); } } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $showTag = $options['tag'] ?? null; @@ -180,8 +198,8 @@ class TextDescriptor extends Descriptor $options['output']->title($title); $serviceIds = isset($options['tag']) && $options['tag'] - ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) - : $this->sortServiceIds($builder->getServiceIds()); + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($container->getServiceIds()); $maxTags = []; if (isset($options['filter'])) { @@ -189,7 +207,7 @@ class TextDescriptor extends Descriptor } foreach ($serviceIds as $key => $serviceId) { - $definition = $this->resolveServiceDefinition($builder, $serviceId); + $definition = $this->resolveServiceDefinition($container, $serviceId); // filter out hidden services unless shown explicitly if ($showHidden xor '.' === ($serviceId[0] ?? null)) { @@ -198,6 +216,10 @@ class TextDescriptor extends Descriptor } if ($definition instanceof Definition) { + if ($definition->hasTag('container.excluded')) { + unset($serviceIds[$key]); + continue; + } if ($showTag) { $tags = $definition->getTag($showTag); foreach ($tags as $tag) { @@ -205,6 +227,10 @@ class TextDescriptor extends Descriptor if (!isset($maxTags[$key])) { $maxTags[$key] = \strlen($key); } + if (\is_array($value)) { + $value = $this->formatParameter($value); + } + if (\strlen($value) > $maxTags[$key]) { $maxTags[$key] = \strlen($value); } @@ -221,7 +247,7 @@ class TextDescriptor extends Descriptor $tableRows = []; $rawOutput = isset($options['raw_text']) && $options['raw_text']; foreach ($serviceIds as $serviceId) { - $definition = $this->resolveServiceDefinition($builder, $serviceId); + $definition = $this->resolveServiceDefinition($container, $serviceId); $styledServiceId = $rawOutput ? $serviceId : sprintf('%s', OutputFormatter::escape($serviceId)); if ($definition instanceof Definition) { @@ -229,7 +255,11 @@ class TextDescriptor extends Descriptor foreach ($this->sortByPriority($definition->getTag($showTag)) as $key => $tag) { $tagValues = []; foreach ($tagsNames as $tagName) { - $tagValues[] = $tag[$tagName] ?? ''; + if (\is_array($tagValue = $tag[$tagName] ?? '')) { + $tagValue = $this->formatParameter($tagValue); + } + + $tagValues[] = $tagValue; } if (0 === $key) { $tableRows[] = array_merge([$serviceId], $tagValues, [$definition->getClass()]); @@ -244,14 +274,14 @@ class TextDescriptor extends Descriptor $alias = $definition; $tableRows[] = array_merge([$styledServiceId, sprintf('alias for "%s"', $alias)], $tagsCount ? array_fill(0, $tagsCount, '') : []); } else { - $tableRows[] = array_merge([$styledServiceId, \get_class($definition)], $tagsCount ? array_fill(0, $tagsCount, '') : []); + $tableRows[] = array_merge([$styledServiceId, $definition::class], $tagsCount ? array_fill(0, $tagsCount, '') : []); } } $options['output']->table($tableHeaders, $tableRows); } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void { if (isset($options['id'])) { $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); @@ -271,9 +301,7 @@ class TextDescriptor extends Descriptor $tagInformation = []; foreach ($tags as $tagName => $tagData) { foreach ($tagData as $tagParameters) { - $parameters = array_map(function ($key, $value) { - return sprintf('%s: %s', $key, $value); - }, array_keys($tagParameters), array_values($tagParameters)); + $parameters = array_map(fn ($key, $value) => sprintf('%s: %s', $key, \is_array($value) ? $this->formatParameter($value) : $value), array_keys($tagParameters), array_values($tagParameters)); $parameters = implode(', ', $parameters); if ('' === $parameters) { @@ -360,12 +388,15 @@ class TextDescriptor extends Descriptor $tableRows[] = ['Arguments', implode("\n", $argumentsInformation)]; } + $inEdges = null !== $container && isset($options['id']) ? $this->getServiceEdges($container, $options['id']) : []; + $tableRows[] = ['Usages', $inEdges ? implode(\PHP_EOL, $inEdges) : 'none']; + $options['output']->table($tableHeaders, $tableRows); } - protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void { - $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); if (!file_exists($containerDeprecationFilePath)) { $options['output']->warning('The deprecation file does not exist, please try warming the cache first.'); @@ -389,7 +420,7 @@ class TextDescriptor extends Descriptor $options['output']->listing($formattedLogs); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void { if ($alias->isPublic() && !$alias->isPrivate()) { $options['output']->comment(sprintf('This service is a public alias for the service %s', (string) $alias)); @@ -397,24 +428,31 @@ class TextDescriptor extends Descriptor $options['output']->comment(sprintf('This service is a private alias for the service %s', (string) $alias)); } - if (!$builder) { + if (!$container) { return; } - $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias])); + $this->describeContainerDefinition($container->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $container); } - protected function describeContainerParameter($parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void { - $options['output']->table( - ['Parameter', 'Value'], - [ - [$options['parameter'], $this->formatParameter($parameter), - ], - ]); + $parameterName = $options['parameter']; + $rows = [ + [$parameterName, $this->formatParameter($parameter)], + ]; + + if ($deprecation) { + $rows[] = [new TableCell( + sprintf('(Since %s %s: %s)', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))), + ['colspan' => 2] + )]; + } + + $options['output']->table(['Parameter', 'Value'], $rows); } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { $dump = new Dumper($this->output); $options['output']->title('Symfony Container Environment Variables'); @@ -476,7 +514,7 @@ class TextDescriptor extends Descriptor } } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $event = $options['event'] ?? null; $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; @@ -493,7 +531,7 @@ class TextDescriptor extends Descriptor } else { $title .= ' Grouped by Event'; // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); } $options['output']->title($title); @@ -508,12 +546,12 @@ class TextDescriptor extends Descriptor } } - protected function describeCallable($callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $this->writeText($this->formatCallable($callable), $options); } - private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io) + private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io): void { $tableHeaders = ['Order', 'Callable', 'Priority']; $tableRows = []; @@ -527,7 +565,7 @@ class TextDescriptor extends Descriptor private function formatRouterConfig(array $config): string { - if (empty($config)) { + if (!$config) { return 'NONE'; } @@ -541,7 +579,7 @@ class TextDescriptor extends Descriptor return trim($configAsString); } - private function formatControllerLink($controller, string $anchorText, callable $getContainer = null): string + private function formatControllerLink(mixed $controller, string $anchorText, callable $getContainer = null): string { if (null === $this->fileLinkFormatter) { return $anchorText; @@ -563,7 +601,7 @@ class TextDescriptor extends Descriptor } else { $r = new \ReflectionFunction($controller); } - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { if (\is_array($controller)) { $controller = implode('::', $controller); } @@ -582,7 +620,7 @@ class TextDescriptor extends Descriptor try { $r = new \ReflectionMethod($container->findDefinition($id)->getClass(), $method); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { return $anchorText; } } @@ -595,11 +633,11 @@ class TextDescriptor extends Descriptor return $anchorText; } - private function formatCallable($callable): string + private function formatCallable(mixed $callable): string { if (\is_array($callable)) { if (\is_object($callable[0])) { - return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]); + return sprintf('%s::%s()', $callable[0]::class, $callable[1]); } return sprintf('%s::%s()', $callable[0], $callable[1]); @@ -622,13 +660,13 @@ class TextDescriptor extends Descriptor } if (method_exists($callable, '__invoke')) { - return sprintf('%s::__invoke()', \get_class($callable)); + return sprintf('%s::__invoke()', $callable::class); } throw new \InvalidArgumentException('Callable is not describable.'); } - private function writeText(string $content, array $options = []) + private function writeText(string $content, array $options = []): void { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, diff --git a/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php index 350452f33..f12e4583b 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php @@ -33,84 +33,84 @@ use Symfony\Component\Routing\RouteCollection; */ class XmlDescriptor extends Descriptor { - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { - $this->writeDocument($this->getRouteCollectionDocument($routes)); + $this->writeDocument($this->getRouteCollectionDocument($routes, $options)); } - protected function describeRoute(Route $route, array $options = []) + protected function describeRoute(Route $route, array $options = []): void { $this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null)); } - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void { $this->writeDocument($this->getContainerParametersDocument($parameters)); } - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void { - $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_hidden']) && $options['show_hidden'])); + $this->writeDocument($this->getContainerTagsDocument($container, isset($options['show_hidden']) && $options['show_hidden'])); } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); } - $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments'])); + $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $container, isset($options['show_arguments']) && $options['show_arguments'])); } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void { - $this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null)); + $this->writeDocument($this->getContainerServicesDocument($container, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null, $options['id'] ?? null)); } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void { - $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])); + $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container)); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), true)); - if (!$builder) { + if (!$container) { $this->writeDocument($dom); return; } - $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $alias), (string) $alias)->childNodes->item(0), true)); + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($container->getDefinition((string) $alias), (string) $alias, false, false, $container)->childNodes->item(0), true)); $this->writeDocument($dom); } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options)); } - protected function describeCallable($callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $this->writeDocument($this->getCallableDocument($callable)); } - protected function describeContainerParameter($parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void { - $this->writeDocument($this->getContainerParameterDocument($parameter, $options)); + $this->writeDocument($this->getContainerParameterDocument($parameter, $deprecation, $options)); } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { throw new LogicException('Using the XML format to debug environment variables is not supported.'); } - protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void { - $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); if (!file_exists($containerDeprecationFilePath)) { throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); } @@ -120,7 +120,6 @@ class XmlDescriptor extends Descriptor $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($deprecationsXML = $dom->createElement('deprecations')); - $formattedLogs = []; $remainingCount = 0; foreach ($logs as $log) { $deprecationsXML->appendChild($deprecationXML = $dom->createElement('deprecation')); @@ -136,19 +135,27 @@ class XmlDescriptor extends Descriptor $this->writeDocument($dom); } - private function writeDocument(\DOMDocument $dom) + private function writeDocument(\DOMDocument $dom): void { $dom->formatOutput = true; $this->write($dom->saveXML()); } - private function getRouteCollectionDocument(RouteCollection $routes): \DOMDocument + private function getRouteCollectionDocument(RouteCollection $routes, array $options): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($routesXML = $dom->createElement('routes')); foreach ($routes->all() as $name => $route) { $routeXML = $this->getRouteDocument($route, $name); + if (($showAliases ??= $options['show_aliases'] ?? false) && $aliases = ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []) { + $routeXML->firstChild->appendChild($aliasesXML = $routeXML->createElement('aliases')); + foreach ($aliases as $alias) { + $aliasesXML->appendChild($aliasXML = $routeXML->createElement('alias')); + $aliasXML->appendChild(new \DOMText($alias)); + } + } + $routesXML->appendChild($routesXML->ownerDocument->importNode($routeXML->childNodes->item(0), true)); } @@ -164,7 +171,7 @@ class XmlDescriptor extends Descriptor $routeXML->setAttribute('name', $name); } - $routeXML->setAttribute('class', \get_class($route)); + $routeXML->setAttribute('class', $route::class); $routeXML->appendChild($pathXML = $dom->createElement('path')); $pathXML->setAttribute('regex', $route->compile()->getRegex()); @@ -228,26 +235,32 @@ class XmlDescriptor extends Descriptor $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($parametersXML = $dom->createElement('parameters')); + $deprecatedParameters = $parameters->allDeprecated(); + foreach ($this->sortParameters($parameters) as $key => $value) { $parametersXML->appendChild($parameterXML = $dom->createElement('parameter')); $parameterXML->setAttribute('key', $key); $parameterXML->appendChild(new \DOMText($this->formatParameter($value))); + + if (isset($deprecatedParameters[$key])) { + $parameterXML->setAttribute('deprecated', sprintf('Since %s %s: %s', $deprecatedParameters[$key][0], $deprecatedParameters[$key][1], sprintf(...\array_slice($deprecatedParameters[$key], 2)))); + } } return $dom; } - private function getContainerTagsDocument(ContainerBuilder $builder, bool $showHidden = false): \DOMDocument + private function getContainerTagsDocument(ContainerBuilder $container, bool $showHidden = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); - foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { $containerXML->appendChild($tagXML = $dom->createElement('tag')); $tagXML->setAttribute('name', $tag); foreach ($definitions as $serviceId => $definition) { - $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true); + $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true, false, $container); $tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), true)); } } @@ -255,45 +268,49 @@ class XmlDescriptor extends Descriptor return $dom; } - private function getContainerServiceDocument(object $service, string $id, ContainerBuilder $builder = null, bool $showArguments = false): \DOMDocument + private function getContainerServiceDocument(object $service, string $id, ContainerBuilder $container = null, bool $showArguments = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); if ($service instanceof Alias) { $dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), true)); - if ($builder) { - $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $service), (string) $service, false, $showArguments)->childNodes->item(0), true)); + if ($container) { + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($container->getDefinition((string) $service), (string) $service, false, $showArguments, $container)->childNodes->item(0), true)); } } elseif ($service instanceof Definition) { - $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, false, $showArguments)->childNodes->item(0), true)); + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, false, $showArguments, $container)->childNodes->item(0), true)); } else { $dom->appendChild($serviceXML = $dom->createElement('service')); $serviceXML->setAttribute('id', $id); - $serviceXML->setAttribute('class', \get_class($service)); + $serviceXML->setAttribute('class', $service::class); } return $dom; } - private function getContainerServicesDocument(ContainerBuilder $builder, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null): \DOMDocument + private function getContainerServicesDocument(ContainerBuilder $container, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null, string $id = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); $serviceIds = $tag - ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($tag)) - : $this->sortServiceIds($builder->getServiceIds()); + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($tag)) + : $this->sortServiceIds($container->getServiceIds()); if ($filter) { $serviceIds = array_filter($serviceIds, $filter); } foreach ($serviceIds as $serviceId) { - $service = $this->resolveServiceDefinition($builder, $serviceId); + $service = $this->resolveServiceDefinition($container, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; } + if ($service instanceof Definition && $service->hasTag('container.excluded')) { + continue; + } + $serviceXML = $this->getContainerServiceDocument($service, $serviceId, null, $showArguments); $containerXML->appendChild($containerXML->ownerDocument->importNode($serviceXML->childNodes->item(0), true)); } @@ -301,7 +318,7 @@ class XmlDescriptor extends Descriptor return $dom; } - private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false): \DOMDocument + private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false, ContainerBuilder $container = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($serviceXML = $dom->createElement('definition')); @@ -341,6 +358,12 @@ class XmlDescriptor extends Descriptor $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false'); $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false'); $serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false'); + if ($definition->isDeprecated()) { + $serviceXML->setAttribute('deprecated', 'true'); + $serviceXML->setAttribute('deprecation_message', $definition->getDeprecation($id)['message']); + } else { + $serviceXML->setAttribute('deprecated', 'false'); + } $serviceXML->setAttribute('file', $definition->getFile() ?? ''); $calls = $definition->getMethodCalls(); @@ -356,7 +379,7 @@ class XmlDescriptor extends Descriptor } if ($showArguments) { - foreach ($this->getArgumentNodes($definition->getArguments(), $dom) as $node) { + foreach ($this->getArgumentNodes($definition->getArguments(), $dom, $container) as $node) { $serviceXML->appendChild($node); } } @@ -378,13 +401,24 @@ class XmlDescriptor extends Descriptor } } + if (null !== $container && null !== $id) { + $edges = $this->getServiceEdges($container, $id); + if ($edges) { + $serviceXML->appendChild($usagesXML = $dom->createElement('usages')); + foreach ($edges as $edge) { + $usagesXML->appendChild($usageXML = $dom->createElement('usage')); + $usageXML->appendChild(new \DOMText($edge)); + } + } + } + return $dom; } /** * @return \DOMNode[] */ - private function getArgumentNodes(array $arguments, \DOMDocument $dom): array + private function getArgumentNodes(array $arguments, \DOMDocument $dom, ContainerBuilder $container = null): array { $nodes = []; @@ -405,18 +439,18 @@ class XmlDescriptor extends Descriptor } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) { $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator'); - foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) { + foreach ($this->getArgumentNodes($argument->getValues(), $dom, $container) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } } elseif ($argument instanceof Definition) { - $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true)->childNodes->item(0), true)); + $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true, $container)->childNodes->item(0), true)); } elseif ($argument instanceof AbstractArgument) { $argumentXML->setAttribute('type', 'abstract'); $argumentXML->appendChild(new \DOMText($argument->getText())); } elseif (\is_array($argument)) { $argumentXML->setAttribute('type', 'collection'); - foreach ($this->getArgumentNodes($argument, $dom) as $childArgumentXML) { + foreach ($this->getArgumentNodes($argument, $dom, $container) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } } elseif ($argument instanceof \UnitEnum) { @@ -447,13 +481,17 @@ class XmlDescriptor extends Descriptor return $dom; } - private function getContainerParameterDocument($parameter, array $options = []): \DOMDocument + private function getContainerParameterDocument(mixed $parameter, ?array $deprecation, array $options = []): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($parameterXML = $dom->createElement('parameter')); if (isset($options['parameter'])) { $parameterXML->setAttribute('key', $options['parameter']); + + if ($deprecation) { + $parameterXML->setAttribute('deprecated', sprintf('Since %s %s: %s', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2)))); + } } $parameterXML->appendChild(new \DOMText($this->formatParameter($parameter))); @@ -472,7 +510,7 @@ class XmlDescriptor extends Descriptor $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners); } else { // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { @@ -486,7 +524,7 @@ class XmlDescriptor extends Descriptor return $dom; } - private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners) + private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners): void { foreach ($eventListeners as $listener) { $callableXML = $this->getCallableDocument($listener); @@ -496,7 +534,7 @@ class XmlDescriptor extends Descriptor } } - private function getCallableDocument($callable): \DOMDocument + private function getCallableDocument(mixed $callable): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($callableXML = $dom->createElement('callable')); @@ -506,7 +544,7 @@ class XmlDescriptor extends Descriptor if (\is_object($callable[0])) { $callableXML->setAttribute('name', $callable[1]); - $callableXML->setAttribute('class', \get_class($callable[0])); + $callableXML->setAttribute('class', $callable[0]::class); } else { if (!str_starts_with($callable[1], 'parent::')) { $callableXML->setAttribute('name', $callable[1]); @@ -560,7 +598,7 @@ class XmlDescriptor extends Descriptor if (method_exists($callable, '__invoke')) { $callableXML->setAttribute('type', 'object'); - $callableXML->setAttribute('name', \get_class($callable)); + $callableXML->setAttribute('name', $callable::class); return $dom; } diff --git a/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php b/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php index 1f17c9994..47d69fef4 100644 --- a/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php +++ b/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php @@ -16,7 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Descriptor\MarkdownDescriptor; use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor; use Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; /** * @author Jean-François Simon diff --git a/lib/symfony/framework-bundle/Controller/AbstractController.php b/lib/symfony/framework-bundle/Controller/AbstractController.php index f6eff3f61..9bcc34606 100644 --- a/lib/symfony/framework-bundle/Controller/AbstractController.php +++ b/lib/symfony/framework-bundle/Controller/AbstractController.php @@ -11,8 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; -use Doctrine\Persistence\ManagerRegistry; use Psr\Container\ContainerInterface; +use Psr\Link\EvolvableLinkInterface; use Psr\Link\LinkInterface; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; @@ -20,7 +20,6 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; -use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use Symfony\Component\HttpFoundation\JsonResponse; @@ -29,12 +28,10 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; -use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\Messenger\Envelope; -use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -46,6 +43,8 @@ use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Contracts\Service\Attribute\Required; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -61,12 +60,10 @@ abstract class AbstractController implements ServiceSubscriberInterface */ protected $container; - /** - * @required - */ + #[Required] public function setContainer(ContainerInterface $container): ?ContainerInterface { - $previous = $this->container; + $previous = $this->container ?? null; $this->container = $container; return $previous; @@ -74,10 +71,8 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Gets a container parameter by its name. - * - * @return array|bool|float|int|string|\UnitEnum|null */ - protected function getParameter(string $name) + protected function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null { if (!$this->container->has('parameter_bag')) { throw new ServiceNotFoundException('parameter_bag.', null, null, [], sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); @@ -86,52 +81,23 @@ abstract class AbstractController implements ServiceSubscriberInterface return $this->container->get('parameter_bag')->get($name); } - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ 'router' => '?'.RouterInterface::class, 'request_stack' => '?'.RequestStack::class, 'http_kernel' => '?'.HttpKernelInterface::class, 'serializer' => '?'.SerializerInterface::class, - 'session' => '?'.SessionInterface::class, 'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class, 'twig' => '?'.Environment::class, - 'doctrine' => '?'.ManagerRegistry::class, // to be removed in 6.0 'form.factory' => '?'.FormFactoryInterface::class, 'security.token_storage' => '?'.TokenStorageInterface::class, 'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class, 'parameter_bag' => '?'.ContainerBagInterface::class, - 'message_bus' => '?'.MessageBusInterface::class, // to be removed in 6.0 - 'messenger.default_bus' => '?'.MessageBusInterface::class, // to be removed in 6.0 + 'web_link.http_header_serializer' => '?'.HttpHeaderSerializer::class, ]; } - /** - * Returns true if the service id is defined. - * - * @deprecated since Symfony 5.4, use method or constructor injection in your controller instead - */ - protected function has(string $id): bool - { - trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__); - - return $this->container->has($id); - } - - /** - * Gets a container service by its id. - * - * @return object The service - * - * @deprecated since Symfony 5.4, use method or constructor injection in your controller instead - */ - protected function get(string $id): object - { - trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__); - - return $this->container->get($id); - } - /** * Generates a URL from the given parameters. * @@ -158,6 +124,8 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Returns a RedirectResponse to the given URL. + * + * @param int $status The HTTP status code (302 "Found" by default) */ protected function redirect(string $url, int $status = 302): RedirectResponse { @@ -166,6 +134,8 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Returns a RedirectResponse to the given route with the given parameters. + * + * @param int $status The HTTP status code (302 "Found" by default) */ protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse { @@ -174,8 +144,10 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. + * + * @param int $status The HTTP status code (200 "OK" by default) */ - protected function json($data, int $status = 200, array $headers = [], array $context = []): JsonResponse + protected function json(mixed $data, int $status = 200, array $headers = [], array $context = []): JsonResponse { if ($this->container->has('serializer')) { $json = $this->container->get('serializer')->serialize($data, 'json', array_merge([ @@ -190,13 +162,11 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Returns a BinaryFileResponse object with original or customized file name and disposition header. - * - * @param \SplFileInfo|string $file File object or path to file to be sent as response */ - protected function file($file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse + protected function file(\SplFileInfo|string $file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse { $response = new BinaryFileResponse($file); - $response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName); + $response->setContentDisposition($disposition, $fileName ?? $response->getFile()->getFilename()); return $response; } @@ -206,13 +176,19 @@ abstract class AbstractController implements ServiceSubscriberInterface * * @throws \LogicException */ - protected function addFlash(string $type, $message): void + protected function addFlash(string $type, mixed $message): void { try { - $this->container->get('request_stack')->getSession()->getFlashBag()->add($type, $message); + $session = $this->container->get('request_stack')->getSession(); } catch (SessionNotFoundException $e) { throw new \LogicException('You cannot use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".', 0, $e); } + + if (!$session instanceof FlashBagAwareSessionInterface) { + trigger_deprecation('symfony/framework-bundle', '6.2', 'Calling "addFlash()" method when the session does not implement %s is deprecated.', FlashBagAwareSessionInterface::class); + } + + $session->getFlashBag()->add($type, $message); } /** @@ -220,7 +196,7 @@ abstract class AbstractController implements ServiceSubscriberInterface * * @throws \LogicException */ - protected function isGranted($attribute, $subject = null): bool + protected function isGranted(mixed $attribute, mixed $subject = null): bool { if (!$this->container->has('security.authorization_checker')) { throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); @@ -235,11 +211,11 @@ abstract class AbstractController implements ServiceSubscriberInterface * * @throws AccessDeniedException */ - protected function denyAccessUnlessGranted($attribute, $subject = null, string $message = 'Access Denied.'): void + protected function denyAccessUnlessGranted(mixed $attribute, mixed $subject = null, string $message = 'Access Denied.'): void { if (!$this->isGranted($attribute, $subject)) { $exception = $this->createAccessDeniedException($message); - $exception->setAttributes($attribute); + $exception->setAttributes([$attribute]); $exception->setSubject($subject); throw $exception; @@ -248,58 +224,56 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Returns a rendered view. + * + * Forms found in parameters are auto-cast to form views. */ protected function renderView(string $view, array $parameters = []): string { - if (!$this->container->has('twig')) { - throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); - } + return $this->doRenderView($view, null, $parameters, __FUNCTION__); + } - return $this->container->get('twig')->render($view, $parameters); + /** + * Returns a rendered block from a view. + * + * Forms found in parameters are auto-cast to form views. + */ + protected function renderBlockView(string $view, string $block, array $parameters = []): string + { + return $this->doRenderView($view, $block, $parameters, __FUNCTION__); } /** * Renders a view. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + * Forms found in parameters are auto-cast to form views. */ protected function render(string $view, array $parameters = [], Response $response = null): Response { - $content = $this->renderView($view, $parameters); + return $this->doRender($view, null, $parameters, $response, __FUNCTION__); + } - if (null === $response) { - $response = new Response(); - } - - $response->setContent($content); - - return $response; + /** + * Renders a block in a view. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + * Forms found in parameters are auto-cast to form views. + */ + protected function renderBlock(string $view, string $block, array $parameters = [], Response $response = null): Response + { + return $this->doRender($view, $block, $parameters, $response, __FUNCTION__); } /** * Renders a view and sets the appropriate status code when a form is listed in parameters. * * If an invalid form is found in the list of parameters, a 422 status code is returned. + * + * @deprecated since Symfony 6.2, use render() instead */ protected function renderForm(string $view, array $parameters = [], Response $response = null): Response { - if (null === $response) { - $response = new Response(); - } - - foreach ($parameters as $k => $v) { - if ($v instanceof FormView) { - throw new \LogicException(sprintf('Passing a FormView to "%s::renderForm()" is not supported, pass directly the form instead for parameter "%s".', get_debug_type($this), $k)); - } - - if (!$v instanceof FormInterface) { - continue; - } - - $parameters[$k] = $v->createView(); - - if (200 === $response->getStatusCode() && $v->isSubmitted() && !$v->isValid()) { - $response->setStatusCode(422); - } - } + trigger_deprecation('symfony/framework-bundle', '6.2', 'The "%s::renderForm()" method is deprecated, use "render()" instead.', get_debug_type($this)); return $this->render($view, $parameters, $response); } @@ -361,7 +335,7 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Creates and returns a Form instance from the type of the form. */ - protected function createForm(string $type, $data = null, array $options = []): FormInterface + protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface { return $this->container->get('form.factory')->create($type, $data, $options); } @@ -369,39 +343,19 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * Creates and returns a form builder instance. */ - protected function createFormBuilder($data = null, array $options = []): FormBuilderInterface + protected function createFormBuilder(mixed $data = null, array $options = []): FormBuilderInterface { return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options); } - /** - * Shortcut to return the Doctrine Registry service. - * - * @throws \LogicException If DoctrineBundle is not available - * - * @deprecated since Symfony 5.4, inject an instance of ManagerRegistry in your controller instead - */ - protected function getDoctrine(): ManagerRegistry - { - trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of ManagerRegistry in your controller instead.', __METHOD__); - - if (!$this->container->has('doctrine')) { - throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".'); - } - - return $this->container->get('doctrine'); - } - /** * Get a user from the Security Token Storage. * - * @return UserInterface|null - * * @throws \LogicException If SecurityBundle is not available * * @see TokenInterface::getUser() */ - protected function getUser() + protected function getUser(): ?UserInterface { if (!$this->container->has('security.token_storage')) { throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); @@ -411,13 +365,7 @@ abstract class AbstractController implements ServiceSubscriberInterface return null; } - // @deprecated since 5.4, $user will always be a UserInterface instance - if (!\is_object($user = $token->getUser())) { - // e.g. anonymous authentication - return null; - } - - return $user; + return $token->getUser(); } /** @@ -426,7 +374,7 @@ abstract class AbstractController implements ServiceSubscriberInterface * @param string $id The id used when generating the token * @param string|null $token The actual token sent with the request that should be validated */ - protected function isCsrfTokenValid(string $id, ?string $token): bool + protected function isCsrfTokenValid(string $id, #[\SensitiveParameter] ?string $token): bool { if (!$this->container->has('security.csrf.token_manager')) { throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); @@ -435,25 +383,6 @@ abstract class AbstractController implements ServiceSubscriberInterface return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token)); } - /** - * Dispatches a message to the bus. - * - * @param object|Envelope $message The message or the message pre-wrapped in an envelope - * - * @deprecated since Symfony 5.4, inject an instance of MessageBusInterface in your controller instead - */ - protected function dispatchMessage(object $message, array $stamps = []): Envelope - { - trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of MessageBusInterface in your controller instead.', __METHOD__); - - if (!$this->container->has('messenger.default_bus')) { - $message = class_exists(Envelope::class) ? 'You need to define the "messenger.default_bus" configuration option.' : 'Try running "composer require symfony/messenger".'; - throw new \LogicException('The message bus is not enabled in your application. '.$message); - } - - return $this->container->get('messenger.default_bus')->dispatch($message, $stamps); - } - /** * Adds a Link HTTP header to the current response. * @@ -473,4 +402,68 @@ abstract class AbstractController implements ServiceSubscriberInterface $request->attributes->set('_links', $linkProvider->withLink($link)); } + + /** + * @param LinkInterface[] $links + */ + protected function sendEarlyHints(iterable $links = [], Response $response = null): Response + { + if (!$this->container->has('web_link.http_header_serializer')) { + throw new \LogicException('You cannot use the "sendEarlyHints" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); + } + + $response ??= new Response(); + + $populatedLinks = []; + foreach ($links as $link) { + if ($link instanceof EvolvableLinkInterface && !$link->getRels()) { + $link = $link->withRel('preload'); + } + + $populatedLinks[] = $link; + } + + $response->headers->set('Link', $this->container->get('web_link.http_header_serializer')->serialize($populatedLinks), false); + $response->sendHeaders(103); + + return $response; + } + + private function doRenderView(string $view, ?string $block, array $parameters, string $method): string + { + if (!$this->container->has('twig')) { + throw new \LogicException(sprintf('You cannot use the "%s" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".', $method)); + } + + foreach ($parameters as $k => $v) { + if ($v instanceof FormInterface) { + $parameters[$k] = $v->createView(); + } + } + + if (null !== $block) { + return $this->container->get('twig')->load($view)->renderBlock($block, $parameters); + } + + return $this->container->get('twig')->render($view, $parameters); + } + + private function doRender(string $view, ?string $block, array $parameters, ?Response $response, string $method): Response + { + $content = $this->doRenderView($view, $block, $parameters, $method); + $response ??= new Response(); + + if (200 === $response->getStatusCode()) { + foreach ($parameters as $v) { + if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) { + $response->setStatusCode(422); + break; + } + } + } + + $response->setContent($content); + + return $response; + } } diff --git a/lib/symfony/framework-bundle/Controller/ControllerResolver.php b/lib/symfony/framework-bundle/Controller/ControllerResolver.php index 0539c1ee7..3449740bf 100644 --- a/lib/symfony/framework-bundle/Controller/ControllerResolver.php +++ b/lib/symfony/framework-bundle/Controller/ControllerResolver.php @@ -21,14 +21,12 @@ use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; */ class ControllerResolver extends ContainerControllerResolver { - /** - * {@inheritdoc} - */ protected function instantiateController(string $class): object { $controller = parent::instantiateController($class); if ($controller instanceof ContainerAwareInterface) { + trigger_deprecation('symfony/dependency-injection', '6.4', 'Relying on "%s" to get the container in "%s" is deprecated, register the controller as a service and use dependency injection instead.', ContainerAwareInterface::class, get_debug_type($controller)); $controller->setContainer($this->container); } if ($controller instanceof AbstractController) { diff --git a/lib/symfony/framework-bundle/Controller/RedirectController.php b/lib/symfony/framework-bundle/Controller/RedirectController.php index 6a0fed64f..f5f42a7f7 100644 --- a/lib/symfony/framework-bundle/Controller/RedirectController.php +++ b/lib/symfony/framework-bundle/Controller/RedirectController.php @@ -27,9 +27,9 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; */ class RedirectController { - private $router; - private $httpPort; - private $httpsPort; + private ?UrlGeneratorInterface $router; + private ?int $httpPort; + private ?int $httpsPort; public function __construct(UrlGeneratorInterface $router = null, int $httpPort = null, int $httpsPort = null) { @@ -54,7 +54,7 @@ class RedirectController * * @throws HttpException In case the route name is empty */ - public function redirectAction(Request $request, string $route, bool $permanent = false, $ignoreAttributes = false, bool $keepRequestMethod = false, bool $keepQueryParams = false): Response + public function redirectAction(Request $request, string $route, bool $permanent = false, bool|array $ignoreAttributes = false, bool $keepRequestMethod = false, bool $keepQueryParams = false): Response { if ('' == $route) { throw new HttpException($permanent ? 410 : 404); @@ -124,9 +124,7 @@ class RedirectController return new RedirectResponse($path, $statusCode); } - if (null === $scheme) { - $scheme = $request->getScheme(); - } + $scheme ??= $request->getScheme(); if ($qs = $request->server->get('QUERY_STRING') ?: $request->getQueryString()) { if (!str_contains($path, '?')) { diff --git a/lib/symfony/framework-bundle/Controller/TemplateController.php b/lib/symfony/framework-bundle/Controller/TemplateController.php index 2283dbc91..437458a49 100644 --- a/lib/symfony/framework-bundle/Controller/TemplateController.php +++ b/lib/symfony/framework-bundle/Controller/TemplateController.php @@ -23,7 +23,7 @@ use Twig\Environment; */ class TemplateController { - private $twig; + private ?Environment $twig; public function __construct(Environment $twig = null) { @@ -38,12 +38,12 @@ class TemplateController * @param int|null $sharedAge Max age for shared (proxy) caching * @param bool|null $private Whether or not caching should apply for client caches only * @param array $context The context (arguments) of the template - * @param int $statusCode The HTTP status code to return with the response. Defaults to 200 + * @param int $statusCode The HTTP status code to return with the response (200 "OK" by default) */ public function templateAction(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response { if (null === $this->twig) { - throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available.'); + throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); } $response = new Response($this->twig->render($template, $context), $statusCode); @@ -65,6 +65,9 @@ class TemplateController return $response; } + /** + * @param int $statusCode The HTTP status code (200 "OK" by default) + */ public function __invoke(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response { return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode); diff --git a/lib/symfony/framework-bundle/DataCollector/AbstractDataCollector.php b/lib/symfony/framework-bundle/DataCollector/AbstractDataCollector.php index 7fa1ee2d3..f2fa5066d 100644 --- a/lib/symfony/framework-bundle/DataCollector/AbstractDataCollector.php +++ b/lib/symfony/framework-bundle/DataCollector/AbstractDataCollector.php @@ -23,11 +23,6 @@ abstract class AbstractDataCollector extends DataCollector implements TemplateAw return static::class; } - public function reset(): void - { - $this->data = []; - } - public static function getTemplate(): ?string { return null; diff --git a/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php b/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php index c5d0673de..700d0f22d 100644 --- a/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php +++ b/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php @@ -22,7 +22,7 @@ use Symfony\Component\HttpKernel\DataCollector\RouterDataCollector as BaseRouter */ class RouterDataCollector extends BaseRouterDataCollector { - public function guessRoute(Request $request, $controller) + public function guessRoute(Request $request, mixed $controller): string { if (\is_array($controller)) { $controller = $controller[0]; diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php index d7db6ebf0..2105a54df 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php @@ -20,10 +20,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class AddAnnotationsCachedReaderPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { // "annotations.cached_reader" is wired late so that any passes using // "annotation_reader" at build time don't get any cache diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php index 51b297554..fb8629c18 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php @@ -17,6 +17,9 @@ use Symfony\Component\DependencyInjection\Reference; class AddDebugLogProcessorPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('profiler')) { @@ -29,13 +32,19 @@ class AddDebugLogProcessorPass implements CompilerPassInterface return; } - $definition = $container->getDefinition('monolog.logger_prototype'); - $definition->setConfigurator([__CLASS__, 'configureLogger']); - $definition->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]); + $container->getDefinition('monolog.logger_prototype') + ->setConfigurator([new Reference('debug.debug_logger_configurator'), 'pushDebugLogger']); } - public static function configureLogger($logger) + /** + * @deprecated since Symfony 6.4, use HttpKernel's DebugLoggerConfigurator instead + * + * @return void + */ + public static function configureLogger(mixed $logger) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s()" method is deprecated, use HttpKernel\'s DebugLoggerConfigurator instead.', __METHOD__); + if (\is_object($logger) && method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { $logger->removeDebugLogger(); } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index 3e2f2768e..dabf1d6ff 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -15,15 +15,19 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s" class is deprecated, use "%s" instead.', AddExpressionLanguageProvidersPass::class, \Symfony\Component\Routing\DependencyInjection\AddExpressionLanguageProvidersPass::class); + /** * Registers the expression language providers. * * @author Fabien Potencier + * + * @deprecated since Symfony 6.4, use Symfony\Component\Routing\DependencyInjection\AddExpressionLanguageProvidersPass instead. */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php index 3fc79f0ee..e8c2ad3a0 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php @@ -18,6 +18,9 @@ use Symfony\Component\DependencyInjection\Reference; class AssetsContextPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('assets.context')) { diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php index 0df5420c7..1e08ef314 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php @@ -25,8 +25,15 @@ use Symfony\Component\DependencyInjection\Dumper\XmlDumper; */ class ContainerBuilderDebugDumpPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { + if (!$container->getParameter('debug.container.dump')) { + return; + } + $cache = new ConfigCache($container->getParameter('debug.container.dump'), true); if (!$cache->isFresh()) { $cache->write((new XmlDumper($container))->dump(), $container->getResources()); diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php index ee2bbb652..7c6ca5d5a 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php @@ -13,12 +13,20 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Translation\TranslatorBagInterface; + +trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s" class is deprecated, use "%s" instead.', DataCollectorTranslatorPass::class, \Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass::class); /** * @author Christian Flothmann + * + * @deprecated since Symfony 6.4, use Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass instead. */ class DataCollectorTranslatorPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->has('translator')) { @@ -27,7 +35,7 @@ class DataCollectorTranslatorPass implements CompilerPassInterface $translatorClass = $container->getParameterBag()->resolveValue($container->findDefinition('translator')->getClass()); - if (!is_subclass_of($translatorClass, 'Symfony\Component\Translation\TranslatorBagInterface')) { + if (!is_subclass_of($translatorClass, TranslatorBagInterface::class)) { $container->removeDefinition('translator.data_collector'); $container->removeDefinition('data_collector.translation'); } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php new file mode 100644 index 000000000..c1a5e444f --- /dev/null +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s" class is deprecated, use argument $debug of HttpKernel\'s Logger instead.', EnableLoggerDebugModePass::class); + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Log\Logger; + +/** + * @deprecated since Symfony 6.4, use argument $debug of HttpKernel's Logger instead + */ +final class EnableLoggerDebugModePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('profiler') || !$container->hasDefinition('logger')) { + return; + } + + $loggerDefinition = $container->getDefinition('logger'); + + if (Logger::class === $loggerDefinition->getClass()) { + $loggerDefinition->setConfigurator([__CLASS__, 'configureLogger']); + } + } + + public static function configureLogger(Logger $logger): void + { + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + $logger->enableDebug(); + } + } +} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php new file mode 100644 index 000000000..09f2dba82 --- /dev/null +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @internal + */ +class ErrorLoggerCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('debug.error_handler_configurator')) { + return; + } + + $definition = $container->getDefinition('debug.error_handler_configurator'); + if ($container->hasDefinition('monolog.logger.php')) { + $definition->replaceArgument(0, new Reference('monolog.logger.php')); + } + if ($container->hasDefinition('monolog.logger.deprecation')) { + $definition->replaceArgument(5, new Reference('monolog.logger.deprecation')); + } + } +} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php index 80cbe52e6..5b31f2884 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php @@ -17,11 +17,18 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\Translation\TranslatorBagInterface; use Symfony\Contracts\Translation\TranslatorInterface; +trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s" class is deprecated, use "%s" instead.', LoggingTranslatorPass::class, \Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass::class); + /** * @author Abdellatif Ait boudad + * + * @deprecated since Symfony 6.4, use Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass instead. */ class LoggingTranslatorPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasAlias('logger') || !$container->hasAlias('translator')) { diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php index 7200b12b9..8f3f9b220 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php @@ -24,6 +24,9 @@ use Symfony\Component\DependencyInjection\Reference; */ class ProfilerPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (false === $container->hasDefinition('profiler')) { @@ -39,8 +42,7 @@ class ProfilerPass implements CompilerPassInterface $template = null; $collectorClass = $container->findDefinition($id)->getClass(); - $isTemplateAware = is_subclass_of($collectorClass, TemplateAwareDataCollectorInterface::class); - if (isset($attributes[0]['template']) || $isTemplateAware) { + if (isset($attributes[0]['template']) || is_subclass_of($collectorClass, TemplateAwareDataCollectorInterface::class)) { $idForTemplate = $attributes[0]['id'] ?? $collectorClass; if (!$idForTemplate) { throw new InvalidArgumentException(sprintf('Data collector service "%s" must have an id attribute in order to specify a template.', $id)); diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php index 8b6479c4f..fedc30d06 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php @@ -19,6 +19,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('session.marshalling_handler')) { diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php deleted file mode 100644 index 7230fc9fb..000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -/** - * @internal to be removed in 6.0 - */ -class SessionPass implements CompilerPassInterface -{ - public function process(ContainerBuilder $container) - { - if (!$container->has('session.factory')) { - return; - } - - // BC layer: Make "session" an alias of ".session.do-not-use" when not overridden by the user - if (!$container->has('session')) { - $alias = $container->setAlias('session', '.session.do-not-use'); - $alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); - // restore previous behavior - $alias->setPublic(true); - - return; - } - - if ($container->hasDefinition('session')) { - $definition = $container->getDefinition('session'); - $definition->setDeprecated('symfony/framework-bundle', '5.3', 'The "%service_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); - } else { - $alias = $container->getAlias('session'); - $alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" and "SessionInterface" aliases are deprecated, use "$requestStack->getSession()" instead.'); - $definition = $container->findDefinition('session'); - } - - // Convert internal service `.session.do-not-use` into alias of `session`. - $container->setAlias('.session.do-not-use', 'session'); - - $bags = [ - 'session.flash_bag' => $container->hasDefinition('session.flash_bag') ? $container->getDefinition('session.flash_bag') : null, - 'session.attribute_bag' => $container->hasDefinition('session.attribute_bag') ? $container->getDefinition('session.attribute_bag') : null, - ]; - - foreach ($definition->getArguments() as $v) { - if (!$v instanceof Reference || !isset($bags[$bag = (string) $v]) || !\is_array($factory = $bags[$bag]->getFactory())) { - continue; - } - - if ([0, 1] !== array_keys($factory) || !$factory[0] instanceof Reference || !\in_array((string) $factory[0], ['session', '.session.do-not-use'], true)) { - continue; - } - - if ('get'.ucfirst(substr($bag, 8, -4)).'Bag' !== $factory[1]) { - continue; - } - - $bags[$bag]->setFactory(null); - } - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php index 942eb635b..aed3b1340 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -21,6 +21,9 @@ use Symfony\Component\DependencyInjection\Reference; */ class TestServiceContainerRealRefPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('test.private_services_locator')) { @@ -30,10 +33,17 @@ class TestServiceContainerRealRefPass implements CompilerPassInterface $privateContainer = $container->getDefinition('test.private_services_locator'); $definitions = $container->getDefinitions(); $privateServices = $privateContainer->getArgument(0); + $renamedIds = []; foreach ($privateServices as $id => $argument) { if (isset($definitions[$target = (string) $argument->getValues()[0]])) { $argument->setValues([new Reference($target)]); + if ($id !== $target) { + $renamedIds[$id] = $target; + } + if ($inner = $definitions[$target]->getTag('container.decorator')[0]['inner'] ?? null) { + $renamedIds[$id] = $inner; + } } else { unset($privateServices[$id]); } @@ -47,8 +57,14 @@ class TestServiceContainerRealRefPass implements CompilerPassInterface if ($definitions[$target]->hasTag('container.private')) { $privateServices[$id] = new ServiceClosureArgument(new Reference($target)); } + + $renamedIds[$id] = $target; } $privateContainer->replaceArgument(0, $privateServices); + + if ($container->hasDefinition('test.service_container') && $renamedIds) { + $container->getDefinition('test.service_container')->setArgument(2, $renamedIds); + } } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php index bc1e5a936..6e7669a71 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -14,7 +14,6 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** @@ -22,17 +21,9 @@ use Symfony\Component\DependencyInjection\Reference; */ class TestServiceContainerWeakRefPass implements CompilerPassInterface { - private $privateTagName; - - public function __construct(string $privateTagName = 'container.private') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/framework-bundle', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->privateTagName = $privateTagName; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('test.private_services_locator')) { @@ -41,10 +32,9 @@ class TestServiceContainerWeakRefPass implements CompilerPassInterface $privateServices = []; $definitions = $container->getDefinitions(); - $hasErrors = method_exists(Definition::class, 'hasErrors') ? 'hasErrors' : 'getErrors'; foreach ($definitions as $id => $definition) { - if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) && !$definition->$hasErrors() && !$definition->isAbstract()) { + if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->hasErrors() && !$definition->isAbstract()) { $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } @@ -56,7 +46,7 @@ class TestServiceContainerWeakRefPass implements CompilerPassInterface while (isset($aliases[$target = (string) $alias])) { $alias = $aliases[$target]; } - if (isset($definitions[$target]) && !$definitions[$target]->$hasErrors() && !$definitions[$target]->isAbstract()) { + if (isset($definitions[$target]) && !$definitions[$target]->hasErrors() && !$definitions[$target]->isAbstract()) { $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php index 386ee7d0c..5f975f868 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -24,15 +24,18 @@ class UnusedTagsPass implements CompilerPassInterface private const KNOWN_TAGS = [ 'annotations.cached_reader', 'assets.package', + 'asset_mapper.compiler', 'auto_alias', 'cache.pool', 'cache.pool.clearer', + 'cache.taggable', 'chatter.transport_factory', 'config_cache.resource_checker', 'console.command', 'container.do_not_inline', 'container.env_var_loader', 'container.env_var_processor', + 'container.excluded', 'container.hot_path', 'container.no_preload', 'container.preload', @@ -44,11 +47,13 @@ class UnusedTagsPass implements CompilerPassInterface 'container.stack', 'controller.argument_value_resolver', 'controller.service_arguments', + 'controller.targeted_value_resolver', 'data_collector', 'event_dispatcher.dispatcher', 'form.type', 'form.type_extension', 'form.type_guesser', + 'html_sanitizer', 'http_client.client', 'kernel.cache_clearer', 'kernel.cache_warmer', @@ -71,13 +76,16 @@ class UnusedTagsPass implements CompilerPassInterface 'property_info.list_extractor', 'property_info.type_extractor', 'proxy', + 'remote_event.consumer', + 'routing.condition_service', 'routing.expression_language_function', 'routing.expression_language_provider', 'routing.loader', 'routing.route_loader', + 'scheduler.schedule_provider', + 'scheduler.task', 'security.authenticator.login_linker', 'security.expression_language_provider', - 'security.remember_me_aware', 'security.remember_me_handler', 'security.voter', 'serializer.encoder', @@ -85,6 +93,7 @@ class UnusedTagsPass implements CompilerPassInterface 'texter.transport_factory', 'translation.dumper', 'translation.extractor', + 'translation.extractor.visitor', 'translation.loader', 'translation.provider_factory', 'twig.extension', @@ -92,9 +101,14 @@ class UnusedTagsPass implements CompilerPassInterface 'twig.runtime', 'validator.auto_mapper', 'validator.constraint_validator', + 'validator.group_provider', 'validator.initializer', + 'workflow', ]; + /** + * @return void + */ public function process(ContainerBuilder $container) { $tags = array_unique(array_merge($container->findTags(), self::KNOWN_TAGS)); @@ -119,7 +133,7 @@ class UnusedTagsPass implements CompilerPassInterface $services = array_keys($container->findTaggedServiceIds($tag)); $message = sprintf('Tag "%s" was defined on service(s) "%s", but was never used.', $tag, implode('", "', $services)); - if (!empty($candidates)) { + if ($candidates) { $message .= sprintf(' Did you mean "%s"?', implode('", "', $candidates)); } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php index ad62e1938..c07208311 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @@ -15,14 +15,18 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; +trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s" class is deprecated, use "%s" instead.', WorkflowGuardListenerPass::class, \Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass::class); + /** * @author Christian Flothmann * @author GrĂ©goire Pineau + * + * @deprecated since Symfony 6.4, use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass instead. */ class WorkflowGuardListenerPass implements CompilerPassInterface { /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { diff --git a/lib/symfony/framework-bundle/DependencyInjection/Configuration.php b/lib/symfony/framework-bundle/DependencyInjection/Configuration.php index c70a07635..46177a09e 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Configuration.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Configuration.php @@ -12,12 +12,12 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Doctrine\Common\Annotations\Annotation; -use Doctrine\Common\Annotations\PsrCachedReader; -use Doctrine\Common\Cache\Cache; use Doctrine\DBAL\Connection; use Psr\Log\LogLevel; +use Seld\JsonLint\JsonParser; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; +use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; @@ -27,6 +27,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\Form\Form; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\Lock\Lock; @@ -37,10 +38,15 @@ use Symfony\Component\Notifier\Notifier; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Scheduler\Schedule; +use Symfony\Component\Semaphore\Semaphore; +use Symfony\Component\Serializer\Encoder\JsonDecode; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Validator\Validation; +use Symfony\Component\Webhook\Controller\WebhookController; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow\WorkflowEvents; @@ -49,7 +55,7 @@ use Symfony\Component\Workflow\WorkflowEvents; */ class Configuration implements ConfigurationInterface { - private $debug; + private bool $debug; /** * @param bool $debug Whether debugging is enabled or not @@ -61,31 +67,47 @@ class Configuration implements ConfigurationInterface /** * Generates the configuration tree builder. - * - * @return TreeBuilder */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('framework'); $rootNode = $treeBuilder->getRootNode(); $rootNode ->beforeNormalization() - ->ifTrue(function ($v) { return !isset($v['assets']) && isset($v['templating']) && class_exists(Package::class); }) + ->ifTrue(fn ($v) => !isset($v['assets']) && isset($v['templating']) && class_exists(Package::class)) ->then(function ($v) { $v['assets'] = []; return $v; }) ->end() + ->validate() + ->always(function ($v) { + if (!isset($v['http_method_override'])) { + trigger_deprecation('symfony/framework-bundle', '6.1', 'Not setting the "framework.http_method_override" config option is deprecated. It will default to "false" in 7.0.'); + + $v['http_method_override'] = true; + } + if (!isset($v['handle_all_throwables'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting the "framework.handle_all_throwables" config option is deprecated. It will default to "true" in 7.0.'); + } + + return $v; + }) + ->end() ->fixXmlConfig('enabled_locale') ->children() ->scalarNode('secret')->end() - ->scalarNode('http_method_override') + ->booleanNode('http_method_override') ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead") - ->defaultTrue() + ->treatNullLike(false) ->end() - ->scalarNode('ide')->defaultNull()->end() + ->scalarNode('trust_x_sendfile_type_header') + ->info('Set true to enable support for xsendfile in binary file responses.') + ->defaultFalse() + ->end() + ->scalarNode('ide')->defaultValue($this->debug ? '%env(default::SYMFONY_IDE)%' : null)->end() ->booleanNode('test')->end() ->scalarNode('default_locale')->defaultValue('en')->end() ->booleanNode('set_locale_from_accept_language') @@ -101,7 +123,7 @@ class Configuration implements ConfigurationInterface ->prototype('scalar')->end() ->end() ->arrayNode('trusted_hosts') - ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() ->prototype('scalar')->end() ->end() ->scalarNode('trusted_proxies')->end() @@ -109,7 +131,7 @@ class Configuration implements ConfigurationInterface ->fixXmlConfig('trusted_header') ->performNoDeepMerging() ->defaultValue(['x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto']) - ->beforeNormalization()->ifString()->then(function ($v) { return $v ? array_map('trim', explode(',', $v)) : []; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end() ->enumPrototype() ->values([ 'forwarded', @@ -120,6 +142,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('error_controller') ->defaultValue('error_controller') ->end() + ->booleanNode('handle_all_throwables')->info('HttpKernel will handle all kinds of \Throwable')->end() ->end() ; @@ -127,12 +150,10 @@ class Configuration implements ConfigurationInterface $parentPackages = (array) $parentPackage; $parentPackages[] = 'symfony/framework-bundle'; - return ContainerBuilder::willBeAvailable($package, $class, $parentPackages, true); + return ContainerBuilder::willBeAvailable($package, $class, $parentPackages); }; - $enableIfStandalone = static function (string $package, string $class) use ($willBeAvailable) { - return !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; - }; + $enableIfStandalone = fn (string $package, string $class) => !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; $this->addCsrfSection($rootNode); $this->addFormSection($rootNode, $enableIfStandalone); @@ -146,10 +167,11 @@ class Configuration implements ConfigurationInterface $this->addSessionSection($rootNode); $this->addRequestSection($rootNode); $this->addAssetsSection($rootNode, $enableIfStandalone); + $this->addAssetMapperSection($rootNode, $enableIfStandalone); $this->addTranslatorSection($rootNode, $enableIfStandalone); - $this->addValidationSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addValidationSection($rootNode, $enableIfStandalone); $this->addAnnotationsSection($rootNode, $willBeAvailable); - $this->addSerializerSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addSerializerSection($rootNode, $enableIfStandalone); $this->addPropertyAccessSection($rootNode, $willBeAvailable); $this->addPropertyInfoSection($rootNode, $enableIfStandalone); $this->addCacheSection($rootNode, $willBeAvailable); @@ -157,7 +179,9 @@ class Configuration implements ConfigurationInterface $this->addExceptionsSection($rootNode); $this->addWebLinkSection($rootNode, $enableIfStandalone); $this->addLockSection($rootNode, $enableIfStandalone); + $this->addSemaphoreSection($rootNode, $enableIfStandalone); $this->addMessengerSection($rootNode, $enableIfStandalone); + $this->addSchedulerSection($rootNode, $enableIfStandalone); $this->addRobotsIndexSection($rootNode); $this->addHttpClientSection($rootNode, $enableIfStandalone); $this->addMailerSection($rootNode, $enableIfStandalone); @@ -165,11 +189,14 @@ class Configuration implements ConfigurationInterface $this->addNotifierSection($rootNode, $enableIfStandalone); $this->addRateLimiterSection($rootNode, $enableIfStandalone); $this->addUidSection($rootNode, $enableIfStandalone); + $this->addHtmlSanitizerSection($rootNode, $enableIfStandalone); + $this->addWebhookSection($rootNode, $enableIfStandalone); + $this->addRemoteEventSection($rootNode, $enableIfStandalone); return $treeBuilder; } - private function addSecretsSection(ArrayNodeDefinition $rootNode) + private function addSecretsSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -185,7 +212,7 @@ class Configuration implements ConfigurationInterface ; } - private function addCsrfSection(ArrayNodeDefinition $rootNode) + private function addCsrfSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -203,7 +230,7 @@ class Configuration implements ConfigurationInterface ; } - private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -221,17 +248,8 @@ class Configuration implements ConfigurationInterface ->scalarNode('field_name')->defaultValue('_token')->end() ->end() ->end() - // to be set to false in Symfony 6.0 ->booleanNode('legacy_error_messages') - ->defaultTrue() - ->validate() - ->ifTrue() - ->then(function ($v) { - trigger_deprecation('symfony/framework-bundle', '5.2', 'Setting the "framework.form.legacy_error_messages" option to "true" is deprecated. It will have no effect as of Symfony 6.0.'); - - return $v; - }) - ->end() + ->setDeprecated('symfony/framework-bundle', '6.2') ->end() ->end() ->end() @@ -239,7 +257,7 @@ class Configuration implements ConfigurationInterface ; } - private function addHttpCacheSection(ArrayNodeDefinition $rootNode) + private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -258,17 +276,22 @@ class Configuration implements ConfigurationInterface ->performNoDeepMerging() ->scalarPrototype()->end() ->end() + ->arrayNode('skip_response_headers') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() ->booleanNode('allow_reload')->end() ->booleanNode('allow_revalidate')->end() ->integerNode('stale_while_revalidate')->end() ->integerNode('stale_if_error')->end() + ->booleanNode('terminate_on_cache_hit')->end() ->end() ->end() ->end() ; } - private function addEsiSection(ArrayNodeDefinition $rootNode) + private function addEsiSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -280,7 +303,7 @@ class Configuration implements ConfigurationInterface ; } - private function addSsiSection(ArrayNodeDefinition $rootNode) + private function addSsiSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -291,7 +314,7 @@ class Configuration implements ConfigurationInterface ->end(); } - private function addFragmentsSection(ArrayNodeDefinition $rootNode) + private function addFragmentsSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -307,7 +330,7 @@ class Configuration implements ConfigurationInterface ; } - private function addProfilerSection(ArrayNodeDefinition $rootNode) + private function addProfilerSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -319,15 +342,15 @@ class Configuration implements ConfigurationInterface ->scalarNode('collect_parameter')->defaultNull()->info('The name of the parameter to use to enable or disable collection on a per request basis')->end() ->booleanNode('only_exceptions')->defaultFalse()->end() ->booleanNode('only_main_requests')->defaultFalse()->end() - ->booleanNode('only_master_requests')->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, use "only_main_requests" instead.')->defaultFalse()->end() ->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end() + ->booleanNode('collect_serializer_data')->info('Enables the serializer data collector and profiler panel')->defaultFalse()->end() ->end() ->end() ->end() ; } - private function addWorkflowSection(ArrayNodeDefinition $rootNode) + private function addWorkflowSection(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('workflow') @@ -344,13 +367,13 @@ class Configuration implements ConfigurationInterface $workflows = []; } - if (1 === \count($workflows) && isset($workflows['workflows']) && !array_is_list($workflows['workflows']) && !empty(array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions']))) { + if (1 === \count($workflows) && isset($workflows['workflows']) && !array_is_list($workflows['workflows']) && array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions'])) { $workflows = $workflows['workflows']; } foreach ($workflows as $key => $workflow) { if (isset($workflow['enabled']) && false === $workflow['enabled']) { - throw new LogicException(sprintf('Cannot disable a single workflow. Remove the configuration for the workflow "%s" instead.', $workflow['name'])); + throw new LogicException(sprintf('Cannot disable a single workflow. Remove the configuration for the workflow "%s" instead.', $key)); } unset($workflows[$key]['enabled']); @@ -387,7 +410,7 @@ class Configuration implements ConfigurationInterface ->values(['method']) ->end() ->scalarNode('property') - ->defaultValue('marking') + ->cannotBeEmpty() ->end() ->scalarNode('service') ->cannotBeEmpty() @@ -397,12 +420,12 @@ class Configuration implements ConfigurationInterface ->arrayNode('supports') ->beforeNormalization() ->ifString() - ->then(function ($v) { return [$v]; }) + ->then(fn ($v) => [$v]) ->end() ->prototype('scalar') ->cannotBeEmpty() ->validate() - ->ifTrue(function ($v) { return !class_exists($v) && !interface_exists($v, false); }) + ->ifTrue(fn ($v) => !class_exists($v) && !interface_exists($v, false)) ->thenInvalid('The supported class or interface "%s" does not exist.') ->end() ->end() @@ -446,6 +469,10 @@ class Configuration implements ConfigurationInterface ->beforeNormalization() ->always() ->then(function ($places) { + if (!\is_array($places)) { + throw new InvalidConfigurationException('The "places" option must be an array in workflow configuration.'); + } + // It's an indexed array of shape ['place1', 'place2'] if (isset($places[0]) && \is_string($places[0])) { return array_map(function (string $place) { @@ -491,6 +518,10 @@ class Configuration implements ConfigurationInterface ->beforeNormalization() ->always() ->then(function ($transitions) { + if (!\is_array($transitions)) { + throw new InvalidConfigurationException('The "transitions" option must be an array in workflow configuration.'); + } + // It's an indexed array, we let the validation occur if (isset($transitions[0]) && \is_array($transitions[0])) { return $transitions; @@ -523,7 +554,7 @@ class Configuration implements ConfigurationInterface ->arrayNode('from') ->beforeNormalization() ->ifString() - ->then(function ($v) { return [$v]; }) + ->then(fn ($v) => [$v]) ->end() ->requiresAtLeastOneElement() ->prototype('scalar') @@ -533,7 +564,7 @@ class Configuration implements ConfigurationInterface ->arrayNode('to') ->beforeNormalization() ->ifString() - ->then(function ($v) { return [$v]; }) + ->then(fn ($v) => [$v]) ->end() ->requiresAtLeastOneElement() ->prototype('scalar') @@ -590,7 +621,7 @@ class Configuration implements ConfigurationInterface ; } - private function addRouterSection(ArrayNodeDefinition $rootNode) + private function addRouterSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -600,6 +631,7 @@ class Configuration implements ConfigurationInterface ->children() ->scalarNode('resource')->isRequired()->end() ->scalarNode('type')->end() + ->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%')->end() ->scalarNode('default_uri') ->info('The default URI used to generate URLs in a non-HTTP context') ->defaultNull() @@ -615,30 +647,48 @@ class Configuration implements ConfigurationInterface ) ->defaultTrue() ->end() - ->booleanNode('utf8')->defaultNull()->end() + ->booleanNode('utf8')->defaultTrue()->end() ->end() ->end() ->end() ; } - private function addSessionSection(ArrayNodeDefinition $rootNode) + private function addSessionSection(ArrayNodeDefinition $rootNode): void { $rootNode + ->validate() + ->always(function (array $v): array { + if ($v['session']['enabled']) { + if (!\array_key_exists('cookie_secure', $v['session'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting the "framework.session.cookie_secure" config option is deprecated. It will default to "auto" in 7.0.'); + } + + if (!\array_key_exists('cookie_samesite', $v['session'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting the "framework.session.cookie_samesite" config option is deprecated. It will default to "lax" in 7.0.'); + } + + if (!\array_key_exists('handler_id', $v['session']) && !\array_key_exists('save_path', $v['session'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting either "framework.session.handler_id" or "save_path" config options is deprecated; "handler_id" will default to null in 7.0 if "save_path" is not set and to "session.handler.native_file" otherwise.'); + } + } + + $v['session'] += [ + 'cookie_samesite' => null, + 'handler_id' => 'session.handler.native_file', + 'save_path' => '%kernel.cache_dir%/sessions', + ]; + + return $v; + }) + ->end() ->children() ->arrayNode('session') ->info('session configuration') ->canBeEnabled() - ->beforeNormalization() - ->ifTrue(function ($v) { - return \is_array($v) && isset($v['storage_id']) && isset($v['storage_factory_id']); - }) - ->thenInvalid('You cannot use both "storage_id" and "storage_factory_id" at the same time under "framework.session"') - ->end() ->children() - ->scalarNode('storage_id')->defaultValue('session.storage.native')->end() - ->scalarNode('storage_factory_id')->defaultNull()->end() - ->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end() + ->scalarNode('storage_factory_id')->defaultValue('session.storage.factory.native')->end() + ->scalarNode('handler_id')->end() ->scalarNode('name') ->validate() ->ifTrue(function ($v) { @@ -654,12 +704,12 @@ class Configuration implements ConfigurationInterface ->scalarNode('cookie_domain')->end() ->enumNode('cookie_secure')->values([true, false, 'auto'])->end() ->booleanNode('cookie_httponly')->defaultTrue()->end() - ->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultNull()->end() + ->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->end() ->booleanNode('use_cookies')->end() ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() - ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() + ->scalarNode('save_path')->end() ->integerNode('metadata_update_threshold') ->defaultValue(0) ->info('seconds to wait between 2 session metadata updates') @@ -678,7 +728,7 @@ class Configuration implements ConfigurationInterface ; } - private function addRequestSection(ArrayNodeDefinition $rootNode) + private function addRequestSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -691,8 +741,8 @@ class Configuration implements ConfigurationInterface ->useAttributeAsKey('name') ->prototype('array') ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); }) - ->then(function ($v) { return $v['mime_type']; }) + ->ifTrue(fn ($v) => \is_array($v) && isset($v['mime_type'])) + ->then(fn ($v) => $v['mime_type']) ->end() ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() @@ -704,7 +754,7 @@ class Configuration implements ConfigurationInterface ; } - private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -761,8 +811,8 @@ class Configuration implements ConfigurationInterface ->scalarNode('version_strategy')->defaultNull()->end() ->scalarNode('version') ->beforeNormalization() - ->ifTrue(function ($v) { return '' === $v; }) - ->then(function ($v) { return; }) + ->ifTrue(fn ($v) => '' === $v) + ->then(fn () => null) ->end() ->end() ->scalarNode('version_format')->defaultNull()->end() @@ -800,7 +850,109 @@ class Configuration implements ConfigurationInterface ; } - private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addAssetMapperSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('asset_mapper') + ->info('Asset Mapper configuration') + ->{$enableIfStandalone('symfony/asset-mapper', AssetMapper::class)}() + ->fixXmlConfig('path') + ->fixXmlConfig('excluded_pattern') + ->fixXmlConfig('extension') + ->fixXmlConfig('importmap_script_attribute') + ->children() + // add array node called "paths" that will be an array of strings + ->arrayNode('paths') + ->info('Directories that hold assets that should be in the mapper. Can be a simple array of an array of ["path/to/assets": "namespace"]') + ->example(['assets/']) + ->normalizeKeys(false) + ->useAttributeAsKey('namespace') + ->beforeNormalization() + ->always() + ->then(function ($v) { + $result = []; + foreach ($v as $key => $item) { + // "dir" => "namespace" + if (\is_string($key)) { + $result[$key] = $item; + + continue; + } + + if (\is_array($item)) { + // $item = ["namespace" => "the/namespace", "value" => "the/dir"] + $result[$item['value']] = $item['namespace'] ?? ''; + } else { + // $item = "the/dir" + $result[$item] = ''; + } + } + + return $result; + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('excluded_patterns') + ->info('Array of glob patterns of asset file paths that should not be in the asset mapper') + ->prototype('scalar')->end() + ->example(['*/assets/build/*', '*/*_.scss']) + ->end() + // boolean called defaulting to true + ->booleanNode('exclude_dotfiles') + ->info('If true, any files starting with "." will be excluded from the asset mapper') + ->defaultTrue() + ->end() + ->booleanNode('server') + ->info('If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default)') + ->defaultValue($this->debug) + ->end() + ->scalarNode('public_prefix') + ->info('The public path where the assets will be written to (and served from when "server" is true)') + ->defaultValue('/assets/') + ->end() + ->enumNode('missing_import_mode') + ->values(['strict', 'warn', 'ignore']) + ->info('Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import \'./non-existent.js\'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is.') + ->defaultValue('warn') + ->end() + ->arrayNode('extensions') + ->info('Key-value pair of file extensions set to their mime type.') + ->normalizeKeys(false) + ->useAttributeAsKey('extension') + ->example(['.zip' => 'application/zip']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('importmap_path') + ->info('The path of the importmap.php file.') + ->defaultValue('%kernel.project_dir%/importmap.php') + ->end() + ->scalarNode('importmap_polyfill') + ->info('The importmap name that will be used to load the polyfill. Set to false to disable.') + ->defaultValue('es-module-shims') + ->end() + ->arrayNode('importmap_script_attributes') + ->info('Key-value pair of attributes to add to script tags output for the importmap.') + ->normalizeKeys(false) + ->useAttributeAsKey('key') + ->example(['data-turbo-track' => 'reload']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('vendor_dir') + ->info('The directory to store JavaScript vendors.') + ->defaultValue('%kernel.project_dir%/assets/vendor') + ->end() + ->scalarNode('provider') + ->setDeprecated('symfony/framework-bundle', '6.4', 'Option "%node%" at "%path%" is deprecated and does nothing. Remove it.') + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -809,12 +961,11 @@ class Configuration implements ConfigurationInterface ->{$enableIfStandalone('symfony/translation', Translator::class)}() ->fixXmlConfig('fallback') ->fixXmlConfig('path') - ->fixXmlConfig('enabled_locale') ->fixXmlConfig('provider') ->children() ->arrayNode('fallbacks') ->info('Defaults to the value of "default_locale".') - ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() ->prototype('scalar')->end() ->defaultValue([]) ->end() @@ -828,11 +979,6 @@ class Configuration implements ConfigurationInterface ->arrayNode('paths') ->prototype('scalar')->end() ->end() - ->arrayNode('enabled_locales') - ->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, set the "framework.enabled_locales" option instead.') - ->prototype('scalar')->end() - ->defaultValue([]) - ->end() ->arrayNode('pseudo_localization') ->canBeEnabled() ->fixXmlConfig('localizable_html_attribute') @@ -876,16 +1022,39 @@ class Configuration implements ConfigurationInterface ; } - private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) + private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode + ->validate() + ->always(function ($v) { + if ($v['validation']['enabled'] && !\array_key_exists('email_validation_mode', $v['validation'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting the "framework.validation.email_validation_mode" config option is deprecated. It will default to "html5" in 7.0.'); + } + + return $v; + }) + ->end() ->children() ->arrayNode('validation') + ->beforeNormalization() + ->ifTrue(fn ($v) => isset($v['enable_annotations'])) + ->then(function ($v) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Option "enable_annotations" at "framework.validation" is deprecated. Use the "enable_attributes" option instead.'); + + if (isset($v['enable_attributes'])) { + throw new LogicException('The "enable_annotations" and "enable_attributes" options at path "framework.validation" must not be both set. Only the "enable_attributes" option must be used.'); + } + $v['enable_attributes'] = $v['enable_annotations']; + + return $v; + }) + ->end() ->info('validation configuration') ->{$enableIfStandalone('symfony/validator', Validation::class)}() ->children() ->scalarNode('cache')->end() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/validator')) ? 'defaultTrue' : 'defaultFalse'}()->end() + ->booleanNode('enable_annotations')->end() + ->booleanNode('enable_attributes')->{class_exists(FullStack::class) ? 'defaultFalse' : 'defaultTrue'}()->end() ->arrayNode('static_method') ->defaultValue(['loadValidatorMetadata']) ->prototype('scalar')->end() @@ -966,18 +1135,18 @@ class Configuration implements ConfigurationInterface ; } - private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) + private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void { - $doctrineCache = $willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotations'); - $psr6Cache = $willBeAvailable('symfony/cache', PsrCachedReader::class, 'doctrine/annotations'); - $rootNode ->children() ->arrayNode('annotations') ->info('annotation configuration') ->{$willBeAvailable('doctrine/annotations', Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() - ->scalarNode('cache')->defaultValue(($doctrineCache || $psr6Cache) ? 'php_array' : 'none')->end() + ->enumNode('cache') + ->values(['none', 'php_array', 'file']) + ->defaultValue('php_array') + ->end() ->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end() ->booleanNode('debug')->defaultValue($this->debug)->end() ->end() @@ -986,15 +1155,29 @@ class Configuration implements ConfigurationInterface ; } - private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) + private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() ->arrayNode('serializer') + ->beforeNormalization() + ->ifTrue(fn ($v) => isset($v['enable_annotations'])) + ->then(function ($v) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Option "enable_annotations" at "framework.serializer" is deprecated. Use the "enable_attributes" option instead.'); + + if (isset($v['enable_attributes'])) { + throw new LogicException('The "enable_annotations" and "enable_attributes" options at path "framework.serializer" must not be both set. Only the "enable_attributes" option must be used.'); + } + $v['enable_attributes'] = $v['enable_annotations']; + + return $v; + }) + ->end() ->info('serializer configuration') ->{$enableIfStandalone('symfony/serializer', Serializer::class)}() ->children() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/serializer')) ? 'defaultTrue' : 'defaultFalse'}()->end() + ->booleanNode('enable_annotations')->end() + ->booleanNode('enable_attributes')->{class_exists(FullStack::class) ? 'defaultFalse' : 'defaultTrue'}()->end() ->scalarNode('name_converter')->end() ->scalarNode('circular_reference_handler')->end() ->scalarNode('max_depth_handler')->end() @@ -1010,6 +1193,10 @@ class Configuration implements ConfigurationInterface ->arrayNode('default_context') ->normalizeKeys(false) ->useAttributeAsKey('name') + ->beforeNormalization() + ->ifTrue(fn () => $this->debug && class_exists(JsonParser::class)) + ->then(fn (array $v) => $v + [JsonDecode::DETAILED_ERROR_MESSAGES => true]) + ->end() ->defaultValue([]) ->prototype('variable')->end() ->end() @@ -1019,7 +1206,7 @@ class Configuration implements ConfigurationInterface ; } - private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) + private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void { $rootNode ->children() @@ -1039,7 +1226,7 @@ class Configuration implements ConfigurationInterface ; } - private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1051,7 +1238,7 @@ class Configuration implements ConfigurationInterface ; } - private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) + private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void { $rootNode ->children() @@ -1074,7 +1261,6 @@ class Configuration implements ConfigurationInterface ->defaultValue('cache.adapter.system') ->end() ->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools/app')->end() - ->scalarNode('default_doctrine_provider')->end() ->scalarNode('default_psr6_provider')->end() ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() @@ -1085,7 +1271,7 @@ class Configuration implements ConfigurationInterface ->prototype('array') ->fixXmlConfig('adapter') ->beforeNormalization() - ->ifTrue(function ($v) { return isset($v['provider']) && \is_array($v['adapters'] ?? $v['adapter'] ?? null) && 1 < \count($v['adapters'] ?? $v['adapter']); }) + ->ifTrue(fn ($v) => isset($v['provider']) && \is_array($v['adapters'] ?? $v['adapter'] ?? null) && 1 < \count($v['adapters'] ?? $v['adapter'])) ->thenInvalid('Pool cannot have a "provider" while more than one adapter is defined') ->end() ->children() @@ -1133,7 +1319,7 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['cache.app']) || isset($v['cache.system']); }) + ->ifTrue(fn ($v) => isset($v['cache.app']) || isset($v['cache.system'])) ->thenInvalid('"cache.app" and "cache.system" are reserved names') ->end() ->end() @@ -1143,9 +1329,20 @@ class Configuration implements ConfigurationInterface ; } - private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) + private function addPhpErrorsSection(ArrayNodeDefinition $rootNode): void { $rootNode + ->validate() + ->always(function (array $v): array { + if (!\array_key_exists('log', $v['php_errors'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting the "framework.php_errors.log" config option is deprecated. It will default to "true" in 7.0.'); + + $v['php_errors']['log'] = $this->debug; + } + + return $v; + }) + ->end() ->children() ->arrayNode('php_errors') ->info('PHP errors handling configuration') @@ -1154,7 +1351,6 @@ class Configuration implements ConfigurationInterface ->variableNode('log') ->info('Use the application logger instead of the PHP logger for logging PHP errors.') ->example('"true" to use the default configuration: log all errors. "false" to disable. An integer bit field of E_* constants, or an array mapping E_* constants to log levels.') - ->defaultValue($this->debug) ->treatNullLike($this->debug) ->beforeNormalization() ->ifArray() @@ -1174,7 +1370,7 @@ class Configuration implements ConfigurationInterface }) ->end() ->validate() - ->ifTrue(function ($v) { return !(\is_int($v) || \is_bool($v) || \is_array($v)); }) + ->ifTrue(fn ($v) => !(\is_int($v) || \is_bool($v) || \is_array($v))) ->thenInvalid('The "php_errors.log" parameter should be either an integer, a boolean, or an array') ->end() ->end() @@ -1189,7 +1385,7 @@ class Configuration implements ConfigurationInterface ; } - private function addExceptionsSection(ArrayNodeDefinition $rootNode) + private function addExceptionsSection(ArrayNodeDefinition $rootNode): void { $logLevels = (new \ReflectionClass(LogLevel::class))->getConstants(); @@ -1207,6 +1403,8 @@ class Configuration implements ConfigurationInterface return $v; } + trigger_deprecation('symfony/framework-bundle', '6.3', '"framework:exceptions" tag is deprecated. Unwrap it and replace your "framework:exception" tags\' "name" attribute by "class".'); + $v = $v['exception']; unset($v['exception']); @@ -1223,7 +1421,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('log_level') ->info('The level of log message. Null to let Symfony decide.') ->validate() - ->ifTrue(function ($v) use ($logLevels) { return null !== $v && !\in_array($v, $logLevels, true); }) + ->ifTrue(fn ($v) => null !== $v && !\in_array($v, $logLevels, true)) ->thenInvalid(sprintf('The log level is not valid. Pick one among "%s".', implode('", "', $logLevels))) ->end() ->defaultNull() @@ -1231,11 +1429,11 @@ class Configuration implements ConfigurationInterface ->scalarNode('status_code') ->info('The status code of the response. Null or 0 to let Symfony decide.') ->beforeNormalization() - ->ifTrue(function ($v) { return 0 === $v; }) - ->then(function ($v) { return null; }) + ->ifTrue(fn ($v) => 0 === $v) + ->then(fn ($v) => null) ->end() ->validate() - ->ifTrue(function ($v) { return null !== $v && ($v < 100 || $v > 599); }) + ->ifTrue(fn ($v) => null !== $v && ($v < 100 || $v > 599)) ->thenInvalid('The status code is not valid. Pick a value between 100 and 599.') ->end() ->defaultNull() @@ -1247,7 +1445,7 @@ class Configuration implements ConfigurationInterface ; } - private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1255,14 +1453,14 @@ class Configuration implements ConfigurationInterface ->info('Lock configuration') ->{$enableIfStandalone('symfony/lock', Lock::class)}() ->beforeNormalization() - ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); }) - ->then(function ($v) { return $v + ['enabled' => true]; }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) ->then(function ($v) { $e = $v['enabled']; unset($v['enabled']); @@ -1272,7 +1470,7 @@ class Configuration implements ConfigurationInterface ->end() ->addDefaultsIfNotSet() ->validate() - ->ifTrue(static function (array $config) { return $config['enabled'] && !$config['resources']; }) + ->ifTrue(fn ($config) => $config['enabled'] && !$config['resources']) ->thenInvalid('At least one resource must be defined.') ->end() ->fixXmlConfig('resource') @@ -1282,10 +1480,10 @@ class Configuration implements ConfigurationInterface ->useAttributeAsKey('name') ->defaultValue(['default' => [class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock']]) ->beforeNormalization() - ->ifString()->then(function ($v) { return ['default' => $v]; }) + ->ifString()->then(fn ($v) => ['default' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); }) + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) ->then(function ($v) { $resources = []; foreach ($v as $resource) { @@ -1300,7 +1498,7 @@ class Configuration implements ConfigurationInterface ->end() ->prototype('array') ->performNoDeepMerging() - ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() ->prototype('scalar')->end() ->end() ->end() @@ -1310,7 +1508,62 @@ class Configuration implements ConfigurationInterface ; } - private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('semaphore') + ->info('Semaphore configuration') + ->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}() + ->beforeNormalization() + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) + ->then(function ($v) { + $e = $v['enabled']; + unset($v['enabled']); + + return ['enabled' => $e, 'resources' => $v]; + }) + ->end() + ->addDefaultsIfNotSet() + ->fixXmlConfig('resource') + ->children() + ->arrayNode('resources') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->requiresAtLeastOneElement() + ->beforeNormalization() + ->ifString()->then(fn ($v) => ['default' => $v]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) + ->then(function ($v) { + $resources = []; + foreach ($v as $resource) { + $resources[] = \is_array($resource) && isset($resource['name']) + ? [$resource['name'] => $resource['value']] + : ['default' => $resource] + ; + } + + return array_merge_recursive([], ...$resources); + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1322,7 +1575,7 @@ class Configuration implements ConfigurationInterface ; } - private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1332,12 +1585,12 @@ class Configuration implements ConfigurationInterface ->fixXmlConfig('transport') ->fixXmlConfig('bus', 'buses') ->validate() - ->ifTrue(function ($v) { return isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']; }) + ->ifTrue(fn ($v) => isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']) ->thenInvalid('You must specify the "default_bus" if you define more than one bus.') ->end() ->validate() - ->ifTrue(static function ($v): bool { return isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']]); }) - ->then(static function (array $v): void { throw new InvalidConfigurationException(sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], implode('", "', array_keys($v['buses'])))); }) + ->ifTrue(fn ($v) => isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']])) + ->then(fn ($v) => throw new InvalidConfigurationException(sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], implode('", "', array_keys($v['buses']))))) ->end() ->children() ->arrayNode('routing') @@ -1448,6 +1701,10 @@ class Configuration implements ConfigurationInterface ->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end() ->end() ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use when processing messages') + ->end() ->end() ->end() ->end() @@ -1456,26 +1713,47 @@ class Configuration implements ConfigurationInterface ->info('Transport name to send failed messages to (after all retries have failed).') ->end() ->booleanNode('reset_on_message') - ->defaultNull() + ->defaultTrue() ->info('Reset container services after each message.') + ->setDeprecated('symfony/framework-bundle', '6.1', 'Option "%node%" at "%path%" is deprecated. It does nothing and will be removed in version 7.0.') + ->validate() + ->ifTrue(fn ($v) => true !== $v) + ->thenInvalid('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.') + ->end() + ->end() + ->arrayNode('stop_worker_on_signals') + ->defaultValue([]) + ->info('A list of signals that should stop the worker; defaults to SIGTERM and SIGINT.') + ->integerPrototype()->end() ->end() ->scalarNode('default_bus')->defaultNull()->end() ->arrayNode('buses') - ->defaultValue(['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]]) + ->defaultValue(['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]]) ->normalizeKeys(false) ->useAttributeAsKey('name') ->arrayPrototype() ->addDefaultsIfNotSet() ->children() - ->enumNode('default_middleware') - ->values([true, false, 'allow_no_handlers']) - ->defaultTrue() + ->arrayNode('default_middleware') + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_string($v) || \is_bool($v)) + ->then(fn ($v) => [ + 'enabled' => 'allow_no_handlers' === $v ? true : $v, + 'allow_no_handlers' => 'allow_no_handlers' === $v, + 'allow_no_senders' => true, + ]) + ->end() + ->canBeDisabled() + ->children() + ->booleanNode('allow_no_handlers')->defaultFalse()->end() + ->booleanNode('allow_no_senders')->defaultTrue()->end() + ->end() ->end() ->arrayNode('middleware') ->performNoDeepMerging() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_string($v) || (\is_array($v) && !\is_int(key($v))); }) - ->then(function ($v) { return [$v]; }) + ->ifTrue(fn ($v) => \is_string($v) || (\is_array($v) && !\is_int(key($v)))) + ->then(fn ($v) => [$v]) ->end() ->defaultValue([]) ->arrayPrototype() @@ -1518,7 +1796,19 @@ class Configuration implements ConfigurationInterface ; } - private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) + private function addSchedulerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('scheduler') + ->info('Scheduler configuration') + ->{$enableIfStandalone('symfony/scheduler', Schedule::class)}() + ->end() + ->end() + ; + } + + private function addRobotsIndexSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -1531,7 +1821,7 @@ class Configuration implements ConfigurationInterface ; } - private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1551,7 +1841,7 @@ class Configuration implements ConfigurationInterface continue; } if (\is_array($scopedConfig['retry_failed'])) { - $scopedConfig['retry_failed'] = $scopedConfig['retry_failed'] + $config['default_options']['retry_failed']; + $scopedConfig['retry_failed'] += $config['default_options']['retry_failed']; } } @@ -1571,6 +1861,11 @@ class Configuration implements ConfigurationInterface ->normalizeKeys(false) ->variablePrototype()->end() ->end() + ->arrayNode('vars') + ->info('Associative array: the default vars used to expand the templated URI.') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() ->integerNode('max_redirects') ->info('The maximum number of redirects to follow.') ->end() @@ -1611,7 +1906,7 @@ class Configuration implements ConfigurationInterface ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') ->end() ->booleanNode('verify_peer') - ->info('Indicates if the peer should be verified in an SSL/TLS context.') + ->info('Indicates if the peer should be verified in a TLS context.') ->end() ->booleanNode('verify_host') ->info('Indicates if the host should exist as a certificate common name.') @@ -1632,7 +1927,7 @@ class Configuration implements ConfigurationInterface ->info('The passphrase used to encrypt the "local_pk" file.') ->end() ->scalarNode('ciphers') - ->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') + ->info('A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') ->end() ->arrayNode('peer_fingerprint') ->info('Associative array: hashing algorithm => hash(es).') @@ -1643,7 +1938,15 @@ class Configuration implements ConfigurationInterface ->variableNode('md5')->end() ->end() ->end() - ->append($this->addHttpClientRetrySection()) + ->scalarNode('crypto_method') + ->info('The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants.') + ->end() + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->append($this->createHttpClientRetrySection()) ->end() ->end() ->scalarNode('mock_response_factory') @@ -1665,11 +1968,11 @@ class Configuration implements ConfigurationInterface }) ->end() ->validate() - ->ifTrue(function ($v) { return !isset($v['scope']) && !isset($v['base_uri']); }) + ->ifTrue(fn ($v) => !isset($v['scope']) && !isset($v['base_uri'])) ->thenInvalid('Either "scope" or "base_uri" should be defined.') ->end() ->validate() - ->ifTrue(function ($v) { return !empty($v['query']) && !isset($v['base_uri']); }) + ->ifTrue(fn ($v) => !empty($v['query']) && !isset($v['base_uri'])) ->thenInvalid('"query" applies to "base_uri" but no base URI is defined.') ->end() ->children() @@ -1754,7 +2057,7 @@ class Configuration implements ConfigurationInterface ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') ->end() ->booleanNode('verify_peer') - ->info('Indicates if the peer should be verified in an SSL/TLS context.') + ->info('Indicates if the peer should be verified in a TLS context.') ->end() ->booleanNode('verify_host') ->info('Indicates if the host should exist as a certificate common name.') @@ -1775,7 +2078,7 @@ class Configuration implements ConfigurationInterface ->info('The passphrase used to encrypt the "local_pk" file.') ->end() ->scalarNode('ciphers') - ->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') + ->info('A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') ->end() ->arrayNode('peer_fingerprint') ->info('Associative array: hashing algorithm => hash(es).') @@ -1786,7 +2089,12 @@ class Configuration implements ConfigurationInterface ->variableNode('md5')->end() ->end() ->end() - ->append($this->addHttpClientRetrySection()) + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->append($this->createHttpClientRetrySection()) ->end() ->end() ->end() @@ -1796,7 +2104,7 @@ class Configuration implements ConfigurationInterface ; } - private function addHttpClientRetrySection() + private function createHttpClientRetrySection(): ArrayNodeDefinition { $root = new NodeBuilder(); @@ -1847,9 +2155,7 @@ class Configuration implements ConfigurationInterface ->arrayNode('methods') ->beforeNormalization() ->ifArray() - ->then(function ($v) { - return array_map('strtoupper', $v); - }) + ->then(fn ($v) => array_map('strtoupper', $v)) ->end() ->prototype('scalar')->end() ->info('A list of HTTP methods that triggers a retry for this status code. When empty, all methods are retried') @@ -1867,7 +2173,7 @@ class Configuration implements ConfigurationInterface ; } - private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1875,7 +2181,7 @@ class Configuration implements ConfigurationInterface ->info('Mailer configuration') ->{$enableIfStandalone('symfony/mailer', Mailer::class)}() ->validate() - ->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); }) + ->ifTrue(fn ($v) => isset($v['dsn']) && \count($v['transports'])) ->thenInvalid('"dsn" and "transports" cannot be used together.') ->end() ->fixXmlConfig('transport') @@ -1895,9 +2201,7 @@ class Configuration implements ConfigurationInterface ->performNoDeepMerging() ->beforeNormalization() ->ifArray() - ->then(function ($v) { - return array_filter(array_values($v)); - }) + ->then(fn ($v) => array_filter(array_values($v))) ->end() ->prototype('scalar')->end() ->end() @@ -1909,8 +2213,8 @@ class Configuration implements ConfigurationInterface ->prototype('array') ->normalizeKeys(false) ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; }) - ->then(function ($v) { return ['value' => $v]; }) + ->ifTrue(fn ($v) => !\is_array($v) || array_keys($v) !== ['value']) + ->then(fn ($v) => ['value' => $v]) ->end() ->children() ->variableNode('value')->end() @@ -1923,13 +2227,16 @@ class Configuration implements ConfigurationInterface ; } - private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() ->arrayNode('notifier') ->info('Notifier configuration') ->{$enableIfStandalone('symfony/notifier', Notifier::class)}() + ->children() + ->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end() + ->end() ->fixXmlConfig('chatter_transport') ->children() ->arrayNode('chatter_transports') @@ -1951,7 +2258,7 @@ class Configuration implements ConfigurationInterface ->arrayNode('channel_policy') ->useAttributeAsKey('name') ->prototype('array') - ->beforeNormalization()->ifString()->then(function (string $v) { return [$v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() ->prototype('scalar')->end() ->end() ->end() @@ -1972,7 +2279,48 @@ class Configuration implements ConfigurationInterface ; } - private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addWebhookSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('webhook') + ->info('Webhook configuration') + ->{$enableIfStandalone('symfony/webhook', WebhookController::class)}() + ->children() + ->scalarNode('message_bus')->defaultValue('messenger.default_bus')->info('The message bus to use.')->end() + ->arrayNode('routing') + ->normalizeKeys(false) + ->useAttributeAsKey('type') + ->prototype('array') + ->children() + ->scalarNode('service') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('secret') + ->defaultValue('') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRemoteEventSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('remote-event') + ->info('RemoteEvent configuration') + ->{$enableIfStandalone('symfony/remote-event', RemoteEvent::class)}() + ->end() + ->end() + ; + } + + private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1981,7 +2329,7 @@ class Configuration implements ConfigurationInterface ->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}() ->fixXmlConfig('limiter') ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['limiters']) && !isset($v['limiter'])) ->then(function (array $v) { $newV = [ 'enabled' => $v['enabled'] ?? true, @@ -2017,7 +2365,6 @@ class Configuration implements ConfigurationInterface ->end() ->integerNode('limit') ->info('The maximum allowed hits in a fixed interval or burst') - ->isRequired() ->end() ->scalarNode('interval') ->info('Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).') @@ -2032,6 +2379,10 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->validate() + ->ifTrue(fn ($v) => 'no_limit' !== $v['policy'] && !isset($v['limit'])) + ->thenInvalid('A limit must be provided when using a policy different than "no_limit".') + ->end() ->end() ->end() ->end() @@ -2040,9 +2391,26 @@ class Configuration implements ConfigurationInterface ; } - private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode + ->validate() + ->always(function ($v) { + if ($v['uid']['enabled']) { + if (!\array_key_exists('default_uuid_version', $v['uid'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting the "framework.uid.default_uuid_version" config option is deprecated. It will default to "7" in 7.0.'); + } + + if (!\array_key_exists('time_based_uuid_version', $v['uid'])) { + trigger_deprecation('symfony/framework-bundle', '6.4', 'Not setting the "framework.uid.time_based_uuid_version" config option is deprecated. It will default to "7" in 7.0.'); + } + } + + $v['uid'] += ['default_uuid_version' => 6, 'time_based_uuid_version' => 6]; + + return $v; + }) + ->end() ->children() ->arrayNode('uid') ->info('Uid configuration') @@ -2050,8 +2418,7 @@ class Configuration implements ConfigurationInterface ->addDefaultsIfNotSet() ->children() ->enumNode('default_uuid_version') - ->defaultValue(6) - ->values([6, 4, 1]) + ->values([7, 6, 4, 1]) ->end() ->enumNode('name_based_uuid_version') ->defaultValue(5) @@ -2061,8 +2428,7 @@ class Configuration implements ConfigurationInterface ->cannotBeEmpty() ->end() ->enumNode('time_based_uuid_version') - ->defaultValue(6) - ->values([6, 1]) + ->values([7, 6, 1]) ->end() ->scalarNode('time_based_uuid_node') ->cannotBeEmpty() @@ -2072,4 +2438,155 @@ class Configuration implements ConfigurationInterface ->end() ; } + + private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('html_sanitizer') + ->info('HtmlSanitizer configuration') + ->{$enableIfStandalone('symfony/html-sanitizer', HtmlSanitizerInterface::class)}() + ->fixXmlConfig('sanitizer') + ->children() + ->arrayNode('sanitizers') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->fixXmlConfig('allow_element') + ->fixXmlConfig('block_element') + ->fixXmlConfig('drop_element') + ->fixXmlConfig('allow_attribute') + ->fixXmlConfig('drop_attribute') + ->fixXmlConfig('force_attribute') + ->fixXmlConfig('allowed_link_scheme') + ->fixXmlConfig('allowed_link_host') + ->fixXmlConfig('allowed_media_scheme') + ->fixXmlConfig('allowed_media_host') + ->fixXmlConfig('with_attribute_sanitizer') + ->fixXmlConfig('without_attribute_sanitizer') + ->children() + ->booleanNode('allow_safe_elements') + ->info('Allows "safe" elements and attributes.') + ->defaultFalse() + ->end() + ->booleanNode('allow_static_elements') + ->info('Allows all static elements and attributes from the W3C Sanitizer API standard.') + ->defaultFalse() + ->end() + ->arrayNode('allow_elements') + ->info('Configures the elements that the sanitizer should retain from the input. The element name is the key, the value is either a list of allowed attributes for this element or "*" to allow the default set of attributes (https://wicg.github.io/sanitizer-api/#default-configuration).') + ->example(['i' => '*', 'a' => ['title'], 'span' => 'class']) + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['attribute'] ?? $n) + ->end() + ->validate() + ->ifTrue(fn ($n): bool => !\is_string($n) && !\is_array($n)) + ->thenInvalid('The value must be either a string or an array of strings.') + ->end() + ->end() + ->end() + ->arrayNode('block_elements') + ->info('Configures elements as blocked. Blocked elements are elements the sanitizer should remove from the input, but retain their children.') + ->beforeNormalization() + ->ifString() + ->then(fn (string $n): array => (array) $n) + ->end() + ->scalarPrototype()->end() + ->end() + ->arrayNode('drop_elements') + ->info('Configures elements as dropped. Dropped elements are elements the sanitizer should remove from the input, including their children.') + ->beforeNormalization() + ->ifString() + ->then(fn (string $n): array => (array) $n) + ->end() + ->scalarPrototype()->end() + ->end() + ->arrayNode('allow_attributes') + ->info('Configures attributes as allowed. Allowed attributes are attributes the sanitizer should retain from the input.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['element'] ?? $n) + ->end() + ->end() + ->end() + ->arrayNode('drop_attributes') + ->info('Configures attributes as dropped. Dropped attributes are attributes the sanitizer should remove from the input.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['element'] ?? $n) + ->end() + ->end() + ->end() + ->arrayNode('force_attributes') + ->info('Forcefully set the values of certain attributes on certain elements.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->scalarPrototype()->end() + ->end() + ->end() + ->booleanNode('force_https_urls') + ->info('Transforms URLs using the HTTP scheme to use the HTTPS scheme instead.') + ->defaultFalse() + ->end() + ->arrayNode('allowed_link_schemes') + ->info('Allows only a given list of schemes to be used in links href attributes.') + ->scalarPrototype()->end() + ->end() + ->variableNode('allowed_link_hosts') + ->info('Allows only a given list of hosts to be used in links href attributes.') + ->defaultValue(null) + ->validate() + ->ifTrue(fn ($v) => !\is_array($v) && null !== $v) + ->thenInvalid('The "allowed_link_hosts" parameter must be an array or null') + ->end() + ->end() + ->booleanNode('allow_relative_links') + ->info('Allows relative URLs to be used in links href attributes.') + ->defaultFalse() + ->end() + ->arrayNode('allowed_media_schemes') + ->info('Allows only a given list of schemes to be used in media source attributes (img, audio, video, ...).') + ->scalarPrototype()->end() + ->end() + ->variableNode('allowed_media_hosts') + ->info('Allows only a given list of hosts to be used in media source attributes (img, audio, video, ...).') + ->defaultValue(null) + ->validate() + ->ifTrue(fn ($v) => !\is_array($v) && null !== $v) + ->thenInvalid('The "allowed_media_hosts" parameter must be an array or null') + ->end() + ->end() + ->booleanNode('allow_relative_medias') + ->info('Allows relative URLs to be used in media source attributes (img, audio, video, ...).') + ->defaultFalse() + ->end() + ->arrayNode('with_attribute_sanitizers') + ->info('Registers custom attribute sanitizers.') + ->scalarPrototype()->end() + ->end() + ->arrayNode('without_attribute_sanitizers') + ->info('Unregisters custom attribute sanitizers.') + ->scalarPrototype()->end() + ->end() + ->integerNode('max_input_length') + ->info('The maximum length allowed for the sanitized input.') + ->defaultValue(0) + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php b/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php index 00412e5c6..8a42bfb29 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php +++ b/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php @@ -12,42 +12,47 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Composer\InstalledVersions; -use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; +use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Types\ContextFactory; +use PhpParser\Parser; use PHPStan\PhpDocParser\Parser\PhpDocParser; use Psr\Cache\CacheItemPoolInterface; +use Psr\Clock\ClockInterface as PsrClockInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; -use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use Psr\Http\Client\ClientInterface; use Psr\Log\LoggerAwareInterface; use Symfony\Bridge\Monolog\Processor\DebugProcessor; use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use Symfony\Bundle\FullStack; use Symfony\Bundle\MercureBundle\MercureBundle; use Symfony\Component\Asset\PackageInterface; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface; use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\ChainAdapter; -use Symfony\Component\Cache\Adapter\DoctrineAdapter; -use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\DependencyInjection\CachePoolPass; -use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\Marshaller\MarshallerInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\ResourceCheckerInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\DataCollector\CommandDataCollector; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Messenger\RunCommandMessageHandler; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -68,106 +73,63 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Finder\Finder; -use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Finder\Glob; +use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormTypeExtensionInterface; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeInterface; +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; +use Symfony\Component\HttpClient\Messenger\PingWebhookMessageHandler; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpClient\UriTemplateHttpClient; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\Lock\Lock; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\LockInterface; use Symfony\Component\Lock\PersistingStoreInterface; use Symfony\Component\Lock\Store\StoreFactory; -use Symfony\Component\Lock\StoreInterface; -use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; -use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; -use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; -use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; -use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; -use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory; -use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; -use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; -use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; +use Symfony\Component\Mailer\Bridge as MailerBridge; +use Symfony\Component\Mailer\Command\MailerTestCommand; +use Symfony\Component\Mailer\EventListener\MessengerTransportListener; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Messenger\Attribute\AsMessageHandler; -use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; -use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; -use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; -use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; +use Symfony\Component\Messenger\Bridge as MessengerBridge; +use Symfony\Component\Messenger\EventListener\StopWorkerOnSignalsListener; use Symfony\Component\Messenger\Handler\BatchHandlerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; -use Symfony\Component\Messenger\Transport\TransportFactoryInterface; +use Symfony\Component\Messenger\Transport\TransportFactoryInterface as MessengerTransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\MimeTypeGuesserInterface; use Symfony\Component\Mime\MimeTypes; -use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; -use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; -use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; -use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; -use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; -use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; -use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; -use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; -use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; -use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; -use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; -use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; -use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; -use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; -use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; -use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransport; -use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; -use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; -use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; -use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory; -use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; -use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; -use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; -use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as SendinblueNotifierTransportFactory; -use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; -use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; -use Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; -use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; -use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; -use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; -use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; -use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; -use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; -use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransport; -use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; -use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; -use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; -use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; +use Symfony\Component\Notifier\Bridge as NotifierBridge; use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\Notifier; use Symfony\Component\Notifier\Recipient\Recipient; use Symfony\Component\Notifier\TexterInterface; use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface; +use Symfony\Component\Process\Messenger\RunProcessMessageHandler; use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; @@ -180,31 +142,47 @@ use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; use Symfony\Component\RateLimiter\LimiterInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; use Symfony\Component\RateLimiter\Storage\CacheStorage; -use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; -use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Routing\Loader\AttributeClassLoader; +use Symfony\Component\Scheduler\Attribute\AsCronTask; +use Symfony\Component\Scheduler\Attribute\AsPeriodicTask; +use Symfony\Component\Scheduler\Attribute\AsSchedule; +use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; +use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Semaphore\PersistingStoreInterface as SemaphoreStoreInterface; +use Symfony\Component\Semaphore\Semaphore; +use Symfony\Component\Semaphore\SemaphoreFactory; +use Symfony\Component\Semaphore\Store\StoreFactory as SemaphoreStoreFactory; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; +use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; +use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; +use Symfony\Component\Serializer\Serializer; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\String\LazyString; use Symfony\Component\String\Slugger\SluggerInterface; -use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; -use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; -use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; +use Symfony\Component\Translation\Bridge as TranslationBridge; use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand; +use Symfony\Component\Translation\Extractor\PhpAstExtractor; +use Symfony\Component\Translation\LocaleSwitcher; use Symfony\Component\Translation\PseudoLocalizationTranslator; use Symfony\Component\Translation\Translator; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider; use Symfony\Component\Validator\ConstraintValidatorInterface; +use Symfony\Component\Validator\GroupProviderInterface; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\ObjectInitializerInterface; use Symfony\Component\Validator\Validation; +use Symfony\Component\Validator\ValidatorBuilder; +use Symfony\Component\Webhook\Controller\WebhookController; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; use Symfony\Component\Workflow\WorkflowInterface; @@ -225,42 +203,42 @@ use Symfony\Contracts\Translation\LocaleAwareInterface; */ class FrameworkExtension extends Extension { - private $formConfigEnabled = false; - private $translationConfigEnabled = false; - private $sessionConfigEnabled = false; - private $annotationsConfigEnabled = false; - private $validatorConfigEnabled = false; - private $messengerConfigEnabled = false; - private $mailerConfigEnabled = false; - private $httpClientConfigEnabled = false; - private $notifierConfigEnabled = false; - private $propertyAccessConfigEnabled = false; - private static $lockConfigEnabled = false; + private array $configsEnabled = []; /** * Responds to the app.config configuration parameter. * + * @return void + * * @throws LogicException */ public function load(array $configs, ContainerBuilder $container) { - if (!class_exists(InstalledVersions::class)) { - trigger_deprecation('symfony/framework-bundle', '5.4', 'Configuring Symfony without the Composer Runtime API is deprecated. Consider upgrading to Composer 2.1 or later.'); - } - $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); + if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('symfony/symfony') && 'symfony/symfony' !== (InstalledVersions::getRootPackage()['name'] ?? '')) { + trigger_deprecation('symfony/symfony', '6.1', 'Requiring the "symfony/symfony" package is deprecated; replace it with standalone components instead.'); + } + $loader->load('web.php'); $loader->load('services.php'); $loader->load('fragment_renderer.php'); $loader->load('error_renderer.php'); - if (ContainerBuilder::willBeAvailable('psr/event-dispatcher', PsrEventDispatcherInterface::class, ['symfony/framework-bundle'], true)) { - $container->setAlias(PsrEventDispatcherInterface::class, 'event_dispatcher'); + if (!ContainerBuilder::willBeAvailable('symfony/clock', ClockInterface::class, ['symfony/framework-bundle'])) { + $container->removeDefinition('clock'); + $container->removeAlias(ClockInterface::class); + $container->removeAlias(PsrClockInterface::class); } $container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class); + $loader->load('process.php'); + + if (!class_exists(RunProcessMessageHandler::class)) { + $container->removeDefinition('process.messenger.process_message_handler'); + } + if ($this->hasConsole()) { $loader->load('console.php'); @@ -274,6 +252,11 @@ class FrameworkExtension extends Extension if (!class_exists(DebugCommand::class)) { $container->removeDefinition('console.command.dotenv_debug'); } + + if (!class_exists(RunCommandMessageHandler::class)) { + $container->removeDefinition('console.messenger.application'); + $container->removeDefinition('console.messenger.execute_command_handler'); + } } // Load Cache configuration first as it is used by other components @@ -282,14 +265,18 @@ class FrameworkExtension extends Extension $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $this->annotationsConfigEnabled = $this->isConfigEnabled($container, $config['annotations']); - $this->translationConfigEnabled = $this->isConfigEnabled($container, $config['translator']); + // warmup config enabled + $this->readConfigEnabled('annotations', $container, $config['annotations']); + $this->readConfigEnabled('translator', $container, $config['translator']); + $this->readConfigEnabled('property_access', $container, $config['property_access']); + $this->readConfigEnabled('profiler', $container, $config['profiler']); + $this->readConfigEnabled('workflows', $container, $config['workflows']); // A translator must always be registered (as support is included by // default in the Form and Validator component). If disabled, an identity // translator will be used and everything will still work as expected. - if ($this->isConfigEnabled($container, $config['translator']) || $this->isConfigEnabled($container, $config['form']) || $this->isConfigEnabled($container, $config['validation'])) { - if (!class_exists(Translator::class) && $this->isConfigEnabled($container, $config['translator'])) { + if ($this->readConfigEnabled('translator', $container, $config['translator']) || $this->readConfigEnabled('form', $container, $config['form']) || $this->readConfigEnabled('validation', $container, $config['validation'])) { + if (!class_exists(Translator::class) && $this->readConfigEnabled('translator', $container, $config['translator'])) { throw new LogicException('Translation support cannot be enabled as the Translation component is not installed. Try running "composer require symfony/translation".'); } @@ -300,14 +287,15 @@ class FrameworkExtension extends Extension $container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']); $container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']); + $container->getDefinition('http_kernel')->replaceArgument(4, $config['handle_all_throwables'] ?? false); // If the slugger is used but the String component is not available, we should throw an error - if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'], true)) { - $container->register('slugger', 'stdClass') + if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'])) { + $container->register('slugger', SluggerInterface::class) ->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".'); } else { - if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'], true)) { - $container->register('slugger', 'stdClass') + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'])) { + $container->register('slugger', SluggerInterface::class) ->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".'); } @@ -321,6 +309,7 @@ class FrameworkExtension extends Extension } $container->setParameter('kernel.http_method_override', $config['http_method_override']); + $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']); $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); @@ -343,11 +332,11 @@ class FrameworkExtension extends Extension } } - if ($this->isConfigEnabled($container, $config['request'])) { + if ($this->readConfigEnabled('request', $container, $config['request'])) { $this->registerRequestConfiguration($config['request'], $container, $loader); } - if ($this->isConfigEnabled($container, $config['assets'])) { + if ($this->readConfigEnabled('assets', $container, $config['assets'])) { if (!class_exists(\Symfony\Component\Asset\Package::class)) { throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".'); } @@ -355,15 +344,29 @@ class FrameworkExtension extends Extension $this->registerAssetsConfiguration($config['assets'], $container, $loader); } - if ($this->httpClientConfigEnabled = $this->isConfigEnabled($container, $config['http_client'])) { - $this->registerHttpClientConfiguration($config['http_client'], $container, $loader, $config['profiler']); + if ($this->readConfigEnabled('asset_mapper', $container, $config['asset_mapper'])) { + if (!class_exists(AssetMapper::class)) { + throw new LogicException('AssetMapper support cannot be enabled as the AssetMapper component is not installed. Try running "composer require symfony/asset-mapper".'); + } + + $this->registerAssetMapperConfiguration($config['asset_mapper'], $container, $loader, $this->readConfigEnabled('assets', $container, $config['assets'])); + } else { + $container->removeDefinition('cache.asset_mapper'); } - if ($this->mailerConfigEnabled = $this->isConfigEnabled($container, $config['mailer'])) { - $this->registerMailerConfiguration($config['mailer'], $container, $loader); + if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { + $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); } - $propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']); + if ($this->readConfigEnabled('mailer', $container, $config['mailer'])) { + $this->registerMailerConfiguration($config['mailer'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook'])); + + if (!$this->hasConsole() || !class_exists(MailerTestCommand::class)) { + $container->removeDefinition('console.command.mailer_test'); + } + } + + $propertyInfoEnabled = $this->readConfigEnabled('property_info', $container, $config['property_info']); $this->registerHttpCacheConfiguration($config['http_cache'], $container, $config['http_method_override']); $this->registerEsiConfiguration($config['esi'], $container, $loader); $this->registerSsiConfiguration($config['ssi'], $container, $loader); @@ -371,32 +374,44 @@ class FrameworkExtension extends Extension $this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale'], $config['enabled_locales']); $this->registerWorkflowConfiguration($config['workflows'], $container, $loader); $this->registerDebugConfiguration($config['php_errors'], $container, $loader); - // @deprecated since Symfony 5.4, in 6.0 change to: - // $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']); - $this->registerRouterConfiguration($config['router'], $container, $loader, $config['translator']['enabled_locales'] ?: $config['enabled_locales']); + $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']); $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader); $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader); $this->registerSecretsConfiguration($config['secrets'], $container, $loader); $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); - if ($this->isConfigEnabled($container, $config['serializer'])) { - if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) { + if ($this->readConfigEnabled('serializer', $container, $config['serializer'])) { + if (!class_exists(Serializer::class)) { throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'); } $this->registerSerializerConfiguration($config['serializer'], $container, $loader); + } else { + $container->getDefinition('argument_resolver.request_payload') + ->setArguments([]) + ->addError('You can neither use "#[MapRequestPayload]" nor "#[MapQueryString]" since the Serializer component is not ' + .(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer.enabled" to true.' : 'installed. Try running "composer require symfony/serializer-pack".') + ) + ->addTag('container.error') + ->clearTag('kernel.event_subscriber'); + + $container->removeDefinition('console.command.serializer_debug'); } if ($propertyInfoEnabled) { $this->registerPropertyInfoConfiguration($container, $loader); } - if (self::$lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) { + if ($this->readConfigEnabled('lock', $container, $config['lock'])) { $this->registerLockConfiguration($config['lock'], $container, $loader); } - if ($this->isConfigEnabled($container, $config['rate_limiter'])) { + if ($this->readConfigEnabled('semaphore', $container, $config['semaphore'])) { + $this->registerSemaphoreConfiguration($config['semaphore'], $container, $loader); + } + + if ($this->readConfigEnabled('rate_limiter', $container, $config['rate_limiter'])) { if (!interface_exists(LimiterInterface::class)) { throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); } @@ -404,7 +419,7 @@ class FrameworkExtension extends Extension $this->registerRateLimiterConfiguration($config['rate_limiter'], $container, $loader); } - if ($this->isConfigEnabled($container, $config['web_link'])) { + if ($this->readConfigEnabled('web_link', $container, $config['web_link'])) { if (!class_exists(HttpHeaderSerializer::class)) { throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".'); } @@ -412,23 +427,24 @@ class FrameworkExtension extends Extension $loader->load('web_link.php'); } - if ($this->isConfigEnabled($container, $config['uid'])) { + if ($this->readConfigEnabled('uid', $container, $config['uid'])) { if (!class_exists(UuidFactory::class)) { throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".'); } $this->registerUidConfiguration($config['uid'], $container, $loader); + } else { + $container->removeDefinition('argument_resolver.uid'); } // register cache before session so both can share the connection services $this->registerCacheConfiguration($config['cache'], $container); - if ($this->isConfigEnabled($container, $config['session'])) { + if ($this->readConfigEnabled('session', $container, $config['session'])) { if (!\extension_loaded('session')) { throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.'); } - $this->sessionConfigEnabled = true; $this->registerSessionConfiguration($config['session'], $container, $loader); if (!empty($config['test'])) { // test listener will replace the existing session listener @@ -441,27 +457,29 @@ class FrameworkExtension extends Extension // csrf depends on session being registered if (null === $config['csrf_protection']['enabled']) { - $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle'], true); + $this->writeConfigEnabled('csrf_protection', $this->readConfigEnabled('session', $container, $config['session']) && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle']), $config['csrf_protection']); } $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); // form depends on csrf being registered - if ($this->isConfigEnabled($container, $config['form'])) { + if ($this->readConfigEnabled('form', $container, $config['form'])) { if (!class_exists(Form::class)) { throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".'); } - $this->formConfigEnabled = true; $this->registerFormConfiguration($config, $container, $loader); - if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'], true)) { - $config['validation']['enabled'] = true; + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'])) { + $this->writeConfigEnabled('validation', true, $config['validation']); } else { $container->setParameter('validator.translation_domain', 'validators'); $container->removeDefinition('form.type_extension.form.validator'); $container->removeDefinition('form.type_guesser.validator'); } + if (!$this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer']) || !class_exists(TextTypeHtmlSanitizerExtension::class)) { + $container->removeDefinition('form.type_extension.form.html_sanitizer'); + } } else { $container->removeDefinition('console.command.form_debug'); } @@ -469,11 +487,24 @@ class FrameworkExtension extends Extension // validation depends on form, annotations being registered $this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled); + $messengerEnabled = $this->readConfigEnabled('messenger', $container, $config['messenger']); + + if ($this->readConfigEnabled('scheduler', $container, $config['scheduler'])) { + if (!$messengerEnabled) { + throw new LogicException('Scheduler support cannot be enabled as the Messenger component is not '.(interface_exists(MessageBusInterface::class) ? 'enabled.' : 'installed. Try running "composer require symfony/messenger".')); + } + $this->registerSchedulerConfiguration($config['scheduler'], $container, $loader); + } else { + $container->removeDefinition('cache.scheduler'); + $container->removeDefinition('console.command.scheduler_debug'); + } + // messenger depends on validation being registered - if ($this->messengerConfigEnabled = $this->isConfigEnabled($container, $config['messenger'])) { - $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['validation']); + if ($messengerEnabled) { + $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $this->readConfigEnabled('validation', $container, $config['validation'])); } else { $container->removeDefinition('console.command.messenger_consume_messages'); + $container->removeDefinition('console.command.messenger_stats'); $container->removeDefinition('console.command.messenger_debug'); $container->removeDefinition('console.command.messenger_stop_workers'); $container->removeDefinition('console.command.messenger_setup_transports'); @@ -482,7 +513,7 @@ class FrameworkExtension extends Extension $container->removeDefinition('console.command.messenger_failed_messages_remove'); $container->removeDefinition('cache.messenger.restart_workers_signal'); - if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(AmqpTransportFactory::class)) { + if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(MessengerBridge\Amqp\Transport\AmqpTransportFactory::class)) { if (class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) { $container->getDefinition('messenger.transport.amqp.factory') ->setClass(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class) @@ -492,7 +523,7 @@ class FrameworkExtension extends Extension } } - if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(RedisTransportFactory::class)) { + if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(MessengerBridge\Redis\Transport\RedisTransportFactory::class)) { if (class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) { $container->getDefinition('messenger.transport.redis.factory') ->setClass(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class) @@ -504,27 +535,63 @@ class FrameworkExtension extends Extension } // notifier depends on messenger, mailer being registered - if ($this->notifierConfigEnabled = $this->isConfigEnabled($container, $config['notifier'])) { - $this->registerNotifierConfiguration($config['notifier'], $container, $loader); + if ($this->readConfigEnabled('notifier', $container, $config['notifier'])) { + $this->registerNotifierConfiguration($config['notifier'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook'])); } - // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier being registered + // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered $this->registerProfilerConfiguration($config['profiler'], $container, $loader); + if ($this->readConfigEnabled('webhook', $container, $config['webhook'])) { + $this->registerWebhookConfiguration($config['webhook'], $container, $loader); + + // If Webhook is installed but the HttpClient or Serializer components are not available, we should throw an error + if (!$this->readConfigEnabled('http_client', $container, $config['http_client'])) { + $container->getDefinition('webhook.transport') + ->setArguments([]) + ->addError('You cannot use the "webhook transport" service since the HttpClient component is not ' + .(class_exists(ScopingHttpClient::class) ? 'enabled. Try setting "framework.http_client.enabled" to true.' : 'installed. Try running "composer require symfony/http-client".') + ) + ->addTag('container.error'); + } + if (!$this->readConfigEnabled('serializer', $container, $config['serializer'])) { + $container->getDefinition('webhook.body_configurator.json') + ->setArguments([]) + ->addError('You cannot use the "webhook transport" service since the Serializer component is not ' + .(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer.enabled" to true.' : 'installed. Try running "composer require symfony/serializer-pack".') + ) + ->addTag('container.error'); + } + } + + if ($this->readConfigEnabled('remote-event', $container, $config['remote-event'])) { + $this->registerRemoteEventConfiguration($config['remote-event'], $container, $loader); + } + + if ($this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer'])) { + if (!class_exists(HtmlSanitizerConfig::class)) { + throw new LogicException('HtmlSanitizer support cannot be enabled as the HtmlSanitizer component is not installed. Try running "composer require symfony/html-sanitizer".'); + } + + $this->registerHtmlSanitizerConfiguration($config['html_sanitizer'], $container, $loader); + } + $this->addAnnotatedClassesToCompile([ '**\\Controller\\', '**\\Entity\\', // Added explicitly so that we don't rely on the class map being dumped to make it work - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController', + AbstractController::class, ]); - if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'])) { $loader->load('mime_type.php'); } $container->registerForAutoconfiguration(PackageInterface::class) ->addTag('assets.package'); + $container->registerForAutoconfiguration(AssetCompilerInterface::class) + ->addTag('asset_mapper.compiler'); $container->registerForAutoconfiguration(Command::class) ->addTag('console.command'); $container->registerForAutoconfiguration(ResourceCheckerInterface::class) @@ -541,6 +608,8 @@ class FrameworkExtension extends Extension ->addTag('container.service_subscriber'); $container->registerForAutoconfiguration(ArgumentValueResolverInterface::class) ->addTag('controller.argument_value_resolver'); + $container->registerForAutoconfiguration(ValueResolverInterface::class) + ->addTag('controller.argument_value_resolver'); $container->registerForAutoconfiguration(AbstractController::class) ->addTag('controller.service_arguments'); $container->registerForAutoconfiguration(DataCollectorInterface::class) @@ -589,20 +658,22 @@ class FrameworkExtension extends Extension ->addTag('serializer.normalizer'); $container->registerForAutoconfiguration(ConstraintValidatorInterface::class) ->addTag('validator.constraint_validator'); + $container->registerForAutoconfiguration(GroupProviderInterface::class) + ->addTag('validator.group_provider'); $container->registerForAutoconfiguration(ObjectInitializerInterface::class) ->addTag('validator.initializer'); $container->registerForAutoconfiguration(MessageHandlerInterface::class) ->addTag('messenger.message_handler'); $container->registerForAutoconfiguration(BatchHandlerInterface::class) ->addTag('messenger.message_handler'); - $container->registerForAutoconfiguration(TransportFactoryInterface::class) + $container->registerForAutoconfiguration(MessengerTransportFactoryInterface::class) ->addTag('messenger.transport_factory'); $container->registerForAutoconfiguration(MimeTypeGuesserInterface::class) ->addTag('mime.mime_type_guesser'); $container->registerForAutoconfiguration(LoggerAwareInterface::class) ->addMethodCall('setLogger', [new Reference('logger')]); - $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \Reflector $reflector) { + $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) { $tagAttributes = get_object_vars($attribute); if ($reflector instanceof \ReflectionMethod) { if (isset($tagAttributes['method'])) { @@ -615,13 +686,47 @@ class FrameworkExtension extends Extension $container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void { $definition->addTag('controller.service_arguments'); }); - $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute): void { + $container->registerAttributeForAutoconfiguration(AsRemoteEventConsumer::class, static function (ChildDefinition $definition, AsRemoteEventConsumer $attribute): void { + $definition->addTag('remote_event.consumer', ['consumer' => $attribute->name]); + }); + $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { $tagAttributes = get_object_vars($attribute); $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; unset($tagAttributes['fromTransport']); - + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('AsMessageHandler attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); + } + $tagAttributes['method'] = $reflector->getName(); + } $definition->addTag('messenger.message_handler', $tagAttributes); }); + $container->registerAttributeForAutoconfiguration(AsTargetedValueResolver::class, static function (ChildDefinition $definition, AsTargetedValueResolver $attribute): void { + $definition->addTag('controller.targeted_value_resolver', $attribute->name ? ['name' => $attribute->name] : []); + }); + $container->registerAttributeForAutoconfiguration(AsSchedule::class, static function (ChildDefinition $definition, AsSchedule $attribute): void { + $definition->addTag('scheduler.schedule_provider', ['name' => $attribute->name]); + }); + foreach ([AsPeriodicTask::class, AsCronTask::class] as $taskAttributeClass) { + $container->registerAttributeForAutoconfiguration( + $taskAttributeClass, + static function (ChildDefinition $definition, AsPeriodicTask|AsCronTask $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { + $tagAttributes = get_object_vars($attribute) + [ + 'trigger' => match ($attribute::class) { + AsPeriodicTask::class => 'every', + AsCronTask::class => 'cron', + }, + ]; + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('"%s" attribute cannot declare a method on "%s::%s()".', $attribute::class, $reflector->class, $reflector->name)); + } + $tagAttributes['method'] = $reflector->getName(); + } + $definition->addTag('scheduler.task', $tagAttributes); + } + ); + } if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers @@ -647,10 +752,7 @@ class FrameworkExtension extends Extension ]); } - /** - * {@inheritdoc} - */ - public function getConfiguration(array $config, ContainerBuilder $container) + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface { return new Configuration($container->getParameter('kernel.debug')); } @@ -660,17 +762,15 @@ class FrameworkExtension extends Extension return class_exists(Application::class); } - private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('form.php'); - $container->getDefinition('form.type_extension.form.validator')->setArgument(1, $config['form']['legacy_error_messages']); - if (null === $config['form']['csrf_protection']['enabled']) { - $config['form']['csrf_protection']['enabled'] = $config['csrf_protection']['enabled']; + $this->writeConfigEnabled('form.csrf_protection', $config['csrf_protection']['enabled'], $config['form']['csrf_protection']); } - if ($this->isConfigEnabled($container, $config['form']['csrf_protection'])) { + if ($this->readConfigEnabled('form.csrf_protection', $container, $config['form']['csrf_protection'])) { if (!$container->hasDefinition('security.csrf.token_generator')) { throw new \LogicException('To use form CSRF protection, "framework.csrf_protection" must be enabled.'); } @@ -683,17 +783,12 @@ class FrameworkExtension extends Extension $container->setParameter('form.type_extension.csrf.enabled', false); } - if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'], true)) { + if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'])) { $container->removeDefinition('form.type_extension.upload.validator'); } - if (!method_exists(CachingFactoryDecorator::class, 'reset')) { - $container->getDefinition('form.choice_list_factory.cached') - ->clearTag('kernel.reset') - ; - } } - private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride) + private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride): void { $options = $config; unset($options['enabled']); @@ -702,6 +797,10 @@ class FrameworkExtension extends Extension unset($options['private_headers']); } + if (!$options['skip_response_headers']) { + unset($options['skip_response_headers']); + } + $container->getDefinition('http_cache') ->setPublic($config['enabled']) ->replaceArgument(3, $options); @@ -714,9 +813,9 @@ class FrameworkExtension extends Extension } } - private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('esi', $container, $config)) { $container->removeDefinition('fragment.renderer.esi'); return; @@ -725,9 +824,9 @@ class FrameworkExtension extends Extension $loader->load('esi.php'); } - private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('ssi', $container, $config)) { $container->removeDefinition('fragment.renderer.ssi'); return; @@ -736,9 +835,9 @@ class FrameworkExtension extends Extension $loader->load('ssi.php'); } - private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('fragments', $container, $config)) { $container->removeDefinition('fragment.renderer.hinclude'); return; @@ -750,9 +849,9 @@ class FrameworkExtension extends Extension $container->setParameter('fragment.path', $config['path']); } - private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('profiler', $container, $config)) { // this is needed for the WebProfiler to work even if the profiler is disabled $container->setParameter('data_collector.templates', []); @@ -763,38 +862,46 @@ class FrameworkExtension extends Extension $loader->load('collectors.php'); $loader->load('cache_debug.php'); - if ($this->formConfigEnabled) { + if ($this->isInitializedConfigEnabled('form')) { $loader->load('form_debug.php'); } - if ($this->validatorConfigEnabled) { + if ($this->isInitializedConfigEnabled('validation')) { $loader->load('validator_debug.php'); } - if ($this->translationConfigEnabled) { + if ($this->isInitializedConfigEnabled('translator')) { $loader->load('translation_debug.php'); $container->getDefinition('translator.data_collector')->setDecoratedService('translator'); } - if ($this->messengerConfigEnabled) { + if ($this->isInitializedConfigEnabled('messenger')) { $loader->load('messenger_debug.php'); } - if ($this->mailerConfigEnabled) { + if ($this->isInitializedConfigEnabled('mailer')) { $loader->load('mailer_debug.php'); } - if ($this->httpClientConfigEnabled) { + if ($this->isInitializedConfigEnabled('workflows')) { + $loader->load('workflow_debug.php'); + } + + if ($this->isInitializedConfigEnabled('http_client')) { $loader->load('http_client_debug.php'); } - if ($this->notifierConfigEnabled) { + if ($this->isInitializedConfigEnabled('notifier')) { $loader->load('notifier_debug.php'); } + if ($this->isInitializedConfigEnabled('serializer') && $config['collect_serializer_data']) { + $loader->load('serializer_debug.php'); + } + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); - $container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests'] || $config['only_master_requests']); + $container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests']); // Choose storage class based on the DSN [$class] = explode(':', $config['dsn'], 2); @@ -810,9 +917,17 @@ class FrameworkExtension extends Extension $container->getDefinition('profiler_listener') ->addArgument($config['collect_parameter']); + + if (!$container->getParameter('kernel.debug') || !class_exists(CliRequest::class) || !$container->has('debug.stopwatch')) { + $container->removeDefinition('console_profiler_listener'); + } + + if (!class_exists(CommandDataCollector::class)) { + $container->removeDefinition('.data_collector.command'); + } } - private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$config['enabled']) { $container->removeDefinition('console.command.workflow_dump'); @@ -828,8 +943,6 @@ class FrameworkExtension extends Extension $registryDefinition = $container->getDefinition('workflow.registry'); - $workflows = []; - foreach ($config['workflows'] as $name => $workflow) { $type = $workflow['type']; $workflowId = sprintf('%s.%s', $type, $name); @@ -858,7 +971,6 @@ class FrameworkExtension extends Extension foreach ($workflow['transitions'] as $transition) { if ('workflow' === $type) { $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $transition['from'], $transition['to']]); - $transitionDefinition->setPublic(false); $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); $container->setDefinition($transitionId, $transitionDefinition); $transitions[] = new Reference($transitionId); @@ -866,7 +978,6 @@ class FrameworkExtension extends Extension $configuration = new Definition(Workflow\EventListener\GuardExpression::class); $configuration->addArgument(new Reference($transitionId)); $configuration->addArgument($transition['guard']); - $configuration->setPublic(false); $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); $guardsConfiguration[$eventName][] = $configuration; } @@ -880,7 +991,6 @@ class FrameworkExtension extends Extension foreach ($transition['from'] as $from) { foreach ($transition['to'] as $to) { $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $from, $to]); - $transitionDefinition->setPublic(false); $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); $container->setDefinition($transitionId, $transitionDefinition); $transitions[] = new Reference($transitionId); @@ -888,7 +998,6 @@ class FrameworkExtension extends Extension $configuration = new Definition(Workflow\EventListener\GuardExpression::class); $configuration->addArgument(new Reference($transitionId)); $configuration->addArgument($transition['guard']); - $configuration->setPublic(false); $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); $guardsConfiguration[$eventName][] = $configuration; } @@ -911,20 +1020,18 @@ class FrameworkExtension extends Extension // Create a Definition $definitionDefinition = new Definition(Workflow\Definition::class); - $definitionDefinition->setPublic(false); $definitionDefinition->addArgument($places); $definitionDefinition->addArgument($transitions); $definitionDefinition->addArgument($initialMarking); $definitionDefinition->addArgument(new Reference(sprintf('%s.metadata_store', $workflowId))); - $workflows[$workflowId] = $definitionDefinition; - // Create MarkingStore - if (isset($workflow['marking_store']['type'])) { + $markingStoreDefinition = null; + if (isset($workflow['marking_store']['type']) || isset($workflow['marking_store']['property'])) { $markingStoreDefinition = new ChildDefinition('workflow.marking_store.method'); $markingStoreDefinition->setArguments([ 'state_machine' === $type, // single state - $workflow['marking_store']['property'], + $workflow['marking_store']['property'] ?? 'marking', ]); } elseif (isset($workflow['marking_store']['service'])) { $markingStoreDefinition = new Reference($workflow['marking_store']['service']); @@ -933,18 +1040,22 @@ class FrameworkExtension extends Extension // Create Workflow $workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type)); $workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId))); - $workflowDefinition->replaceArgument(1, $markingStoreDefinition ?? null); + $workflowDefinition->replaceArgument(1, $markingStoreDefinition); $workflowDefinition->replaceArgument(3, $name); $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); - $workflowDefinition->addTag('container.private', [ - 'package' => 'symfony/framework-bundle', - 'version' => '5.3', - ]); + + $workflowDefinition->addTag('workflow', ['name' => $name]); + if ('workflow' === $type) { + $workflowDefinition->addTag('workflow.workflow', ['name' => $name]); + } elseif ('state_machine' === $type) { + $workflowDefinition->addTag('workflow.state_machine', ['name' => $name]); + } // Store to container $container->setDefinition($workflowId, $workflowDefinition); $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition); $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type); + $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name); // Validate Workflow if ('state_machine' === $workflow['type']) { @@ -953,9 +1064,7 @@ class FrameworkExtension extends Extension $validator = new Workflow\Validator\WorkflowValidator(); } - $trs = array_map(function (Reference $ref) use ($container): Workflow\Transition { - return $container->get((string) $ref); - }, $transitions); + $trs = array_map(fn (Reference $ref): Workflow\Transition => $container->get((string) $ref), $transitions); $realDefinition = new Workflow\Definition($places, $trs, $initialMarking); $validator->validate($realDefinition, $name); @@ -963,7 +1072,6 @@ class FrameworkExtension extends Extension if ($workflow['supports']) { foreach ($workflow['supports'] as $supportedClassName) { $strategyDefinition = new Definition(Workflow\SupportStrategy\InstanceOfSupportStrategy::class, [$supportedClassName]); - $strategyDefinition->setPublic(false); $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), $strategyDefinition]); } } elseif (isset($workflow['support_strategy'])) { @@ -987,7 +1095,7 @@ class FrameworkExtension extends Extension throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } - if (!class_exists(Security::class)) { + if (!class_exists(AuthenticationEvents::class)) { throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".'); } @@ -1011,24 +1119,45 @@ class FrameworkExtension extends Extension } } - $commandDumpDefinition = $container->getDefinition('console.command.workflow_dump'); - $commandDumpDefinition->setArgument(0, $workflows); + $listenerAttributes = [ + Workflow\Attribute\AsAnnounceListener::class, + Workflow\Attribute\AsCompletedListener::class, + Workflow\Attribute\AsEnterListener::class, + Workflow\Attribute\AsEnteredListener::class, + Workflow\Attribute\AsGuardListener::class, + Workflow\Attribute\AsLeaveListener::class, + Workflow\Attribute\AsTransitionListener::class, + ]; + + foreach ($listenerAttributes as $attribute) { + $container->registerAttributeForAutoconfiguration($attribute, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) { + $tagAttributes = get_object_vars($attribute); + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('"%s" attribute cannot declare a method on "%s::%s()".', $attribute::class, $reflector->class, $reflector->name)); + } + $tagAttributes['method'] = $reflector->getName(); + } + $definition->addTag('kernel.event_listener', $tagAttributes); + }); + } } - private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('debug_prod.php'); + $debug = $container->getParameter('kernel.debug'); + if (class_exists(Stopwatch::class)) { $container->register('debug.stopwatch', Stopwatch::class) ->addArgument(true) + ->setPublic($debug) ->addTag('kernel.reset', ['method' => 'reset']); $container->setAlias(Stopwatch::class, new Alias('debug.stopwatch', false)); } - $debug = $container->getParameter('kernel.debug'); - - if ($debug) { + if ($debug && !$container->hasParameter('debug.container.dump')) { $container->setParameter('debug.container.dump', '%kernel.build_dir%/%kernel.container_class%.xml'); } @@ -1036,12 +1165,12 @@ class FrameworkExtension extends Extension $loader->load('debug.php'); } - $definition = $container->findDefinition('debug.debug_handlers_listener'); + $definition = $container->findDefinition('debug.error_handler_configurator'); if (false === $config['log']) { - $definition->replaceArgument(1, null); + $definition->replaceArgument(0, null); } elseif (true !== $config['log']) { - $definition->replaceArgument(2, $config['log']); + $definition->replaceArgument(1, $config['log']); } if (!$config['throw']) { @@ -1050,15 +1179,18 @@ class FrameworkExtension extends Extension if ($debug && class_exists(DebugProcessor::class)) { $definition = new Definition(DebugProcessor::class); - $definition->setPublic(false); - $definition->addArgument(new Reference('request_stack')); + $definition->addArgument(new Reference('.virtual_request_stack')); + $definition->addTag('kernel.reset', ['method' => 'reset']); $container->setDefinition('debug.log_processor', $definition); + + $container->register('debug.debug_logger_configurator', DebugLoggerConfigurator::class) + ->setArguments([new Reference('debug.log_processor'), '%kernel.runtime_mode.web%']); } } - private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []) + private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('router', $container, $config)) { $container->removeDefinition('console.command.router_debug'); $container->removeDefinition('console.command.router_match'); $container->removeDefinition('messenger.middleware.router_context'); @@ -1071,10 +1203,6 @@ class FrameworkExtension extends Extension $loader->load('routing.php'); - if (null === $config['utf8']) { - trigger_deprecation('symfony/framework-bundle', '5.1', 'Not setting the "framework.router.utf8" configuration option is deprecated, it will default to "true" in version 6.0.'); - } - if ($config['utf8']) { $container->getDefinition('routing.loader')->replaceArgument(1, ['utf8' => true]); } @@ -1084,11 +1212,12 @@ class FrameworkExtension extends Extension $container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]); } - if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'], true)) { + if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'])) { $container->removeDefinition('router.expression_language_provider'); } $container->setParameter('router.resource', $config['resource']); + $container->setParameter('router.cache_dir', $config['cache_dir']); $router = $container->findDefinition('router.default'); $argument = $router->getArgument(2); $argument['strict_requirements'] = $config['strict_requirements']; @@ -1105,54 +1234,20 @@ class FrameworkExtension extends Extension ->replaceArgument(0, $config['default_uri']); } - if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) { - return; - } - - $container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), + if ($this->isInitializedConfigEnabled('annotations') && (new \ReflectionClass(AttributeClassLoader::class))->hasProperty('reader')) { + $container->getDefinition('routing.loader.attribute')->setArguments([ + new Reference('annotation_reader'), '%kernel.environment%', ]); - - $container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('file_locator'), - new Reference('routing.loader.annotation'), - ]); - - $container->register('routing.loader.annotation.file', AnnotationFileLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('file_locator'), - new Reference('routing.loader.annotation'), - ]); + } } - private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('session.php'); // session storage - if (null === $config['storage_factory_id']) { - trigger_deprecation('symfony/framework-bundle', '5.3', 'Not setting the "framework.session.storage_factory_id" configuration option is deprecated, it will default to "session.storage.factory.native" and will replace the "framework.session.storage_id" configuration option in version 6.0.'); - $container->setAlias('session.storage', $config['storage_id']); - $container->setAlias('session.storage.factory', 'session.storage.factory.service'); - } else { - $container->setAlias('session.storage.factory', $config['storage_factory_id']); - - $container->removeAlias(SessionStorageInterface::class); - $container->removeDefinition('session.storage.metadata_bag'); - $container->removeDefinition('session.storage.native'); - $container->removeDefinition('session.storage.php_bridge'); - $container->removeDefinition('session.storage.mock_file'); - $container->removeAlias('session.storage.filesystem'); - } + $container->setAlias('session.storage.factory', $config['storage_factory_id']); $options = ['cache_limiter' => '0']; foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'sid_length', 'sid_bits_per_character'] as $key) { @@ -1162,32 +1257,16 @@ class FrameworkExtension extends Extension } if ('auto' === ($options['cookie_secure'] ?? null)) { - if (null === $config['storage_factory_id']) { - $locator = $container->getDefinition('session_listener')->getArgument(0); - $locator->setValues($locator->getValues() + [ - 'session_storage' => new Reference('session.storage', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), - 'request_stack' => new Reference('request_stack'), - ]); - } else { - $container->getDefinition('session.storage.factory.native')->replaceArgument(3, true); - $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(2, true); - } + $container->getDefinition('session.storage.factory.native')->replaceArgument(3, true); + $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(2, true); } $container->setParameter('session.storage.options', $options); // session handler (the internal callback registered with PHP session management) if (null === $config['handler_id']) { - // Set the handler class to be null - if ($container->hasDefinition('session.storage.native')) { - $container->getDefinition('session.storage.native')->replaceArgument(1, null); - $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); - } else { - $container->getDefinition('session.storage.factory.native')->replaceArgument(1, null); - $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(0, null); - } - - $container->setAlias('session.handler', 'session.handler.native_file'); + $config['save_path'] = null; + $container->setAlias('session.handler', 'session.handler.native'); } else { $container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs); @@ -1208,7 +1287,7 @@ class FrameworkExtension extends Extension $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']); } - private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if ($config['formats']) { $loader->load('request.php'); @@ -1218,7 +1297,7 @@ class FrameworkExtension extends Extension } } - private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('assets.php'); @@ -1251,6 +1330,77 @@ class FrameworkExtension extends Extension } } + private function registerAssetMapperConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $assetEnabled): void + { + $loader->load('asset_mapper.php'); + + if (!$assetEnabled) { + $container->removeDefinition('asset_mapper.asset_package'); + } + + $paths = $config['paths']; + foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { + if ($container->fileExists($dir = $bundle['path'].'/Resources/public') || $container->fileExists($dir = $bundle['path'].'/public')) { + $paths[$dir] = sprintf('bundles/%s', preg_replace('/bundle$/', '', strtolower($name))); + } + } + $excludedPathPatterns = []; + foreach ($config['excluded_patterns'] as $path) { + $excludedPathPatterns[] = Glob::toRegex($path, true, false); + } + + $container->getDefinition('asset_mapper.repository') + ->setArgument(0, $paths) + ->setArgument(2, $excludedPathPatterns) + ->setArgument(3, $config['exclude_dotfiles']); + + $container->getDefinition('asset_mapper.public_assets_path_resolver') + ->setArgument(0, $config['public_prefix']); + + $publicDirectory = $this->getPublicDirectory($container); + $publicAssetsDirectory = rtrim($publicDirectory.'/'.ltrim($config['public_prefix'], '/'), '/'); + $container->getDefinition('asset_mapper.local_public_assets_filesystem') + ->setArgument(0, $publicDirectory) + ; + + $container->getDefinition('asset_mapper.compiled_asset_mapper_config_reader') + ->setArgument(0, $publicAssetsDirectory); + + if (!$config['server']) { + $container->removeDefinition('asset_mapper.dev_server_subscriber'); + } else { + $container->getDefinition('asset_mapper.dev_server_subscriber') + ->setArgument(1, $config['public_prefix']) + ->setArgument(2, $config['extensions']); + } + + $container->getDefinition('asset_mapper.compiler.css_asset_url_compiler') + ->setArgument(0, $config['missing_import_mode']); + + $container->getDefinition('asset_mapper.compiler.javascript_import_path_compiler') + ->setArgument(1, $config['missing_import_mode']); + + $container + ->getDefinition('asset_mapper.importmap.remote_package_storage') + ->replaceArgument(0, $config['vendor_dir']) + ; + $container + ->getDefinition('asset_mapper.mapped_asset_factory') + ->replaceArgument(2, $config['vendor_dir']) + ; + + $container + ->getDefinition('asset_mapper.importmap.config_reader') + ->replaceArgument(0, $config['importmap_path']) + ; + + $container + ->getDefinition('asset_mapper.importmap.renderer') + ->replaceArgument(3, $config['importmap_polyfill']) + ->replaceArgument(4, $config['importmap_script_attributes']) + ; + } + /** * Returns a definition for an asset package. */ @@ -1262,7 +1412,6 @@ class FrameworkExtension extends Extension $package = new ChildDefinition($baseUrls ? 'assets.url_package' : 'assets.path_package'); $package - ->setPublic(false) ->replaceArgument(0, $baseUrls ?: $basePath) ->replaceArgument(1, $version) ; @@ -1296,9 +1445,9 @@ class FrameworkExtension extends Extension return new Reference('assets.empty_version_strategy'); } - private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales) + private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('translator', $container, $config)) { $container->removeDefinition('console.command.translation_debug'); $container->removeDefinition('console.command.translation_extract'); $container->removeDefinition('console.command.translation_pull'); @@ -1308,6 +1457,18 @@ class FrameworkExtension extends Extension } $loader->load('translation.php'); + + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleSwitcher::class, ['symfony/framework-bundle'])) { + $container->removeDefinition('translation.locale_switcher'); + } + + // don't use ContainerBuilder::willBeAvailable() as these are not needed in production + if (interface_exists(Parser::class) && class_exists(PhpAstExtractor::class)) { + $container->removeDefinition('translation.extractor.php'); + } else { + $container->removeDefinition('translation.extractor.php_ast'); + } + $loader->load('translation_providers.php'); // Use the "real" translator instead of the identity default @@ -1319,10 +1480,7 @@ class FrameworkExtension extends Extension $defaultOptions = $translator->getArgument(4); $defaultOptions['cache_dir'] = $config['cache_dir']; $translator->setArgument(4, $defaultOptions); - - // @deprecated since Symfony 5.4, in 6.0 change to: - // $translator->setArgument(5, $enabledLocales); - $translator->setArgument(5, $config['enabled_locales'] ?: $enabledLocales); + $translator->setArgument(5, $enabledLocales); $container->setParameter('translator.logging', $config['logging']); $container->setParameter('translator.default_path', $config['default_path']); @@ -1331,17 +1489,17 @@ class FrameworkExtension extends Extension $dirs = []; $transPaths = []; $nonExistingDirs = []; - if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'])) { $r = new \ReflectionClass(Validation::class); $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } - if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'])) { $r = new \ReflectionClass(Form::class); $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } - if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'])) { $r = new \ReflectionClass(AuthenticationException::class); $dirs[] = $transPaths[] = \dirname($r->getFileName(), 2).'/Resources/translations'; @@ -1387,9 +1545,7 @@ class FrameworkExtension extends Extension $finder = Finder::create() ->followLinks() ->files() - ->filter(function (\SplFileInfo $file) { - return 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename()); - }) + ->filter(fn (\SplFileInfo $file) => 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename())) ->in($dir) ->sortByName() ; @@ -1412,9 +1568,7 @@ class FrameworkExtension extends Extension 'resource_files' => $files, 'scanned_directories' => $scannedDirectories = array_merge($dirs, $nonExistingDirs), 'cache_vary' => [ - 'scanned_directories' => array_map(static function (string $dir) use ($projectDir): string { - return str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir; - }, $scannedDirectories), + 'scanned_directories' => array_map(fn ($dir) => str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir, $scannedDirectories), ], ] ); @@ -1436,9 +1590,10 @@ class FrameworkExtension extends Extension } $classToServices = [ - CrowdinProviderFactory::class => 'translation.provider_factory.crowdin', - LocoProviderFactory::class => 'translation.provider_factory.loco', - LokaliseProviderFactory::class => 'translation.provider_factory.lokalise', + TranslationBridge\Crowdin\CrowdinProviderFactory::class => 'translation.provider_factory.crowdin', + TranslationBridge\Loco\LocoProviderFactory::class => 'translation.provider_factory.loco', + TranslationBridge\Lokalise\LokaliseProviderFactory::class => 'translation.provider_factory.lokalise', + TranslationBridge\Phrase\PhraseProviderFactory::class => 'translation.provider_factory.phrase', ]; $parentPackages = ['symfony/framework-bundle', 'symfony/translation', 'symfony/http-client']; @@ -1446,7 +1601,7 @@ class FrameworkExtension extends Extension foreach ($classToServices as $class => $service) { $package = substr($service, \strlen('translation.provider_factory.')); - if (!$container->hasDefinition('http_client') || !ContainerBuilder::willBeAvailable(sprintf('symfony/%s-translation-provider', $package), $class, $parentPackages, true)) { + if (!$container->hasDefinition('http_client') || !ContainerBuilder::willBeAvailable(sprintf('symfony/%s-translation-provider', $package), $class, $parentPackages)) { $container->removeDefinition($service); } } @@ -1455,9 +1610,7 @@ class FrameworkExtension extends Extension return; } - // @deprecated since Symfony 5.4, in 6.0 change to: - // $locales = $enabledLocales; - $locales = $config['enabled_locales'] ?: $enabledLocales; + $locales = $enabledLocales; foreach ($config['providers'] as $provider) { if ($provider['locales']) { @@ -1484,9 +1637,9 @@ class FrameworkExtension extends Extension $container->getDefinition('translation.provider_collection')->setArgument(0, $config['providers']); } - private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled) + private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled): void { - if (!$this->validatorConfigEnabled = $this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('validation', $container, $config)) { $container->removeDefinition('console.command.validator_debug'); return; @@ -1520,13 +1673,9 @@ class FrameworkExtension extends Extension $definition = $container->findDefinition('validator.email'); $definition->replaceArgument(0, $config['email_validation_mode']); - if (\array_key_exists('enable_annotations', $config) && $config['enable_annotations']) { - if (!$this->annotationsConfigEnabled && \PHP_VERSION_ID < 80000) { - throw new \LogicException('"enable_annotations" on the validator cannot be set as the PHP version is lower than 8 and Doctrine Annotations support is disabled. Consider upgrading PHP.'); - } - - $validatorBuilder->addMethodCall('enableAnnotationMapping', [true]); - if ($this->annotationsConfigEnabled) { + if (\array_key_exists('enable_attributes', $config) && $config['enable_attributes']) { + $validatorBuilder->addMethodCall('enableAttributeMapping', [true]); + if ($this->isInitializedConfigEnabled('annotations') && method_exists(ValidatorBuilder::class, 'setDoctrineAnnotationReader')) { $validatorBuilder->addMethodCall('setDoctrineAnnotationReader', [new Reference('annotation_reader')]); } } @@ -1554,16 +1703,19 @@ class FrameworkExtension extends Extension if (!class_exists(ExpressionLanguage::class)) { $container->removeDefinition('validator.expression_language'); + $container->removeDefinition('validator.expression_language_provider'); + } elseif (!class_exists(ExpressionLanguageProvider::class)) { + $container->removeDefinition('validator.expression_language_provider'); } } - private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files) + private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files): void { $fileRecorder = function ($extension, $path) use (&$files) { $files['yaml' === $extension ? 'yml' : $extension][] = $path; }; - if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'])) { $reflClass = new \ReflectionClass(Form::class); $fileRecorder('xml', \dirname($reflClass->getFileName()).'/Resources/config/validation.xml'); } @@ -1572,8 +1724,8 @@ class FrameworkExtension extends Extension $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config'; if ( - $container->fileExists($file = $configDir.'/validation.yaml', false) || - $container->fileExists($file = $configDir.'/validation.yml', false) + $container->fileExists($file = $configDir.'/validation.yaml', false) + || $container->fileExists($file = $configDir.'/validation.yml', false) ) { $fileRecorder('yml', $file); } @@ -1595,14 +1747,14 @@ class FrameworkExtension extends Extension $this->registerMappingFilesFromConfig($container, $config, $fileRecorder); } - private function registerMappingFilesFromDir(string $dir, callable $fileRecorder) + private function registerMappingFilesFromDir(string $dir, callable $fileRecorder): void { foreach (Finder::create()->followLinks()->files()->in($dir)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) { $fileRecorder($file->getExtension(), $file->getRealPath()); } } - private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder) + private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder): void { foreach ($config['mapping']['paths'] as $path) { if (is_dir($path)) { @@ -1619,9 +1771,9 @@ class FrameworkExtension extends Extension } } - private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader) + private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader): void { - if (!$this->annotationsConfigEnabled) { + if (!$this->isInitializedConfigEnabled('annotations')) { return; } @@ -1629,19 +1781,9 @@ class FrameworkExtension extends Extension throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed. Try running "composer require doctrine/annotations".'); } - $loader->load('annotations.php'); + trigger_deprecation('symfony/framework-bundle', '6.4', 'Enabling the integration of Doctrine annotations is deprecated. Set the "framework.annotations.enabled" config option to false.'); - // registerUniqueLoader exists since doctrine/annotations v1.6 - if (!method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { - // registerLoader exists only in doctrine/annotations v1 - if (method_exists(AnnotationRegistry::class, 'registerLoader')) { - $container->getDefinition('annotations.dummy_registry') - ->setMethodCalls([['registerLoader', ['class_exists']]]); - } else { - // remove the dummy registry when doctrine/annotations v2 is used - $container->removeDefinition('annotations.dummy_registry'); - } - } + $loader->load('annotations.php'); if ('none' === $config['cache']) { $container->removeDefinition('annotations.cached_reader'); @@ -1649,29 +1791,24 @@ class FrameworkExtension extends Extension return; } - $cacheService = $config['cache']; - if (\in_array($config['cache'], ['php_array', 'file'])) { - if ('php_array' === $config['cache']) { - $cacheService = 'annotations.cache_adapter'; + if ('php_array' === $config['cache']) { + $cacheService = 'annotations.cache_adapter'; - // Enable warmer only if PHP array is used for cache - $definition = $container->findDefinition('annotations.cache_warmer'); - $definition->addTag('kernel.cache_warmer'); - } elseif ('file' === $config['cache']) { - $cacheService = 'annotations.filesystem_cache_adapter'; - $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); - - if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { - throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir)); - } - - $container - ->getDefinition('annotations.filesystem_cache_adapter') - ->replaceArgument(2, $cacheDir) - ; - } + // Enable warmer only if PHP array is used for cache + $definition = $container->findDefinition('annotations.cache_warmer'); + $definition->addTag('kernel.cache_warmer'); } else { - trigger_deprecation('symfony/framework-bundle', '5.3', 'Using a custom service for "framework.annotation.cache" is deprecated, only values "none", "php_array" and "file" are valid in version 6.0.'); + $cacheService = 'annotations.filesystem_cache_adapter'; + $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); + + if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { + throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir)); + } + + $container + ->getDefinition('annotations.filesystem_cache_adapter') + ->replaceArgument(2, $cacheDir) + ; } $container @@ -1686,9 +1823,9 @@ class FrameworkExtension extends Extension $container->removeDefinition('annotations.psr_cached_reader'); } - private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { - if (!$this->propertyAccessConfigEnabled = $this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('property_access', $container, $config)) { return; } @@ -1712,9 +1849,9 @@ class FrameworkExtension extends Extension ; } - private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('secrets', $container, $config)) { $container->removeDefinition('console.command.secrets_set'); $container->removeDefinition('console.command.secrets_list'); $container->removeDefinition('console.command.secrets_remove'); @@ -1736,11 +1873,11 @@ class FrameworkExtension extends Extension } if ($config['decryption_env_var']) { - if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $config['decryption_env_var'])) { + if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w++$/', $config['decryption_env_var'])) { throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var'])); } - if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'])) { $container->getDefinition('secrets.decryption_key')->replaceArgument(1, $config['decryption_env_var']); } else { $container->getDefinition('secrets.vault')->replaceArgument(1, "%env({$config['decryption_env_var']})%"); @@ -1752,9 +1889,9 @@ class FrameworkExtension extends Extension } } - private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('csrf_protection', $container, $config)) { return; } @@ -1762,7 +1899,7 @@ class FrameworkExtension extends Extension throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".'); } - if (!$this->sessionConfigEnabled) { + if (!$this->isInitializedConfigEnabled('session')) { throw new \LogicException('CSRF protection needs sessions to be enabled.'); } @@ -1774,16 +1911,13 @@ class FrameworkExtension extends Extension } } - private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('serializer.php'); - if ($container->getParameter('kernel.debug')) { - $container->removeDefinition('serializer.mapping.cache_class_metadata_factory'); - } $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); - if (!$this->propertyAccessConfigEnabled) { + if (!$this->isInitializedConfigEnabled('property_access')) { $container->removeAlias('serializer.property_accessor'); $container->removeDefinition('serializer.normalizer.object'); } @@ -1792,7 +1926,7 @@ class FrameworkExtension extends Extension $container->removeDefinition('serializer.encoder.yaml'); } - if (!class_exists(UnwrappingDenormalizer::class) || !$this->propertyAccessConfigEnabled) { + if (!$this->isInitializedConfigEnabled('property_access')) { $container->removeDefinition('serializer.denormalizer.unwrapping'); } @@ -1800,24 +1934,26 @@ class FrameworkExtension extends Extension $container->removeDefinition('serializer.normalizer.mime_message'); } - $serializerLoaders = []; - if (isset($config['enable_annotations']) && $config['enable_annotations']) { - if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) { - throw new \LogicException('"enable_annotations" on the serializer cannot be set as the PHP version is lower than 8 and Annotations support is disabled. Consider upgrading PHP.'); - } + if ($container->getParameter('kernel.debug')) { + $container->removeDefinition('serializer.mapping.cache_class_metadata_factory'); + } + if (!class_exists(Translator::class)) { + $container->removeDefinition('serializer.normalizer.translatable'); + } + + $serializerLoaders = []; + if (isset($config['enable_attributes']) && $config['enable_attributes']) { $annotationLoader = new Definition( - 'Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', + AttributeLoader::class, [new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)] ); - $annotationLoader->setPublic(false); $serializerLoaders[] = $annotationLoader; } $fileRecorder = function ($extension, $path) use (&$serializerLoaders) { - $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? 'Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader' : 'Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', [$path]); - $definition->setPublic(false); + $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? YamlFileLoader::class : XmlFileLoader::class, [$path]); $serializerLoaders[] = $definition; }; @@ -1829,8 +1965,8 @@ class FrameworkExtension extends Extension } if ( - $container->fileExists($file = $configDir.'/serialization.yaml', false) || - $container->fileExists($file = $configDir.'/serialization.yml', false) + $container->fileExists($file = $configDir.'/serialization.yaml', false) + || $container->fileExists($file = $configDir.'/serialization.yml', false) ) { $fileRecorder('yml', $file); } @@ -1872,7 +2008,7 @@ class FrameworkExtension extends Extension } } - private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader) + private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void { if (!interface_exists(PropertyInfoExtractorInterface::class)) { throw new LogicException('PropertyInfo support cannot be enabled as the PropertyInfo component is not installed. Try running "composer require symfony/property-info".'); @@ -1881,15 +2017,15 @@ class FrameworkExtension extends Extension $loader->load('property_info.php'); if ( - ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/property-info'], true) - && ContainerBuilder::willBeAvailable('phpdocumentor/type-resolver', ContextFactory::class, ['symfony/framework-bundle', 'symfony/property-info'], true) + ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/property-info']) + && ContainerBuilder::willBeAvailable('phpdocumentor/type-resolver', ContextFactory::class, ['symfony/framework-bundle', 'symfony/property-info']) ) { $definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class); $definition->addTag('property_info.type_extractor', ['priority' => -1000]); } if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) { - $definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor'); + $definition = $container->register('property_info.php_doc_extractor', PhpDocExtractor::class); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); } @@ -1899,7 +2035,7 @@ class FrameworkExtension extends Extension } } - private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('lock.php'); @@ -1912,9 +2048,11 @@ class FrameworkExtension extends Extension $storeDefinitions = []; foreach ($resourceStores as $resourceStore) { $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); - $storeDefinition = new Definition(interface_exists(StoreInterface::class) ? StoreInterface::class : PersistingStoreInterface::class); - $storeDefinition->setFactory([StoreFactory::class, 'createStore']); - $storeDefinition->setArguments([$resourceStore]); + $storeDefinition = new Definition(PersistingStoreInterface::class); + $storeDefinition + ->setFactory([StoreFactory::class, 'createStore']) + ->setArguments([$resourceStore]) + ->addTag('lock.store'); $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); @@ -1927,10 +2065,7 @@ class FrameworkExtension extends Extension if (\count($storeDefinitions) > 1) { $combinedDefinition = new ChildDefinition('lock.store.combined.abstract'); $combinedDefinition->replaceArgument(0, $storeDefinitions); - $container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.'.$resourceName.'.factory" instead.'); $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($resourceStores), $combinedDefinition); - } else { - $container->setAlias('lock.'.$resourceName.'.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.'.$resourceName.'.factory" instead.')); } // Generate factories for each resource @@ -1938,57 +2073,109 @@ class FrameworkExtension extends Extension $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId)); $container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition); - // Generate services for lock instances - $lockDefinition = new Definition(Lock::class); - $lockDefinition->setPublic(false); - $lockDefinition->setFactory([new Reference('lock.'.$resourceName.'.factory'), 'createLock']); - $lockDefinition->setArguments([$resourceName]); - $container->setDefinition('lock.'.$resourceName, $lockDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.'.$resourceName.'.factory" instead.'); - // provide alias for default resource if ('default' === $resourceName) { - $container->setAlias('lock.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.')); $container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false)); - $container->setAlias('lock', (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.')); - $container->setAlias(PersistingStoreInterface::class, (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.')); $container->setAlias(LockFactory::class, new Alias('lock.factory', false)); - $container->setAlias(LockInterface::class, (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.')); } else { - $container->registerAliasForArgument($storeDefinitionId, PersistingStoreInterface::class, $resourceName.'.lock.store')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' '.$resourceName.'LockFactory" instead.'); $container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory'); - $container->registerAliasForArgument('lock.'.$resourceName, LockInterface::class, $resourceName.'.lock')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' $'.$resourceName.'LockFactory" instead.'); } } } - private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $validationConfig) + private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('semaphore.php'); + + foreach ($config['resources'] as $resourceName => $resourceStore) { + $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); + $storeDefinition = new Definition(SemaphoreStoreInterface::class); + $storeDefinition->setFactory([SemaphoreStoreFactory::class, 'createStore']); + $storeDefinition->setArguments([$resourceStore]); + + $container->setDefinition($storeDefinitionId = '.semaphore.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); + + // Generate factories for each resource + $factoryDefinition = new ChildDefinition('semaphore.factory.abstract'); + $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId)); + $container->setDefinition('semaphore.'.$resourceName.'.factory', $factoryDefinition); + + // Generate services for semaphore instances + $semaphoreDefinition = new Definition(Semaphore::class); + $semaphoreDefinition->setFactory([new Reference('semaphore.'.$resourceName.'.factory'), 'createSemaphore']); + $semaphoreDefinition->setArguments([$resourceName]); + + // provide alias for default resource + if ('default' === $resourceName) { + $container->setAlias('semaphore.factory', new Alias('semaphore.'.$resourceName.'.factory', false)); + $container->setAlias(SemaphoreFactory::class, new Alias('semaphore.factory', false)); + } else { + $container->registerAliasForArgument('semaphore.'.$resourceName.'.factory', SemaphoreFactory::class, $resourceName.'.semaphore.factory'); + } + } + } + + private function registerSchedulerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(SchedulerTransportFactory::class)) { + throw new LogicException('Scheduler support cannot be enabled as the Scheduler component is not installed. Try running "composer require symfony/scheduler".'); + } + + $loader->load('scheduler.php'); + + if (!$this->hasConsole()) { + $container->removeDefinition('console.command.scheduler_debug'); + } + } + + private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $validationEnabled): void { if (!interface_exists(MessageBusInterface::class)) { throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".'); } + if (!$this->hasConsole()) { + $container->removeDefinition('console.command.messenger_stats'); + } + $loader->load('messenger.php'); if (!interface_exists(DenormalizerInterface::class)) { $container->removeDefinition('serializer.normalizer.flatten_exception'); } - if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', MessengerBridge\Amqp\Transport\AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory'); } - if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', MessengerBridge\Redis\Transport\RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory'); } - if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', MessengerBridge\AmazonSqs\Transport\AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory'); } - if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', MessengerBridge\Beanstalkd\Transport\BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory'); } + if ($config['stop_worker_on_signals'] && $this->hasConsole()) { + $container->getDefinition('console.command.messenger_consume_messages') + ->replaceArgument(8, $config['stop_worker_on_signals']); + $container->getDefinition('console.command.messenger_failed_messages_retry') + ->replaceArgument(6, $config['stop_worker_on_signals']); + } + + if ($this->hasConsole() && $container->hasDefinition('messenger.listener.stop_worker_signals_listener')) { + $container->getDefinition('messenger.listener.stop_worker_signals_listener')->clearTag('kernel.event_subscriber'); + } + if (!class_exists(StopWorkerOnSignalsListener::class)) { + $container->removeDefinition('messenger.listener.stop_worker_signals_listener'); + } elseif ($config['stop_worker_on_signals']) { + $container->getDefinition('messenger.listener.stop_worker_signals_listener')->replaceArgument(0, $config['stop_worker_on_signals']); + } + if (null === $config['default_bus'] && 1 === \count($config['buses'])) { $config['default_bus'] = key($config['buses']); } @@ -2008,12 +2195,9 @@ class FrameworkExtension extends Extension foreach ($config['buses'] as $busId => $bus) { $middleware = $bus['middleware']; - if ($bus['default_middleware']) { - if ('allow_no_handlers' === $bus['default_middleware']) { - $defaultMiddleware['after'][1]['arguments'] = [true]; - } else { - unset($defaultMiddleware['after'][1]['arguments']); - } + if ($bus['default_middleware']['enabled']) { + $defaultMiddleware['after'][0]['arguments'] = [$bus['default_middleware']['allow_no_senders']]; + $defaultMiddleware['after'][1]['arguments'] = [$bus['default_middleware']['allow_no_handlers']]; // argument to add_bus_name_stamp_middleware $defaultMiddleware['before'][0]['arguments'] = [$busId]; @@ -2022,7 +2206,7 @@ class FrameworkExtension extends Extension } foreach ($middleware as $middlewareItem) { - if (!$validationConfig['enabled'] && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) { + if (!$validationEnabled && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) { throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".'); } } @@ -2078,6 +2262,7 @@ class FrameworkExtension extends Extension $senderAliases = []; $transportRetryReferences = []; + $transportRateLimiterReferences = []; foreach ($config['transports'] as $name => $transport) { $serializerId = $transport['serializer'] ?? 'messenger.default_serializer'; $transportDefinition = (new Definition(TransportInterface::class)) @@ -2106,6 +2291,14 @@ class FrameworkExtension extends Extension $transportRetryReferences[$name] = new Reference($retryServiceId); } + + if ($transport['rate_limiter']) { + if (!interface_exists(LimiterInterface::class)) { + throw new LogicException('Rate limiter cannot be used within Messenger as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); + } + + $transportRateLimiterReferences[$name] = new Reference('limiter.'.$transport['rate_limiter']); + } } $senderReferences = []; @@ -2126,13 +2319,15 @@ class FrameworkExtension extends Extension } } - $failureTransportReferencesByTransportName = array_map(function ($failureTransportName) use ($senderReferences) { - return $senderReferences[$failureTransportName]; - }, $failureTransportsByName); + $failureTransportReferencesByTransportName = array_map(fn ($failureTransportName) => $senderReferences[$failureTransportName], $failureTransportsByName); $messageToSendersMapping = []; foreach ($config['routing'] as $message => $messageConfiguration) { - if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) { + if ('*' !== $message && !class_exists($message) && !interface_exists($message, false) && !preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++\*$/', $message)) { + if (str_contains($message, '*')) { + throw new LogicException(sprintf('Invalid Messenger routing configuration: invalid namespace "%s" wildcard.', $message)); + } + throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message)); } @@ -2160,13 +2355,22 @@ class FrameworkExtension extends Extension $container->getDefinition('messenger.retry_strategy_locator') ->replaceArgument(0, $transportRetryReferences); + if (!$transportRateLimiterReferences) { + $container->removeDefinition('messenger.rate_limiter_locator'); + } else { + $container->getDefinition('messenger.rate_limiter_locator') + ->replaceArgument(0, $transportRateLimiterReferences); + } + if (\count($failureTransports) > 0) { - $container->getDefinition('console.command.messenger_failed_messages_retry') - ->replaceArgument(0, $config['failure_transport']); - $container->getDefinition('console.command.messenger_failed_messages_show') - ->replaceArgument(0, $config['failure_transport']); - $container->getDefinition('console.command.messenger_failed_messages_remove') - ->replaceArgument(0, $config['failure_transport']); + if ($this->hasConsole()) { + $container->getDefinition('console.command.messenger_failed_messages_retry') + ->replaceArgument(0, $config['failure_transport']); + $container->getDefinition('console.command.messenger_failed_messages_show') + ->replaceArgument(0, $config['failure_transport']); + $container->getDefinition('console.command.messenger_failed_messages_remove') + ->replaceArgument(0, $config['failure_transport']); + } $failureTransportsByTransportNameServiceLocator = ServiceLocatorTagPass::register($container, $failureTransportReferencesByTransportName); $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener') @@ -2178,34 +2382,13 @@ class FrameworkExtension extends Extension $container->removeDefinition('console.command.messenger_failed_messages_remove'); } - if (false === $config['reset_on_message']) { - throw new LogicException('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.'); - } - if (!$container->hasDefinition('console.command.messenger_consume_messages')) { $container->removeDefinition('messenger.listener.reset_services'); - } elseif (null === $config['reset_on_message']) { - trigger_deprecation('symfony/framework-bundle', '5.4', 'Not setting the "framework.messenger.reset_on_message" configuration option is deprecated, it will default to "true" in version 6.0.'); - - $container->getDefinition('console.command.messenger_consume_messages')->replaceArgument(5, null); - $container->removeDefinition('messenger.listener.reset_services'); } } - private function registerCacheConfiguration(array $config, ContainerBuilder $container) + private function registerCacheConfiguration(array $config, ContainerBuilder $container): void { - if (!class_exists(DefaultMarshaller::class)) { - $container->removeDefinition('cache.default_marshaller'); - } - - if (!class_exists(DoctrineAdapter::class)) { - $container->removeDefinition('cache.adapter.doctrine'); - } - - if (!class_exists(DoctrineDbalAdapter::class)) { - $container->removeDefinition('cache.adapter.doctrine_dbal'); - } - $version = new Parameter('container.build_id'); $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version); @@ -2218,7 +2401,7 @@ class FrameworkExtension extends Extension // Inline any env vars referenced in the parameter $container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true)); } - foreach (['doctrine', 'psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) { + foreach (['psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) { if (isset($config[$name = 'default_'.$name.'_provider'])) { $container->setAlias('cache.'.$name, new Alias(CachePoolPass::getServiceProvider($container, $config[$name]), false)); } @@ -2254,9 +2437,11 @@ class FrameworkExtension extends Extension if ($isRedisTagAware && 'cache.app' === $name) { $container->setAlias('cache.app.taggable', $name); + $definition->addTag('cache.taggable', ['pool' => $name]); } elseif ($isRedisTagAware) { $tagAwareId = $name; $container->setAlias('.'.$name.'.inner', $name); + $definition->addTag('cache.taggable', ['pool' => $name]); } elseif ($pool['tags']) { if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) { $pool['tags'] = '.'.$pool['tags'].'.inner'; @@ -2264,15 +2449,10 @@ class FrameworkExtension extends Extension $container->register($name, TagAwareAdapter::class) ->addArgument(new Reference('.'.$name.'.inner')) ->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null) + ->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) ->setPublic($pool['public']) - ; - - if (method_exists(TagAwareAdapter::class, 'setLogger')) { - $container - ->getDefinition($name) - ->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) - ->addTag('monolog.logger', ['channel' => 'cache']); - } + ->addTag('cache.taggable', ['pool' => $name]) + ->addTag('monolog.logger', ['channel' => 'cache']); $pool['name'] = $tagAwareId = $name; $pool['public'] = false; @@ -2281,6 +2461,7 @@ class FrameworkExtension extends Extension $tagAwareId = '.'.$name.'.taggable'; $container->register($tagAwareId, TagAwareAdapter::class) ->addArgument(new Reference($name)) + ->addTag('cache.taggable', ['pool' => $name]) ; } @@ -2297,9 +2478,8 @@ class FrameworkExtension extends Extension $container->setDefinition($name, $definition); } - if (method_exists(PropertyAccessor::class, 'createCache')) { + if (class_exists(PropertyAccessor::class)) { $propertyAccessDefinition = $container->register('cache.property_access', AdapterInterface::class); - $propertyAccessDefinition->setPublic(false); if (!$container->getParameter('kernel.debug')) { $propertyAccessDefinition->setFactory([PropertyAccessor::class, 'createCache']); @@ -2313,31 +2493,48 @@ class FrameworkExtension extends Extension } } - private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $profilerConfig) + private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('http_client.php'); $options = $config['default_options'] ?? []; $retryOptions = $options['retry_failed'] ?? ['enabled' => false]; unset($options['retry_failed']); - $container->getDefinition('http_client')->setArguments([$options, $config['max_host_connections'] ?? 6]); + $defaultUriTemplateVars = $options['vars'] ?? []; + unset($options['vars']); + $container->getDefinition('http_client.transport')->setArguments([$options, $config['max_host_connections'] ?? 6]); - if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'], true)) { + if (!class_exists(PingWebhookMessageHandler::class)) { + $container->removeDefinition('http_client.messenger.ping_webhook_handler'); + } + + if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'])) { $container->removeDefinition('psr18.http_client'); $container->removeAlias(ClientInterface::class); } - if (!ContainerBuilder::willBeAvailable('php-http/httplug', HttpClient::class, ['symfony/framework-bundle', 'symfony/http-client'], true)) { - $container->removeDefinition(HttpClient::class); + if (!$hasHttplug = ContainerBuilder::willBeAvailable('php-http/httplug', HttpAsyncClient::class, ['symfony/framework-bundle', 'symfony/http-client'])) { + $container->removeDefinition('httplug.http_client'); + $container->removeAlias(HttpAsyncClient::class); + $container->removeAlias(HttpClient::class); } - if ($this->isConfigEnabled($container, $retryOptions)) { + if ($this->readConfigEnabled('http_client.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); } - $httpClientId = ($retryOptions['enabled'] ?? false) ? 'http_client.retryable.inner' : ($this->isConfigEnabled($container, $profilerConfig) ? '.debug.http_client.inner' : 'http_client'); + if (ContainerBuilder::willBeAvailable('guzzlehttp/uri-template', \GuzzleHttp\UriTemplate\UriTemplate::class, [])) { + $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.guzzle'); + } elseif (ContainerBuilder::willBeAvailable('rize/uri-template', \Rize\UriTemplate::class, [])) { + $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.rize'); + } + + $container + ->getDefinition('http_client.uri_template') + ->setArgument(2, $defaultUriTemplateVars); + foreach ($config['scoped_clients'] as $name => $scopeConfig) { - if ('http_client' === $name) { + if ($container->has($name)) { throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name)); } @@ -2352,20 +2549,29 @@ class FrameworkExtension extends Extension $container->register($name, ScopingHttpClient::class) ->setFactory([ScopingHttpClient::class, 'forBaseUri']) - ->setArguments([new Reference($httpClientId), $baseUri, $scopeConfig]) + ->setArguments([new Reference('http_client.transport'), $baseUri, $scopeConfig]) ->addTag('http_client.client') ; } else { $container->register($name, ScopingHttpClient::class) - ->setArguments([new Reference($httpClientId), [$scope => $scopeConfig], $scope]) + ->setArguments([new Reference('http_client.transport'), [$scope => $scopeConfig], $scope]) ->addTag('http_client.client') ; } - if ($this->isConfigEnabled($container, $retryOptions)) { + if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, $name, $container); } + $container + ->register($name.'.uri_template', UriTemplateHttpClient::class) + ->setDecoratedService($name, null, 7) // Between TraceableHttpClient (5) and RetryableHttpClient (10) + ->setArguments([ + new Reference($name.'.uri_template.inner'), + new Reference('http_client.uri_template_expander', ContainerInterface::NULL_ON_INVALID_REFERENCE), + $defaultUriTemplateVars, + ]); + $container->registerAliasForArgument($name, HttpClientInterface::class); if ($hasPsr18) { @@ -2374,21 +2580,24 @@ class FrameworkExtension extends Extension $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name); } + + if ($hasHttplug) { + $container->setDefinition('httplug.'.$name, new ChildDefinition('httplug.http_client')) + ->replaceArgument(0, new Reference($name)); + + $container->registerAliasForArgument('httplug.'.$name, HttpAsyncClient::class, $name); + } } if ($responseFactoryId = $config['mock_response_factory'] ?? null) { - $container->register($httpClientId.'.mock_client', MockHttpClient::class) - ->setDecoratedService($httpClientId, null, -10) // lower priority than TraceableHttpClient + $container->register('http_client.mock_client', MockHttpClient::class) + ->setDecoratedService('http_client.transport', null, -10) // lower priority than TraceableHttpClient (5) ->setArguments([new Reference($responseFactoryId)]); } } - private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container) + private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container): void { - if (!class_exists(RetryableHttpClient::class)) { - throw new LogicException('Support for retrying failed requests requires symfony/http-client 5.2 or higher, try upgrading.'); - } - if (null !== $options['retry_strategy']) { $retryStrategy = new Reference($options['retry_strategy']); } else { @@ -2415,12 +2624,12 @@ class FrameworkExtension extends Extension $container ->register($name.'.retryable', RetryableHttpClient::class) - ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient + ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient (5) ->setArguments([new Reference($name.'.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')]) ->addTag('monolog.logger', ['channel' => 'http_client']); } - private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void { if (!class_exists(Mailer::class)) { throw new LogicException('Mailer support cannot be enabled as the component is not installed. Try running "composer require symfony/mailer".'); @@ -2435,9 +2644,6 @@ class FrameworkExtension extends Extension $container->getDefinition('mailer.transports')->setArgument(0, $transports); $container->getDefinition('mailer.default_transport')->setArgument(0, current($transports)); - $container->removeDefinition('mailer.logger_message_listener'); - $container->setAlias('mailer.logger_message_listener', (new Alias('mailer.message_logger_listener'))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "mailer.message_logger_listener" instead.')); - $mailer = $container->getDefinition('mailer.mailer'); if (false === $messageBus = $config['message_bus']) { $mailer->replaceArgument(1, null); @@ -2446,25 +2652,46 @@ class FrameworkExtension extends Extension } $classToServices = [ - GmailTransportFactory::class => 'mailer.transport_factory.gmail', - MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', - MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', - MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', - OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp', - PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', - SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', - SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue', - SesTransportFactory::class => 'mailer.transport_factory.amazon', + MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo', + MailerBridge\Google\Transport\GmailTransportFactory::class => 'mailer.transport_factory.gmail', + MailerBridge\Infobip\Transport\InfobipTransportFactory::class => 'mailer.transport_factory.infobip', + MailerBridge\MailerSend\Transport\MailerSendTransportFactory::class => 'mailer.transport_factory.mailersend', + MailerBridge\Mailgun\Transport\MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', + MailerBridge\Mailjet\Transport\MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', + MailerBridge\MailPace\Transport\MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace', + MailerBridge\Mailchimp\Transport\MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', + MailerBridge\OhMySmtp\Transport\OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp', + MailerBridge\Postmark\Transport\PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', + MailerBridge\Scaleway\Transport\ScalewayTransportFactory::class => 'mailer.transport_factory.scaleway', + MailerBridge\Sendgrid\Transport\SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', + MailerBridge\Sendinblue\Transport\SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue', + MailerBridge\Amazon\Transport\SesTransportFactory::class => 'mailer.transport_factory.amazon', ]; foreach ($classToServices as $class => $service) { $package = substr($service, \strlen('mailer.transport_factory.')); - if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'], true)) { + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) { $container->removeDefinition($service); } } + if ($webhookEnabled) { + $webhookRequestParsers = [ + MailerBridge\Mailgun\Webhook\MailgunRequestParser::class => 'mailer.webhook.request_parser.mailgun', + MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark', + MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class => 'mailer.webhook.request_parser.sendgrid', + ]; + + foreach ($webhookRequestParsers as $class => $service) { + $package = substr($service, \strlen('mailer.transport_factory.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) { + $container->removeDefinition($service); + } + } + } + $envelopeListener = $container->getDefinition('mailer.envelope_listener'); $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); @@ -2483,9 +2710,17 @@ class FrameworkExtension extends Extension } else { $container->removeDefinition('mailer.message_listener'); } + + if (!class_exists(MessengerTransportListener::class)) { + $container->removeDefinition('mailer.messenger_transport_listener'); + } + + if ($webhookEnabled) { + $loader->load('mailer_webhook.php'); + } } - private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void { if (!class_exists(Notifier::class)) { throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".'); @@ -2507,14 +2742,26 @@ class FrameworkExtension extends Extension $container->removeAlias(TexterInterface::class); } - if ($this->mailerConfigEnabled) { + if ($this->isInitializedConfigEnabled('mailer')) { $sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0); $container->getDefinition('notifier.channel.email')->setArgument(2, $sender); } else { $container->removeDefinition('notifier.channel.email'); } - if ($this->messengerConfigEnabled) { + foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms'] as $serviceId) { + if (!$container->hasDefinition($serviceId)) { + continue; + } + + if (false === $messageBus = $config['message_bus']) { + $container->getDefinition($serviceId)->replaceArgument(1, null); + } else { + $container->getDefinition($serviceId)->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + } + + if ($this->isInitializedConfigEnabled('messenger')) { if ($config['notification_on_failed_messages']) { $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber'); } @@ -2537,50 +2784,76 @@ class FrameworkExtension extends Extension ->addTag('texter.transport_factory'); $classToServices = [ - AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', - AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', - ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', - DiscordTransportFactory::class => 'notifier.transport_factory.discord', - EsendexTransportFactory::class => 'notifier.transport_factory.esendex', - ExpoTransportFactory::class => 'notifier.transport_factory.expo', - FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', - FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', - FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', - FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', - GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', - GitterTransportFactory::class => 'notifier.transport_factory.gitter', - GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', - InfobipTransportFactory::class => 'notifier.transport_factory.infobip', - IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', - LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', - LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', - MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', - MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', - MercureTransportFactory::class => 'notifier.transport_factory.mercure', - MessageBirdTransport::class => 'notifier.transport_factory.message-bird', - MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media', - MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams', - MobytTransportFactory::class => 'notifier.transport_factory.mobyt', - NexmoTransportFactory::class => 'notifier.transport_factory.nexmo', - OctopushTransportFactory::class => 'notifier.transport_factory.octopush', - OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', - OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', - RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', - SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue', - SinchTransportFactory::class => 'notifier.transport_factory.sinch', - SlackTransportFactory::class => 'notifier.transport_factory.slack', - Sms77TransportFactory::class => 'notifier.transport_factory.sms77', - SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', - SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', - SmscTransportFactory::class => 'notifier.transport_factory.smsc', - SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', - TelegramTransportFactory::class => 'notifier.transport_factory.telegram', - TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', - TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms', - TwilioTransportFactory::class => 'notifier.transport_factory.twilio', - VonageTransportFactory::class => 'notifier.transport_factory.vonage', - YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', - ZulipTransportFactory::class => 'notifier.transport_factory.zulip', + NotifierBridge\AllMySms\AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', + NotifierBridge\AmazonSns\AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', + NotifierBridge\Bandwidth\BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth', + NotifierBridge\Brevo\BrevoTransportFactory::class => 'notifier.transport_factory.brevo', + NotifierBridge\Chatwork\ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', + NotifierBridge\Clickatell\ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', + NotifierBridge\ClickSend\ClickSendTransportFactory::class => 'notifier.transport_factory.click-send', + NotifierBridge\ContactEveryone\ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone', + NotifierBridge\Discord\DiscordTransportFactory::class => 'notifier.transport_factory.discord', + NotifierBridge\Engagespot\EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot', + NotifierBridge\Esendex\EsendexTransportFactory::class => 'notifier.transport_factory.esendex', + NotifierBridge\Expo\ExpoTransportFactory::class => 'notifier.transport_factory.expo', + NotifierBridge\FakeChat\FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', + NotifierBridge\FakeSms\FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', + NotifierBridge\Firebase\FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', + NotifierBridge\FortySixElks\FortySixElksTransportFactory::class => 'notifier.transport_factory.forty-six-elks', + NotifierBridge\FreeMobile\FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', + NotifierBridge\GatewayApi\GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', + NotifierBridge\Gitter\GitterTransportFactory::class => 'notifier.transport_factory.gitter', + NotifierBridge\GoIp\GoIpTransportFactory::class => 'notifier.transport_factory.go-ip', + NotifierBridge\GoogleChat\GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', + NotifierBridge\Infobip\InfobipTransportFactory::class => 'notifier.transport_factory.infobip', + NotifierBridge\Iqsms\IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', + NotifierBridge\Isendpro\IsendproTransportFactory::class => 'notifier.transport_factory.isendpro', + NotifierBridge\KazInfoTeh\KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', + NotifierBridge\LightSms\LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', + NotifierBridge\LineNotify\LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify', + NotifierBridge\LinkedIn\LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', + NotifierBridge\Mailjet\MailjetTransportFactory::class => 'notifier.transport_factory.mailjet', + NotifierBridge\Mastodon\MastodonTransportFactory::class => 'notifier.transport_factory.mastodon', + NotifierBridge\Mattermost\MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', + NotifierBridge\Mercure\MercureTransportFactory::class => 'notifier.transport_factory.mercure', + NotifierBridge\MessageBird\MessageBirdTransport::class => 'notifier.transport_factory.message-bird', + NotifierBridge\MessageMedia\MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media', + NotifierBridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams', + NotifierBridge\Mobyt\MobytTransportFactory::class => 'notifier.transport_factory.mobyt', + NotifierBridge\Novu\NovuTransportFactory::class => 'notifier.transport_factory.novu', + NotifierBridge\Ntfy\NtfyTransportFactory::class => 'notifier.transport_factory.ntfy', + NotifierBridge\Octopush\OctopushTransportFactory::class => 'notifier.transport_factory.octopush', + NotifierBridge\OneSignal\OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', + NotifierBridge\OrangeSms\OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', + NotifierBridge\OvhCloud\OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', + NotifierBridge\PagerDuty\PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty', + NotifierBridge\Plivo\PlivoTransportFactory::class => 'notifier.transport_factory.plivo', + NotifierBridge\Pushover\PushoverTransportFactory::class => 'notifier.transport_factory.pushover', + NotifierBridge\Redlink\RedlinkTransportFactory::class => 'notifier.transport_factory.redlink', + NotifierBridge\RingCentral\RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', + NotifierBridge\RocketChat\RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', + NotifierBridge\Sendberry\SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', + NotifierBridge\SimpleTextin\SimpleTextinTransportFactory::class => 'notifier.transport_factory.simple-textin', + NotifierBridge\Sendinblue\SendinblueTransportFactory::class => 'notifier.transport_factory.sendinblue', + NotifierBridge\Sinch\SinchTransportFactory::class => 'notifier.transport_factory.sinch', + NotifierBridge\Slack\SlackTransportFactory::class => 'notifier.transport_factory.slack', + NotifierBridge\Sms77\Sms77TransportFactory::class => 'notifier.transport_factory.sms77', + NotifierBridge\Smsapi\SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', + NotifierBridge\SmsBiuras\SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', + NotifierBridge\Smsc\SmscTransportFactory::class => 'notifier.transport_factory.smsc', + NotifierBridge\SmsFactor\SmsFactorTransportFactory::class => 'notifier.transport_factory.sms-factor', + NotifierBridge\Smsmode\SmsmodeTransportFactory::class => 'notifier.transport_factory.smsmode', + NotifierBridge\SpotHit\SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', + NotifierBridge\Telegram\TelegramTransportFactory::class => 'notifier.transport_factory.telegram', + NotifierBridge\Telnyx\TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', + NotifierBridge\Termii\TermiiTransportFactory::class => 'notifier.transport_factory.termii', + NotifierBridge\TurboSms\TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms', + NotifierBridge\Twilio\TwilioTransportFactory::class => 'notifier.transport_factory.twilio', + NotifierBridge\Twitter\TwitterTransportFactory::class => 'notifier.transport_factory.twitter', + NotifierBridge\Vonage\VonageTransportFactory::class => 'notifier.transport_factory.vonage', + NotifierBridge\Yunpian\YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', + NotifierBridge\Zendesk\ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', + NotifierBridge\Zulip\ZulipTransportFactory::class => 'notifier.transport_factory.zulip', ]; $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; @@ -2588,27 +2861,26 @@ class FrameworkExtension extends Extension foreach ($classToServices as $class => $service) { $package = substr($service, \strlen('notifier.transport_factory.')); - if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages, true)) { + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages)) { $container->removeDefinition($service); - $container->removeAlias(str_replace('-', '', $service)); // @deprecated to be removed in 6.0 } } - if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, true) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages, true) && \in_array(MercureBundle::class, $container->getParameter('kernel.bundles'), true)) { - $container->getDefinition($classToServices[MercureTransportFactory::class]) + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages) && \in_array(MercureBundle::class, $container->getParameter('kernel.bundles'), true)) { + $container->getDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]) ->replaceArgument('$registry', new Reference(HubRegistry::class)); - } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, true)) { - $container->removeDefinition($classToServices[MercureTransportFactory::class]); + } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages)) { + $container->removeDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]); } - if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], true)) { - $container->getDefinition($classToServices[FakeChatTransportFactory::class]) + if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', NotifierBridge\FakeChat\FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { + $container->getDefinition($classToServices[NotifierBridge\FakeChat\FakeChatTransportFactory::class]) ->replaceArgument('$mailer', new Reference('mailer')) ->replaceArgument('$logger', new Reference('logger')); } - if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], true)) { - $container->getDefinition($classToServices[FakeSmsTransportFactory::class]) + if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', NotifierBridge\FakeSms\FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { + $container->getDefinition($classToServices[NotifierBridge\FakeSms\FakeSmsTransportFactory::class]) ->replaceArgument('$mailer', new Reference('mailer')) ->replaceArgument('$logger', new Reference('logger')); } @@ -2621,41 +2893,111 @@ class FrameworkExtension extends Extension $notifier->addMethodCall('addAdminRecipient', [new Reference($id)]); } } + + if ($webhookEnabled) { + $loader->load('notifier_webhook.php'); + } } - private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerWebhookConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(WebhookController::class)) { + throw new LogicException('Webhook support cannot be enabled as the component is not installed. Try running "composer require symfony/webhook".'); + } + + $loader->load('webhook.php'); + + $parsers = []; + foreach ($config['routing'] as $type => $cfg) { + $parsers[$type] = [ + 'parser' => new Reference($cfg['service']), + 'secret' => $cfg['secret'], + ]; + } + + $controller = $container->getDefinition('webhook.controller'); + $controller->replaceArgument(0, $parsers); + $controller->replaceArgument(1, new Reference($config['message_bus'])); + } + + private function registerRemoteEventConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(RemoteEvent::class)) { + throw new LogicException('RemoteEvent support cannot be enabled as the component is not installed. Try running "composer require symfony/remote-event".'); + } + + $loader->load('remote_event.php'); + } + + private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('rate_limiter.php'); foreach ($config['limiters'] as $name => $limiterConfig) { - self::registerRateLimiter($container, $name, $limiterConfig); + // default configuration (when used by other DI extensions) + $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; + + $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); + + if (null !== $limiterConfig['lock_factory']) { + if (!interface_exists(LockInterface::class)) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name)); + } + + if (!$this->isInitializedConfigEnabled('lock')) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name)); + } + + $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); + } + unset($limiterConfig['lock_factory']); + + if (null === $storageId = $limiterConfig['storage_service'] ?? null) { + $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); + } + + $limiter->replaceArgument(1, new Reference($storageId)); + unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']); + + $limiterConfig['id'] = $name; + $limiter->replaceArgument(0, $limiterConfig); + + $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); } } + /** + * @deprecated since Symfony 6.2 + * + * @return void + */ public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) { + trigger_deprecation('symfony/framework-bundle', '6.2', 'The "%s()" method is deprecated.', __METHOD__); + // default configuration (when used by other DI extensions) $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); if (null !== $limiterConfig['lock_factory']) { - if (!self::$lockConfigEnabled) { - throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed and configured.', $name)); + if (!interface_exists(LockInterface::class)) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name)); + } + if (!$container->hasDefinition('lock.factory.abstract')) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name)); } $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); } unset($limiterConfig['lock_factory']); - $storageId = $limiterConfig['storage_service'] ?? null; - if (null === $storageId) { + if (null === $storageId = $limiterConfig['storage_service'] ?? null) { $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); } $limiter->replaceArgument(1, new Reference($storageId)); - unset($limiterConfig['storage_service']); - unset($limiterConfig['cache_pool']); + unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']); $limiterConfig['id'] = $name; $limiter->replaceArgument(0, $limiterConfig); @@ -2663,7 +3005,7 @@ class FrameworkExtension extends Extension $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); } - private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('uid.php'); @@ -2684,34 +3026,162 @@ class FrameworkExtension extends Extension } } + private function registerHtmlSanitizerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('html_sanitizer.php'); + + foreach ($config['sanitizers'] as $sanitizerName => $sanitizerConfig) { + $configId = 'html_sanitizer.config.'.$sanitizerName; + $def = $container->register($configId, HtmlSanitizerConfig::class); + + // Base + if ($sanitizerConfig['allow_safe_elements']) { + $def->addMethodCall('allowSafeElements', [], true); + } + + if ($sanitizerConfig['allow_static_elements']) { + $def->addMethodCall('allowStaticElements', [], true); + } + + // Configures elements + foreach ($sanitizerConfig['allow_elements'] as $element => $attributes) { + $def->addMethodCall('allowElement', [$element, $attributes], true); + } + + foreach ($sanitizerConfig['block_elements'] as $element) { + $def->addMethodCall('blockElement', [$element], true); + } + + foreach ($sanitizerConfig['drop_elements'] as $element) { + $def->addMethodCall('dropElement', [$element], true); + } + + // Configures attributes + foreach ($sanitizerConfig['allow_attributes'] as $attribute => $elements) { + $def->addMethodCall('allowAttribute', [$attribute, $elements], true); + } + + foreach ($sanitizerConfig['drop_attributes'] as $attribute => $elements) { + $def->addMethodCall('dropAttribute', [$attribute, $elements], true); + } + + // Force attributes + foreach ($sanitizerConfig['force_attributes'] as $element => $attributes) { + foreach ($attributes as $attrName => $attrValue) { + $def->addMethodCall('forceAttribute', [$element, $attrName, $attrValue], true); + } + } + + // Settings + $def->addMethodCall('forceHttpsUrls', [$sanitizerConfig['force_https_urls']], true); + if ($sanitizerConfig['allowed_link_schemes']) { + $def->addMethodCall('allowLinkSchemes', [$sanitizerConfig['allowed_link_schemes']], true); + } + $def->addMethodCall('allowLinkHosts', [$sanitizerConfig['allowed_link_hosts']], true); + $def->addMethodCall('allowRelativeLinks', [$sanitizerConfig['allow_relative_links']], true); + if ($sanitizerConfig['allowed_media_schemes']) { + $def->addMethodCall('allowMediaSchemes', [$sanitizerConfig['allowed_media_schemes']], true); + } + $def->addMethodCall('allowMediaHosts', [$sanitizerConfig['allowed_media_hosts']], true); + $def->addMethodCall('allowRelativeMedias', [$sanitizerConfig['allow_relative_medias']], true); + + // Custom attribute sanitizers + foreach ($sanitizerConfig['with_attribute_sanitizers'] as $serviceName) { + $def->addMethodCall('withAttributeSanitizer', [new Reference($serviceName)], true); + } + + foreach ($sanitizerConfig['without_attribute_sanitizers'] as $serviceName) { + $def->addMethodCall('withoutAttributeSanitizer', [new Reference($serviceName)], true); + } + + if ($sanitizerConfig['max_input_length']) { + $def->addMethodCall('withMaxInputLength', [$sanitizerConfig['max_input_length']], true); + } + + // Create the sanitizer and link its config + $sanitizerId = 'html_sanitizer.sanitizer.'.$sanitizerName; + $container->register($sanitizerId, HtmlSanitizer::class) + ->addTag('html_sanitizer', ['sanitizer' => $sanitizerName]) + ->addArgument(new Reference($configId)); + + if ('default' !== $sanitizerName) { + $container->registerAliasForArgument($sanitizerId, HtmlSanitizerInterface::class, $sanitizerName); + } + } + } + private function resolveTrustedHeaders(array $headers): int { $trustedHeaders = 0; foreach ($headers as $h) { - switch ($h) { - case 'forwarded': $trustedHeaders |= Request::HEADER_FORWARDED; break; - case 'x-forwarded-for': $trustedHeaders |= Request::HEADER_X_FORWARDED_FOR; break; - case 'x-forwarded-host': $trustedHeaders |= Request::HEADER_X_FORWARDED_HOST; break; - case 'x-forwarded-proto': $trustedHeaders |= Request::HEADER_X_FORWARDED_PROTO; break; - case 'x-forwarded-port': $trustedHeaders |= Request::HEADER_X_FORWARDED_PORT; break; - case 'x-forwarded-prefix': $trustedHeaders |= Request::HEADER_X_FORWARDED_PREFIX; break; - } + $trustedHeaders |= match ($h) { + 'forwarded' => Request::HEADER_FORWARDED, + 'x-forwarded-for' => Request::HEADER_X_FORWARDED_FOR, + 'x-forwarded-host' => Request::HEADER_X_FORWARDED_HOST, + 'x-forwarded-proto' => Request::HEADER_X_FORWARDED_PROTO, + 'x-forwarded-port' => Request::HEADER_X_FORWARDED_PORT, + 'x-forwarded-prefix' => Request::HEADER_X_FORWARDED_PREFIX, + default => 0, + }; } return $trustedHeaders; } - /** - * {@inheritdoc} - */ - public function getXsdValidationBasePath() + public function getXsdValidationBasePath(): string|false { return \dirname(__DIR__).'/Resources/config/schema'; } - public function getNamespace() + public function getNamespace(): string { return 'http://symfony.com/schema/dic/symfony'; } + + protected function isConfigEnabled(ContainerBuilder $container, array $config): bool + { + throw new \LogicException('To prevent using outdated configuration, you must use the "readConfigEnabled" method instead.'); + } + + private function isInitializedConfigEnabled(string $path): bool + { + if (isset($this->configsEnabled[$path])) { + return $this->configsEnabled[$path]; + } + + throw new LogicException(sprintf('Can not read config enabled at "%s" because it has not been initialized.', $path)); + } + + private function readConfigEnabled(string $path, ContainerBuilder $container, array $config): bool + { + return $this->configsEnabled[$path] ??= parent::isConfigEnabled($container, $config); + } + + private function writeConfigEnabled(string $path, bool $value, array &$config): void + { + if (isset($this->configsEnabled[$path])) { + throw new LogicException('Can not change config enabled because it has already been read.'); + } + + $this->configsEnabled[$path] = $value; + $config['enabled'] = $value; + } + + private function getPublicDirectory(ContainerBuilder $container): string + { + $projectDir = $container->getParameter('kernel.project_dir'); + $defaultPublicDir = $projectDir.'/public'; + + $composerFilePath = $projectDir.'/composer.json'; + + if (!file_exists($composerFilePath)) { + return $defaultPublicDir; + } + + $container->addResource(new FileResource($composerFilePath)); + $composerConfig = json_decode(file_get_contents($composerFilePath), true); + + return isset($composerConfig['extra']['public-dir']) ? $projectDir.'/'.$composerConfig['extra']['public-dir'] : $defaultPublicDir; + } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php b/lib/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php new file mode 100644 index 000000000..158054fe4 --- /dev/null +++ b/lib/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class VirtualRequestStackPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if ($container->has('.virtual_request_stack')) { + return; + } + + if ($container->hasDefinition('debug.event_dispatcher')) { + $container->getDefinition('debug.event_dispatcher')->replaceArgument(3, new Reference('request_stack', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); + } + + if ($container->hasDefinition('debug.log_processor')) { + $container->getDefinition('debug.log_processor')->replaceArgument(0, new Reference('request_stack')); + } + } +} diff --git a/lib/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php b/lib/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php new file mode 100644 index 000000000..d3fc38106 --- /dev/null +++ b/lib/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Profiler\Profile; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @internal + * + * @author Jules Pietri + */ +final class ConsoleProfilerListener implements EventSubscriberInterface +{ + private ?\Throwable $error = null; + /** @var \SplObjectStorage */ + private \SplObjectStorage $profiles; + /** @var \SplObjectStorage */ + private \SplObjectStorage $parents; + + public function __construct( + private readonly Profiler $profiler, + private readonly RequestStack $requestStack, + private readonly Stopwatch $stopwatch, + private readonly UrlGeneratorInterface $urlGenerator, + ) { + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::COMMAND => ['initialize', 4096], + ConsoleEvents::ERROR => ['catch', -2048], + ConsoleEvents::TERMINATE => ['profile', -4096], + ]; + } + + public function initialize(ConsoleCommandEvent $event): void + { + $input = $event->getInput(); + if (!$input->hasOption('profile') || !$input->getOption('profile')) { + $this->profiler->disable(); + + return; + } + + $request = $this->requestStack->getCurrentRequest(); + + if (!$request instanceof CliRequest || $request->command !== $event->getCommand()) { + return; + } + + $request->attributes->set('_stopwatch_token', substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); + $this->stopwatch->openSection(); + } + + public function catch(ConsoleErrorEvent $event): void + { + $this->error = $event->getError(); + } + + public function profile(ConsoleTerminateEvent $event): void + { + if (!$this->profiler->isEnabled()) { + return; + } + + $request = $this->requestStack->getCurrentRequest(); + + if (!$request instanceof CliRequest || $request->command !== $event->getCommand()) { + return; + } + + if (null !== $sectionId = $request->attributes->get('_stopwatch_token')) { + // we must close the section before saving the profile to allow late collect + try { + $this->stopwatch->stopSection($sectionId); + } catch (\LogicException) { + // noop + } + } + + $request->command->exitCode = $event->getExitCode(); + $request->command->interruptedBySignal = $event->getInterruptingSignal(); + + $profile = $this->profiler->collect($request, $request->getResponse(), $this->error); + $this->error = null; + $this->profiles[$request] = $profile; + + if ($this->parents[$request] = $this->requestStack->getParentRequest()) { + // do not save on sub commands + return; + } + + // attach children to parents + foreach ($this->profiles as $request) { + if (null !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + $output = $event->getOutput(); + $output = $output instanceof ConsoleOutputInterface && $output->isVerbose() ? $output->getErrorOutput() : null; + + // save profiles + foreach ($this->profiles as $r) { + $p = $this->profiles[$r]; + $this->profiler->saveProfile($p); + + $token = $p->getToken(); + $output?->writeln(sprintf( + 'See profile %s', + $this->urlGenerator->generate('_profiler', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL), + $token + )); + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } +} diff --git a/lib/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php b/lib/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php index 53cae12eb..d7bdc8e66 100644 --- a/lib/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php +++ b/lib/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php @@ -32,9 +32,6 @@ final class SuggestMissingPackageSubscriber implements EventSubscriberInterface 'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'], '_default' => ['Doctrine ORM', 'symfony/orm-pack'], ], - 'generate' => [ - '_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle'], - ], 'make' => [ '_default' => ['MakerBundle', 'symfony/maker-bundle --dev'], ], diff --git a/lib/symfony/framework-bundle/FrameworkBundle.php b/lib/symfony/framework-bundle/FrameworkBundle.php index 4ec54eccf..4ba30a5c8 100644 --- a/lib/symfony/framework-bundle/FrameworkBundle.php +++ b/lib/symfony/framework-bundle/FrameworkBundle.php @@ -13,18 +13,15 @@ namespace Symfony\Bundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AssetsContextPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ErrorLoggerCompilerPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RemoveUnusedSessionMarshallingHandlerPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SessionPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\VirtualRequestStackPass; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\ChainAdapter; @@ -45,6 +42,7 @@ use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\Form\DependencyInjection\FormPass; use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; @@ -58,8 +56,12 @@ use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Messenger\DependencyInjection\MessengerPass; use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass; use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; +use Symfony\Component\Routing\DependencyInjection\AddExpressionLanguageProvidersPass; use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; +use Symfony\Component\Scheduler\DependencyInjection\AddScheduleMessengerPass; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass; +use Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass; use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass; use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass; use Symfony\Component\Translation\DependencyInjection\TranslatorPass; @@ -69,6 +71,8 @@ use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass; use Symfony\Component\VarExporter\Internal\Hydrator; use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\Workflow\DependencyInjection\WorkflowDebugPass; +use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass; // Help opcache.preload discover always-needed symbols class_exists(ApcuAdapter::class); @@ -88,15 +92,34 @@ class_exists(Registry::class); */ class FrameworkBundle extends Bundle { + /** + * @return void + */ public function boot() { - ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true); + $_ENV['DOCTRINE_DEPRECATIONS'] = $_SERVER['DOCTRINE_DEPRECATIONS'] ??= 'trigger'; + + $handler = ErrorHandler::register(null, false); + + // When upgrading an existing Symfony application from 6.2 to 6.3, and + // the cache is warmed up, the service is not available yet, so we need + // to check if it exists. + if ($this->container->has('debug.error_handler_configurator')) { + $this->container->get('debug.error_handler_configurator')->configure($handler); + } if ($this->container->getParameter('kernel.http_method_override')) { Request::enableHttpMethodParameterOverride(); } + + if ($this->container->hasParameter('kernel.trust_x_sendfile_type_header') && $this->container->getParameter('kernel.trust_x_sendfile_type_header')) { + BinaryFileResponse::trustXSendfileTypeHeader(); + } } + /** + * @return void + */ public function build(ContainerBuilder $container) { parent::build($container); @@ -122,7 +145,7 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new RegisterControllerArgumentLocatorsPass()); $container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new RoutingResolverPass()); - $container->addCompilerPass(new DataCollectorTranslatorPass()); + $this->addCompilerPassIfExists($container, DataCollectorTranslatorPass::class); $container->addCompilerPass(new ProfilerPass()); // must be registered before removing private services as some might be listeners/subscribers // but as late as possible to get resolved parameters @@ -135,8 +158,8 @@ class FrameworkBundle extends Bundle // twig.template_iterator definition $this->addCompilerPassIfExists($container, TranslatorPass::class, PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $this->addCompilerPassIfExists($container, TranslatorPathsPass::class, PassConfig::TYPE_AFTER_REMOVING); - $container->addCompilerPass(new LoggingTranslatorPass()); - $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); + $this->addCompilerPassIfExists($container, LoggingTranslatorPass::class); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); $this->addCompilerPassIfExists($container, TranslationExtractorPass::class); $this->addCompilerPassIfExists($container, TranslationDumperPass::class); $container->addCompilerPass(new FragmentRendererPass()); @@ -147,29 +170,33 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); $this->addCompilerPassIfExists($container, FormPass::class); - $container->addCompilerPass(new WorkflowGuardListenerPass()); + $this->addCompilerPassIfExists($container, WorkflowGuardListenerPass::class); $container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new RegisterLocaleAwareServicesPass()); $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); $container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING); $this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class); + $this->addCompilerPassIfExists($container, AddScheduleMessengerPass::class); $this->addCompilerPassIfExists($container, MessengerPass::class); $this->addCompilerPassIfExists($container, HttpClientPass::class); $this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class); $container->addCompilerPass(new RegisterReverseContainerPass(true)); $container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass()); - $container->addCompilerPass(new SessionPass()); + // must be registered after MonologBundle's LoggerChannelPass + $container->addCompilerPass(new ErrorLoggerCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $container->addCompilerPass(new VirtualRequestStackPass()); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2); $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_BEFORE_REMOVING, -255); $container->addCompilerPass(new CacheCollectorPass(), PassConfig::TYPE_BEFORE_REMOVING); + $this->addCompilerPassIfExists($container, WorkflowDebugPass::class); } } - private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) + private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): void { $container->addResource(new ClassExistenceResource($class)); diff --git a/lib/symfony/framework-bundle/HttpCache/HttpCache.php b/lib/symfony/framework-bundle/HttpCache/HttpCache.php index fe38c4adc..f312a7afe 100644 --- a/lib/symfony/framework-bundle/HttpCache/HttpCache.php +++ b/lib/symfony/framework-bundle/HttpCache/HttpCache.php @@ -30,14 +30,14 @@ class HttpCache extends BaseHttpCache protected $cacheDir; protected $kernel; - private $store; - private $surrogate; - private $options; + private ?StoreInterface $store = null; + private ?SurrogateInterface $surrogate; + private array $options; /** - * @param string|StoreInterface $cache The cache directory (default used if null) or the storage instance + * @param $cache The cache directory (default used if null) or the storage instance */ - public function __construct(KernelInterface $kernel, $cache = null, SurrogateInterface $surrogate = null, array $options = null) + public function __construct(KernelInterface $kernel, string|StoreInterface $cache = null, SurrogateInterface $surrogate = null, array $options = null) { $this->kernel = $kernel; $this->surrogate = $surrogate; @@ -45,8 +45,6 @@ class HttpCache extends BaseHttpCache if ($cache instanceof StoreInterface) { $this->store = $cache; - } elseif (null !== $cache && !\is_string($cache)) { - throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a SurrogateInterface, "%s" given.', __METHOD__, get_debug_type($cache))); } else { $this->cacheDir = $cache; } @@ -62,10 +60,7 @@ class HttpCache extends BaseHttpCache parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($this->options, $this->getOptions())); } - /** - * {@inheritdoc} - */ - protected function forward(Request $request, bool $catch = false, Response $entry = null) + protected function forward(Request $request, bool $catch = false, Response $entry = null): Response { $this->getKernel()->boot(); $this->getKernel()->getContainer()->set('cache', $this); @@ -75,26 +70,18 @@ class HttpCache extends BaseHttpCache /** * Returns an array of options to customize the Cache configuration. - * - * @return array */ - protected function getOptions() + protected function getOptions(): array { return []; } - /** - * @return SurrogateInterface - */ - protected function createSurrogate() + protected function createSurrogate(): SurrogateInterface { return $this->surrogate ?? new Esi(); } - /** - * @return StoreInterface - */ - protected function createStore() + protected function createStore(): StoreInterface { return $this->store ?? new Store($this->cacheDir ?: $this->kernel->getCacheDir().'/http_cache'); } diff --git a/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php b/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php index c25f90d63..bbbfd5426 100644 --- a/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php +++ b/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php @@ -20,7 +20,6 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; use Symfony\Component\Routing\Loader\PhpFileLoader as RoutingPhpFileLoader; use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\RouteCollectionBuilder; /** * A Kernel that provides configuration hooks. @@ -51,14 +50,15 @@ trait MicroKernelTrait { $configDir = $this->getConfigDir(); - $container->import($configDir.'/{packages}/*.yaml'); - $container->import($configDir.'/{packages}/'.$this->environment.'/*.yaml'); + $container->import($configDir.'/{packages}/*.{php,yaml}'); + $container->import($configDir.'/{packages}/'.$this->environment.'/*.{php,yaml}'); if (is_file($configDir.'/services.yaml')) { $container->import($configDir.'/services.yaml'); $container->import($configDir.'/{services}_'.$this->environment.'.yaml'); } else { $container->import($configDir.'/{services}.php'); + $container->import($configDir.'/{services}_'.$this->environment.'.php'); } } @@ -75,14 +75,18 @@ trait MicroKernelTrait { $configDir = $this->getConfigDir(); - $routes->import($configDir.'/{routes}/'.$this->environment.'/*.yaml'); - $routes->import($configDir.'/{routes}/*.yaml'); + $routes->import($configDir.'/{routes}/'.$this->environment.'/*.{php,yaml}'); + $routes->import($configDir.'/{routes}/*.{php,yaml}'); if (is_file($configDir.'/routes.yaml')) { $routes->import($configDir.'/routes.yaml'); } else { $routes->import($configDir.'/{routes}.php'); } + + if (false !== ($fileName = (new \ReflectionObject($this))->getFileName())) { + $routes->import($fileName, 'attribute'); + } } /** @@ -101,9 +105,6 @@ trait MicroKernelTrait return $this->getConfigDir().'/bundles.php'; } - /** - * {@inheritdoc} - */ public function getCacheDir(): string { if (isset($_SERVER['APP_CACHE_DIR'])) { @@ -113,17 +114,20 @@ trait MicroKernelTrait return parent::getCacheDir(); } - /** - * {@inheritdoc} - */ + public function getBuildDir(): string + { + if (isset($_SERVER['APP_BUILD_DIR'])) { + return $_SERVER['APP_BUILD_DIR'].'/'.$this->environment; + } + + return parent::getBuildDir(); + } + public function getLogDir(): string { return $_SERVER['APP_LOG_DIR'] ?? parent::getLogDir(); } - /** - * {@inheritdoc} - */ public function registerBundles(): iterable { $contents = require $this->getBundlesPath(); @@ -135,7 +139,7 @@ trait MicroKernelTrait } /** - * {@inheritdoc} + * @return void */ public function registerContainerConfiguration(LoaderInterface $loader) { @@ -147,7 +151,7 @@ trait MicroKernelTrait ], ]); - $kernelClass = false !== strpos(static::class, "@anonymous\0") ? parent::class : static::class; + $kernelClass = str_contains(static::class, "@anonymous\0") ? parent::class : static::class; if (!$container->hasDefinition('kernel')) { $container->register('kernel', $kernelClass) @@ -177,12 +181,10 @@ trait MicroKernelTrait /* @var ContainerPhpFileLoader $kernelLoader */ $kernelLoader = $loader->getResolver()->resolve($file); $kernelLoader->setCurrentDir(\dirname($file)); - $instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $kernelLoader, $kernelLoader)(); + $instanceof = &\Closure::bind(fn &() => $this->instanceof, $kernelLoader, $kernelLoader)(); $valuePreProcessor = AbstractConfigurator::$valuePreProcessor; - AbstractConfigurator::$valuePreProcessor = function ($value) { - return $this === $value ? new Reference('kernel') : $value; - }; + AbstractConfigurator::$valuePreProcessor = fn ($value) => $this === $value ? new Reference('kernel') : $value; try { $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); @@ -208,17 +210,6 @@ trait MicroKernelTrait $collection = new RouteCollection(); $configureRoutes = new \ReflectionMethod($this, 'configureRoutes'); - $configuratorClass = $configureRoutes->getNumberOfParameters() > 0 && ($type = $configureRoutes->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null; - - if ($configuratorClass && !is_a(RoutingConfigurator::class, $configuratorClass, true)) { - trigger_deprecation('symfony/framework-bundle', '5.1', 'Using type "%s" for argument 1 of method "%s:configureRoutes()" is deprecated, use "%s" instead.', RouteCollectionBuilder::class, self::class, RoutingConfigurator::class); - - $routes = new RouteCollectionBuilder($loader); - $this->configureRoutes($routes); - - return $routes->build(); - } - $configureRoutes->getClosure($this)(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment())); foreach ($collection as $route) { @@ -226,6 +217,8 @@ trait MicroKernelTrait if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) { $route->setDefault('_controller', ['kernel', $controller[1]]); + } elseif ($controller instanceof \Closure && $this === ($r = new \ReflectionFunction($controller))->getClosureThis() && !str_contains($r->name, '{closure}')) { + $route->setDefault('_controller', ['kernel', $r->name]); } } diff --git a/lib/symfony/framework-bundle/KernelBrowser.php b/lib/symfony/framework-bundle/KernelBrowser.php index a02853012..45120e42b 100644 --- a/lib/symfony/framework-bundle/KernelBrowser.php +++ b/lib/symfony/framework-bundle/KernelBrowser.php @@ -30,13 +30,10 @@ use Symfony\Component\Security\Core\User\UserInterface; */ class KernelBrowser extends HttpKernelBrowser { - private $hasPerformedRequest = false; - private $profiler = false; - private $reboot = true; + private bool $hasPerformedRequest = false; + private bool $profiler = false; + private bool $reboot = true; - /** - * {@inheritdoc} - */ public function __construct(KernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) { parent::__construct($kernel, $server, $history, $cookieJar); @@ -44,10 +41,8 @@ class KernelBrowser extends HttpKernelBrowser /** * Returns the container. - * - * @return ContainerInterface */ - public function getContainer() + public function getContainer(): ContainerInterface { $container = $this->kernel->getContainer(); @@ -56,20 +51,16 @@ class KernelBrowser extends HttpKernelBrowser /** * Returns the kernel. - * - * @return KernelInterface */ - public function getKernel() + public function getKernel(): KernelInterface { return $this->kernel; } /** * Gets the profile associated with the current Response. - * - * @return HttpProfile|false|null */ - public function getProfile() + public function getProfile(): HttpProfile|false|null { if (null === $this->response || !$this->getContainer()->has('profiler')) { return false; @@ -82,6 +73,8 @@ class KernelBrowser extends HttpKernelBrowser * Enables the profiler for the very next request. * * If the profiler is not enabled, the call to this method does nothing. + * + * @return void */ public function enableProfiler() { @@ -95,6 +88,8 @@ class KernelBrowser extends HttpKernelBrowser * * By default, the Client reboots the Kernel for each request. This method * allows to keep the same kernel across requests. + * + * @return void */ public function disableReboot() { @@ -103,6 +98,8 @@ class KernelBrowser extends HttpKernelBrowser /** * Enables kernel reboot between requests. + * + * @return void */ public function enableReboot() { @@ -110,43 +107,42 @@ class KernelBrowser extends HttpKernelBrowser } /** - * @param UserInterface $user + * @param UserInterface $user + * @param array $tokenAttributes * * @return $this */ - public function loginUser(object $user, string $firewallContext = 'main'): self + public function loginUser(object $user, string $firewallContext = 'main'/* , array $tokenAttributes = [] */): static { + $tokenAttributes = 2 < \func_num_args() ? func_get_arg(2) : []; + if (!interface_exists(UserInterface::class)) { - throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed.', __METHOD__)); + throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed. Try running "composer require symfony/security-core".', __METHOD__)); } if (!$user instanceof UserInterface) { - throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, \is_object($user) ? \get_class($user) : \gettype($user))); + throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, get_debug_type($user))); } $token = new TestBrowserToken($user->getRoles(), $user, $firewallContext); - // @deprecated since Symfony 5.4 - if (method_exists($token, 'setAuthenticated')) { + $token->setAttributes($tokenAttributes); + // required for compatibility with Symfony 5.4 + if (method_exists($token, 'isAuthenticated')) { $token->setAuthenticated(true, false); } $container = $this->getContainer(); $container->get('security.untracked_token_storage')->setToken($token); - if ($container->has('session.factory')) { - $session = $container->get('session.factory')->createSession(); - } elseif ($container->has('session')) { - $session = $container->get('session'); - } else { + if (!$container->has('session.factory')) { return $this; } + $session = $container->get('session.factory')->createSession(); $session->set('_security_'.$firewallContext, serialize($token)); $session->save(); - $domains = array_unique(array_map(function (Cookie $cookie) use ($session) { - return $cookie->getName() === $session->getName() ? $cookie->getDomain() : ''; - }, $this->getCookieJar()->all())) ?: ['']; + $domains = array_unique(array_map(fn (Cookie $cookie) => $cookie->getName() === $session->getName() ? $cookie->getDomain() : '', $this->getCookieJar()->all())) ?: ['']; foreach ($domains as $domain) { $cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain); $this->getCookieJar()->set($cookie); @@ -156,13 +152,9 @@ class KernelBrowser extends HttpKernelBrowser } /** - * {@inheritdoc} - * * @param Request $request - * - * @return Response */ - protected function doRequest(object $request) + protected function doRequest(object $request): Response { // avoid shutting down the Kernel if no request has been performed yet // WebTestCase::createClient() boots the Kernel but do not handle a request @@ -184,13 +176,9 @@ class KernelBrowser extends HttpKernelBrowser } /** - * {@inheritdoc} - * * @param Request $request - * - * @return Response */ - protected function doRequestInProcess(object $request) + protected function doRequestInProcess(object $request): Response { $response = parent::doRequestInProcess($request); @@ -208,10 +196,8 @@ class KernelBrowser extends HttpKernelBrowser * client and override this method. * * @param Request $request - * - * @return string */ - protected function getScript(object $request) + protected function getScript(object $request): string { $kernel = var_export(serialize($this->kernel), true); $request = var_export(serialize($request), true); diff --git a/lib/symfony/framework-bundle/LICENSE b/lib/symfony/framework-bundle/LICENSE index 008370457..0138f8f07 100644 --- a/lib/symfony/framework-bundle/LICENSE +++ b/lib/symfony/framework-bundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2023 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/framework-bundle/README.md b/lib/symfony/framework-bundle/README.md index 76c7700fa..14c600fac 100644 --- a/lib/symfony/framework-bundle/README.md +++ b/lib/symfony/framework-bundle/README.md @@ -4,6 +4,11 @@ FrameworkBundle FrameworkBundle provides a tight integration between Symfony components and the Symfony full-stack framework. +Sponsor +------- + +Help Symfony by [sponsoring][1] its development! + Resources --------- @@ -11,3 +16,5 @@ Resources * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) + +[3]: https://symfony.com/sponsor diff --git a/lib/symfony/framework-bundle/Resources/config/annotations.php b/lib/symfony/framework-bundle/Resources/config/annotations.php index 33a2f4698..7667acc5d 100644 --- a/lib/symfony/framework-bundle/Resources/config/annotations.php +++ b/lib/symfony/framework-bundle/Resources/config/annotations.php @@ -12,10 +12,8 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Common\Annotations\Reader; -use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter; @@ -24,13 +22,8 @@ use Symfony\Component\Cache\Adapter\PhpArrayAdapter; return static function (ContainerConfigurator $container) { $container->services() ->set('annotations.reader', AnnotationReader::class) - ->call('addGlobalIgnoredName', [ - 'required', - service('annotations.dummy_registry')->nullOnInvalid(), // dummy arg to register class_exists as annotation loader only when required - ]) - - ->set('annotations.dummy_registry', AnnotationRegistry::class) - ->call('registerUniqueLoader', ['class_exists']) + ->call('addGlobalIgnoredName', ['required']) // @deprecated since Symfony 6.3 + ->deprecate('symfony/framework-bundle', '6.4', 'The "%service_id%" service is deprecated without replacement.') ->set('annotations.cached_reader', PsrCachedReader::class) ->args([ @@ -40,6 +33,7 @@ return static function (ContainerConfigurator $container) { ]) ->tag('annotations.cached_reader') ->tag('container.do_not_inline') + ->deprecate('symfony/framework-bundle', '6.4', 'The "%service_id%" service is deprecated without replacement.') ->set('annotations.filesystem_cache_adapter', FilesystemAdapter::class) ->args([ @@ -47,13 +41,7 @@ return static function (ContainerConfigurator $container) { 0, abstract_arg('Cache-Directory'), ]) - - ->set('annotations.filesystem_cache', DoctrineProvider::class) - ->factory([DoctrineProvider::class, 'wrap']) - ->args([ - service('annotations.filesystem_cache_adapter'), - ]) - ->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"') + ->deprecate('symfony/framework-bundle', '6.4', 'The "%service_id%" service is deprecated without replacement.') ->set('annotations.cache_warmer', AnnotationsCacheWarmer::class) ->args([ @@ -61,7 +49,9 @@ return static function (ContainerConfigurator $container) { param('kernel.cache_dir').'/annotations.php', '#^Symfony\\\\(?:Component\\\\HttpKernel\\\\|Bundle\\\\FrameworkBundle\\\\Controller\\\\(?!.*Controller$))#', param('kernel.debug'), + false, ]) + ->deprecate('symfony/framework-bundle', '6.4', 'The "%service_id%" service is deprecated without replacement.') ->set('annotations.cache_adapter', PhpArrayAdapter::class) ->factory([PhpArrayAdapter::class, 'create']) @@ -70,14 +60,12 @@ return static function (ContainerConfigurator $container) { service('cache.annotations'), ]) ->tag('container.hot_path') - - ->set('annotations.cache', DoctrineProvider::class) - ->factory([DoctrineProvider::class, 'wrap']) - ->args([ - service('annotations.cache_adapter'), - ]) - ->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"') + ->deprecate('symfony/framework-bundle', '6.4', 'The "%service_id%" service is deprecated without replacement.') ->alias('annotation_reader', 'annotations.reader') - ->alias(Reader::class, 'annotation_reader'); + ->deprecate('symfony/framework-bundle', '6.4', 'The "%alias_id%" service alias is deprecated without replacement.') + + ->alias(Reader::class, 'annotation_reader') + ->deprecate('symfony/framework-bundle', '6.4', 'The "%alias_id%" service alias is deprecated without replacement.') + ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/asset_mapper.php b/lib/symfony/framework-bundle/Resources/config/asset_mapper.php new file mode 100644 index 000000000..f41574d3b --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/asset_mapper.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperCompiler; +use Symfony\Component\AssetMapper\AssetMapperDevServerSubscriber; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\AssetMapperRepository; +use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand; +use Symfony\Component\AssetMapper\Command\DebugAssetMapperCommand; +use Symfony\Component\AssetMapper\Command\ImportMapAuditCommand; +use Symfony\Component\AssetMapper\Command\ImportMapInstallCommand; +use Symfony\Component\AssetMapper\Command\ImportMapOutdatedCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand; +use Symfony\Component\AssetMapper\Command\ImportMapUpdateCommand; +use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader; +use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler; +use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler; +use Symfony\Component\AssetMapper\Compiler\SourceMappingUrlsCompiler; +use Symfony\Component\AssetMapper\Factory\CachedMappedAssetFactory; +use Symfony\Component\AssetMapper\Factory\MappedAssetFactory; +use Symfony\Component\AssetMapper\ImportMap\ImportMapAuditor; +use Symfony\Component\AssetMapper\ImportMap\ImportMapConfigReader; +use Symfony\Component\AssetMapper\ImportMap\ImportMapGenerator; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; +use Symfony\Component\AssetMapper\ImportMap\ImportMapUpdateChecker; +use Symfony\Component\AssetMapper\ImportMap\ImportMapVersionChecker; +use Symfony\Component\AssetMapper\ImportMap\RemotePackageDownloader; +use Symfony\Component\AssetMapper\ImportMap\RemotePackageStorage; +use Symfony\Component\AssetMapper\ImportMap\Resolver\JsDelivrEsmResolver; +use Symfony\Component\AssetMapper\MapperAwareAssetPackage; +use Symfony\Component\AssetMapper\Path\LocalPublicAssetsFilesystem; +use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolver; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('asset_mapper', AssetMapper::class) + ->args([ + service('asset_mapper.repository'), + service('asset_mapper.mapped_asset_factory'), + service('asset_mapper.compiled_asset_mapper_config_reader'), + ]) + ->alias(AssetMapperInterface::class, 'asset_mapper') + + ->set('asset_mapper.mapped_asset_factory', MappedAssetFactory::class) + ->args([ + service('asset_mapper.public_assets_path_resolver'), + service('asset_mapper_compiler'), + abstract_arg('vendor directory'), + ]) + + ->set('asset_mapper.cached_mapped_asset_factory', CachedMappedAssetFactory::class) + ->args([ + service('.inner'), + param('kernel.cache_dir').'/asset_mapper', + param('kernel.debug'), + ]) + ->decorate('asset_mapper.mapped_asset_factory') + + ->set('asset_mapper.repository', AssetMapperRepository::class) + ->args([ + abstract_arg('array of asset mapper paths'), + param('kernel.project_dir'), + abstract_arg('array of excluded path patterns'), + abstract_arg('exclude dot files'), + ]) + + ->set('asset_mapper.public_assets_path_resolver', PublicAssetsPathResolver::class) + ->args([ + abstract_arg('asset public prefix'), + ]) + + ->set('asset_mapper.local_public_assets_filesystem', LocalPublicAssetsFilesystem::class) + ->args([ + abstract_arg('public directory'), + ]) + + ->set('asset_mapper.compiled_asset_mapper_config_reader', CompiledAssetMapperConfigReader::class) + ->args([ + abstract_arg('public assets directory'), + ]) + + ->set('asset_mapper.asset_package', MapperAwareAssetPackage::class) + ->decorate('assets._default_package') + ->args([ + service('.inner'), + service('asset_mapper'), + ]) + + ->set('asset_mapper.dev_server_subscriber', AssetMapperDevServerSubscriber::class) + ->args([ + service('asset_mapper'), + abstract_arg('asset public prefix'), + abstract_arg('extensions map'), + service('cache.asset_mapper'), + service('profiler')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + + ->set('asset_mapper.command.compile', AssetMapperCompileCommand::class) + ->args([ + service('asset_mapper.compiled_asset_mapper_config_reader'), + service('asset_mapper'), + service('asset_mapper.importmap.generator'), + service('asset_mapper.local_public_assets_filesystem'), + param('kernel.project_dir'), + param('kernel.debug'), + service('event_dispatcher')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('asset_mapper.command.debug', DebugAssetMapperCommand::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.repository'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('asset_mapper_compiler', AssetMapperCompiler::class) + ->args([ + tagged_iterator('asset_mapper.compiler'), + service_closure('asset_mapper'), + ]) + + ->set('asset_mapper.compiler.css_asset_url_compiler', CssAssetUrlCompiler::class) + ->args([ + abstract_arg('missing import mode'), + service('logger'), + ]) + ->tag('asset_mapper.compiler') + ->tag('monolog.logger', ['channel' => 'asset_mapper']) + + ->set('asset_mapper.compiler.source_mapping_urls_compiler', SourceMappingUrlsCompiler::class) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.compiler.javascript_import_path_compiler', JavaScriptImportPathCompiler::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + abstract_arg('missing import mode'), + service('logger'), + ]) + ->tag('asset_mapper.compiler') + ->tag('monolog.logger', ['channel' => 'asset_mapper']) + + ->set('asset_mapper.importmap.config_reader', ImportMapConfigReader::class) + ->args([ + abstract_arg('importmap.php path'), + service('asset_mapper.importmap.remote_package_storage'), + ]) + + ->set('asset_mapper.importmap.manager', ImportMapManager::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.importmap.remote_package_downloader'), + service('asset_mapper.importmap.resolver'), + ]) + ->alias(ImportMapManager::class, 'asset_mapper.importmap.manager') + + ->set('asset_mapper.importmap.generator', ImportMapGenerator::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.compiled_asset_mapper_config_reader'), + service('asset_mapper.importmap.config_reader'), + ]) + + ->set('asset_mapper.importmap.remote_package_storage', RemotePackageStorage::class) + ->args([ + abstract_arg('vendor directory'), + ]) + + ->set('asset_mapper.importmap.remote_package_downloader', RemotePackageDownloader::class) + ->args([ + service('asset_mapper.importmap.remote_package_storage'), + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.importmap.resolver'), + ]) + + ->set('asset_mapper.importmap.version_checker', ImportMapVersionChecker::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.importmap.remote_package_downloader'), + ]) + + ->set('asset_mapper.importmap.resolver', JsDelivrEsmResolver::class) + ->args([service('http_client')]) + + ->set('asset_mapper.importmap.renderer', ImportMapRenderer::class) + ->args([ + service('asset_mapper.importmap.generator'), + service('assets.packages')->nullOnInvalid(), + param('kernel.charset'), + abstract_arg('polyfill URL'), + abstract_arg('script HTML attributes'), + service('request_stack'), + ]) + + ->set('asset_mapper.importmap.auditor', ImportMapAuditor::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + service('http_client'), + ]) + ->set('asset_mapper.importmap.update_checker', ImportMapUpdateChecker::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + service('http_client'), + ]) + + ->set('asset_mapper.importmap.command.require', ImportMapRequireCommand::class) + ->args([ + service('asset_mapper.importmap.manager'), + service('asset_mapper.importmap.version_checker'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.remove', ImportMapRemoveCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.update', ImportMapUpdateCommand::class) + ->args([ + service('asset_mapper.importmap.manager'), + service('asset_mapper.importmap.version_checker'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.install', ImportMapInstallCommand::class) + ->args([ + service('asset_mapper.importmap.remote_package_downloader'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.audit', ImportMapAuditCommand::class) + ->args([service('asset_mapper.importmap.auditor')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.outdated', ImportMapOutdatedCommand::class) + ->args([service('asset_mapper.importmap.update_checker')]) + ->tag('console.command') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/assets.php b/lib/symfony/framework-bundle/Resources/config/assets.php index 1e250aab4..245712186 100644 --- a/lib/symfony/framework-bundle/Resources/config/assets.php +++ b/lib/symfony/framework-bundle/Resources/config/assets.php @@ -18,7 +18,6 @@ use Symfony\Component\Asset\PathPackage; use Symfony\Component\Asset\UrlPackage; use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy; -use Symfony\Component\Asset\VersionStrategy\RemoteJsonManifestVersionStrategy; use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; return static function (ContainerConfigurator $container) { @@ -82,13 +81,5 @@ return static function (ContainerConfigurator $container) { service('http_client')->nullOnInvalid(), false, ]) - - ->set('assets.remote_json_manifest_version_strategy', RemoteJsonManifestVersionStrategy::class) - ->abstract() - ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "assets.json_manifest_version_strategy" instead.') - ->args([ - abstract_arg('manifest url'), - service('http_client'), - ]) ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/cache.php b/lib/symfony/framework-bundle/Resources/config/cache.php index e333e5f54..87207cf95 100644 --- a/lib/symfony/framework-bundle/Resources/config/cache.php +++ b/lib/symfony/framework-bundle/Resources/config/cache.php @@ -16,7 +16,6 @@ use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\MemcachedAdapter; @@ -40,6 +39,7 @@ return static function (ContainerConfigurator $container) { ->set('cache.app.taggable', TagAwareAdapter::class) ->args([service('cache.app')]) + ->tag('cache.taggable', ['pool' => 'cache.app']) ->set('cache.system') ->parent('cache.adapter.system') @@ -66,11 +66,21 @@ return static function (ContainerConfigurator $container) { ->private() ->tag('cache.pool') + ->set('cache.asset_mapper') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + ->set('cache.messenger.restart_workers_signal') ->parent('cache.app') ->private() ->tag('cache.pool') + ->set('cache.scheduler') + ->parent('cache.app') + ->private() + ->tag('cache.pool') + ->set('cache.adapter.system', AdapterInterface::class) ->abstract() ->factory([AbstractAdapter::class, 'createSystemCache']) @@ -95,22 +105,6 @@ return static function (ContainerConfigurator $container) { ->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset']) ->tag('monolog.logger', ['channel' => 'cache']) - ->set('cache.adapter.doctrine', DoctrineAdapter::class) - ->abstract() - ->args([ - abstract_arg('Doctrine provider service'), - '', // namespace - 0, // default lifetime - ]) - ->call('setLogger', [service('logger')->ignoreOnInvalid()]) - ->tag('cache.pool', [ - 'provider' => 'cache.default_doctrine_provider', - 'clearer' => 'cache.default_clearer', - 'reset' => 'reset', - ]) - ->tag('monolog.logger', ['channel' => 'cache']) - ->deprecate('symfony/framework-bundle', '5.4', 'The "%service_id%" service inherits from "cache.adapter.doctrine" which is deprecated.') - ->set('cache.adapter.filesystem', FilesystemAdapter::class) ->abstract() ->args([ @@ -257,9 +251,6 @@ return static function (ContainerConfigurator $container) { ->alias(CacheItemPoolInterface::class, 'cache.app') - ->alias(AdapterInterface::class, 'cache.app') - ->deprecate('symfony/framework-bundle', '5.4', sprintf('The "%%alias_id%%" alias is deprecated, use "%s" instead.', CacheItemPoolInterface::class)) - ->alias(CacheInterface::class, 'cache.app') ->alias(TagAwareCacheInterface::class, 'cache.app.taggable') diff --git a/lib/symfony/framework-bundle/Resources/config/collectors.php b/lib/symfony/framework-bundle/Resources/config/collectors.php index abf9ded5e..aa6d4e33c 100644 --- a/lib/symfony/framework-bundle/Resources/config/collectors.php +++ b/lib/symfony/framework-bundle/Resources/config/collectors.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector; +use Symfony\Component\Console\DataCollector\CommandDataCollector; use Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector; use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; use Symfony\Component\HttpKernel\DataCollector\EventDataCollector; @@ -30,7 +31,7 @@ return static function (ContainerConfigurator $container) { ->set('data_collector.request', RequestDataCollector::class) ->args([ - service('request_stack')->ignoreOnInvalid(), + service('.virtual_request_stack')->ignoreOnInvalid(), ]) ->tag('kernel.event_subscriber') ->tag('data_collector', ['template' => '@WebProfiler/Collector/request.html.twig', 'id' => 'request', 'priority' => 335]) @@ -47,8 +48,8 @@ return static function (ContainerConfigurator $container) { ->set('data_collector.events', EventDataCollector::class) ->args([ - service('debug.event_dispatcher')->ignoreOnInvalid(), - service('request_stack')->ignoreOnInvalid(), + tagged_iterator('event_dispatcher.dispatcher', 'name'), + service('.virtual_request_stack')->ignoreOnInvalid(), ]) ->tag('data_collector', ['template' => '@WebProfiler/Collector/events.html.twig', 'id' => 'events', 'priority' => 290]) @@ -56,7 +57,7 @@ return static function (ContainerConfigurator $container) { ->args([ service('logger')->ignoreOnInvalid(), sprintf('%s/%s', param('kernel.build_dir'), param('kernel.container_class')), - service('request_stack')->ignoreOnInvalid(), + service('.virtual_request_stack')->ignoreOnInvalid(), ]) ->tag('monolog.logger', ['channel' => 'profiler']) ->tag('data_collector', ['template' => '@WebProfiler/Collector/logger.html.twig', 'id' => 'logger', 'priority' => 300]) @@ -74,5 +75,8 @@ return static function (ContainerConfigurator $container) { ->set('data_collector.router', RouterDataCollector::class) ->tag('kernel.event_listener', ['event' => KernelEvents::CONTROLLER, 'method' => 'onKernelController']) ->tag('data_collector', ['template' => '@WebProfiler/Collector/router.html.twig', 'id' => 'router', 'priority' => 285]) + + ->set('.data_collector.command', CommandDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/command.html.twig', 'id' => 'command', 'priority' => 335]) ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/console.php b/lib/symfony/framework-bundle/Resources/config/console.php index 610a83add..334d20426 100644 --- a/lib/symfony/framework-bundle/Resources/config/console.php +++ b/lib/symfony/framework-bundle/Resources/config/console.php @@ -16,6 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand; use Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolInvalidateTagsCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand; use Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand; @@ -37,16 +38,21 @@ use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand; use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand; +use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber; use Symfony\Component\Console\EventListener\ErrorListener; +use Symfony\Component\Console\Messenger\RunCommandMessageHandler; use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand; use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; -use Symfony\Component\Messenger\Command\DebugCommand; +use Symfony\Component\Messenger\Command\DebugCommand as MessengerDebugCommand; use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand; use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand; use Symfony\Component\Messenger\Command\FailedMessagesShowCommand; use Symfony\Component\Messenger\Command\SetupTransportsCommand; +use Symfony\Component\Messenger\Command\StatsCommand; use Symfony\Component\Messenger\Command\StopWorkersCommand; +use Symfony\Component\Scheduler\Command\DebugCommand as SchedulerDebugCommand; +use Symfony\Component\Serializer\Command\DebugCommand as SerializerDebugCommand; use Symfony\Component\Translation\Command\TranslationPullCommand; use Symfony\Component\Translation\Command\TranslationPushCommand; use Symfony\Component\Translation\Command\XliffLintCommand; @@ -93,6 +99,12 @@ return static function (ContainerConfigurator $container) { ]) ->tag('console.command') + ->set('console.command.cache_pool_invalidate_tags', CachePoolInvalidateTagsCommand::class) + ->args([ + tagged_locator('cache.taggable', 'pool'), + ]) + ->tag('console.command') + ->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class) ->args([ service('cache.global_clearer'), @@ -152,6 +164,8 @@ return static function (ContainerConfigurator $container) { [], // Receiver names service('messenger.listener.reset_services')->nullOnInvalid(), [], // Bus names + service('messenger.rate_limiter_locator')->nullOnInvalid(), + null, ]) ->tag('console.command') ->tag('monolog.logger', ['channel' => 'messenger']) @@ -163,7 +177,7 @@ return static function (ContainerConfigurator $container) { ]) ->tag('console.command') - ->set('console.command.messenger_debug', DebugCommand::class) + ->set('console.command.messenger_debug', MessengerDebugCommand::class) ->args([ [], // Message to handlers mapping ]) @@ -181,14 +195,18 @@ return static function (ContainerConfigurator $container) { abstract_arg('Receivers'), service('messenger.routable_message_bus'), service('event_dispatcher'), - service('logger'), + service('logger')->nullOnInvalid(), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), + null, ]) ->tag('console.command') + ->tag('monolog.logger', ['channel' => 'messenger']) ->set('console.command.messenger_failed_messages_show', FailedMessagesShowCommand::class) ->args([ abstract_arg('Default failure receiver name'), abstract_arg('Receivers'), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), ]) ->tag('console.command') @@ -196,6 +214,20 @@ return static function (ContainerConfigurator $container) { ->args([ abstract_arg('Default failure receiver name'), abstract_arg('Receivers'), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.messenger_stats', StatsCommand::class) + ->args([ + service('messenger.receiver_locator'), + abstract_arg('Receivers names'), + ]) + ->tag('console.command') + + ->set('console.command.scheduler_debug', SchedulerDebugCommand::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), ]) ->tag('console.command') @@ -213,6 +245,12 @@ return static function (ContainerConfigurator $container) { ]) ->tag('console.command') + ->set('console.command.serializer_debug', SerializerDebugCommand::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + ]) + ->tag('console.command') + ->set('console.command.translation_debug', TranslationDebugCommand::class) ->args([ service('translator'), @@ -267,6 +305,9 @@ return static function (ContainerConfigurator $container) { ->tag('console.command', ['command' => 'translation:push']) ->set('console.command.workflow_dump', WorkflowDumpCommand::class) + ->args([ + tagged_locator('workflow', 'name'), + ]) ->tag('console.command') ->set('console.command.xliff_lint', XliffLintCommand::class) @@ -327,5 +368,18 @@ return static function (ContainerConfigurator $container) { service('secrets.local_vault')->ignoreOnInvalid(), ]) ->tag('console.command') + + ->set('console.messenger.application', Application::class) + ->share(false) + ->call('setAutoExit', [false]) + ->args([ + service('kernel'), + ]) + + ->set('console.messenger.execute_command_handler', RunCommandMessageHandler::class) + ->args([ + service('console.messenger.application'), + ]) + ->tag('messenger.message_handler') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/debug.php b/lib/symfony/framework-bundle/Resources/config/debug.php index cfaad8c1d..5c426653d 100644 --- a/lib/symfony/framework-bundle/Resources/config/debug.php +++ b/lib/symfony/framework-bundle/Resources/config/debug.php @@ -24,7 +24,7 @@ return static function (ContainerConfigurator $container) { service('debug.event_dispatcher.inner'), service('debug.stopwatch'), service('logger')->nullOnInvalid(), - service('request_stack')->nullOnInvalid(), + service('.virtual_request_stack')->nullOnInvalid(), ]) ->tag('monolog.logger', ['channel' => 'event']) ->tag('kernel.reset', ['method' => 'reset']) diff --git a/lib/symfony/framework-bundle/Resources/config/debug_prod.php b/lib/symfony/framework-bundle/Resources/config/debug_prod.php index f381b018f..691786ff6 100644 --- a/lib/symfony/framework-bundle/Resources/config/debug_prod.php +++ b/lib/symfony/framework-bundle/Resources/config/debug_prod.php @@ -11,26 +11,30 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\HttpKernel\Debug\ErrorHandlerConfigurator; use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; return static function (ContainerConfigurator $container) { $container->parameters()->set('debug.error_handler.throw_at', -1); $container->services() - ->set('debug.debug_handlers_listener', DebugHandlersListener::class) + ->set('debug.error_handler_configurator', ErrorHandlerConfigurator::class) + ->public() ->args([ - null, // Exception handler - service('monolog.logger.php')->nullOnInvalid(), + service('logger')->nullOnInvalid(), null, // Log levels map for enabled error levels param('debug.error_handler.throw_at'), param('kernel.debug'), param('kernel.debug'), - service('monolog.logger.deprecation')->nullOnInvalid(), + null, // Deprecation logger if different from the one above ]) - ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'php']) + ->set('debug.debug_handlers_listener', DebugHandlersListener::class) + ->args([null, param('kernel.runtime_mode.web')]) + ->tag('kernel.event_subscriber') + ->set('debug.file_link_formatter', FileLinkFormatter::class) ->args([param('debug.file_link_format')]) diff --git a/lib/symfony/framework-bundle/Resources/config/form.php b/lib/symfony/framework-bundle/Resources/config/form.php index e4c0745c6..3c936a284 100644 --- a/lib/symfony/framework-bundle/Resources/config/form.php +++ b/lib/symfony/framework-bundle/Resources/config/form.php @@ -19,8 +19,10 @@ use Symfony\Component\Form\Extension\Core\Type\ColorType; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension; use Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension; +use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; use Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension; use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension; @@ -59,9 +61,7 @@ return static function (ContainerConfigurator $container) { ->alias(FormRegistryInterface::class, 'form.registry') ->set('form.factory', FormFactory::class) - ->public() ->args([service('form.registry')]) - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->alias(FormFactoryInterface::class, 'form.factory') @@ -104,10 +104,8 @@ return static function (ContainerConfigurator $container) { ->tag('form.type') ->set('form.type.file', FileType::class) - ->public() ->args([service('translator')->ignoreOnInvalid()]) ->tag('form.type') - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->set('form.type.color', ColorType::class) ->args([service('translator')->ignoreOnInvalid()]) @@ -117,6 +115,10 @@ return static function (ContainerConfigurator $container) { ->args([service('translator')->ignoreOnInvalid()]) ->tag('form.type_extension', ['extended-type' => FormType::class]) + ->set('form.type_extension.form.html_sanitizer', TextTypeHtmlSanitizerExtension::class) + ->args([tagged_locator('html_sanitizer', 'sanitizer')]) + ->tag('form.type_extension', ['extended-type' => TextType::class]) + ->set('form.type_extension.form.http_foundation', FormTypeHttpFoundationExtension::class) ->args([service('form.type_extension.form.request_handler')]) ->tag('form.type_extension') @@ -130,7 +132,7 @@ return static function (ContainerConfigurator $container) { ->set('form.type_extension.form.validator', FormTypeValidatorExtension::class) ->args([ service('validator'), - true, + false, service('twig.form.renderer')->ignoreOnInvalid(), service('translator')->ignoreOnInvalid(), ]) diff --git a/lib/symfony/framework-bundle/Resources/config/html_sanitizer.php b/lib/symfony/framework-bundle/Resources/config/html_sanitizer.php new file mode 100644 index 000000000..175dc2e23 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/html_sanitizer.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('html_sanitizer.config.default', HtmlSanitizerConfig::class) + ->call('allowSafeElements', [], true) + + ->set('html_sanitizer.sanitizer.default', HtmlSanitizer::class) + ->args([service('html_sanitizer.config.default')]) + ->tag('html_sanitizer', ['sanitizer' => 'default']) + + ->alias('html_sanitizer', 'html_sanitizer.sanitizer.default') + ->alias(HtmlSanitizerInterface::class, 'html_sanitizer') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/http_client.php b/lib/symfony/framework-bundle/Resources/config/http_client.php index ba70b90ad..a4c78d0ec 100644 --- a/lib/symfony/framework-bundle/Resources/config/http_client.php +++ b/lib/symfony/framework-bundle/Resources/config/http_client.php @@ -11,18 +11,21 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Http\Client\HttpAsyncClient; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\HttplugClient; +use Symfony\Component\HttpClient\Messenger\PingWebhookMessageHandler; use Symfony\Component\HttpClient\Psr18Client; use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\UriTemplateHttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; return static function (ContainerConfigurator $container) { $container->services() - ->set('http_client', HttpClientInterface::class) + ->set('http_client.transport', HttpClientInterface::class) ->factory([HttpClient::class, 'create']) ->args([ [], // default options @@ -31,6 +34,10 @@ return static function (ContainerConfigurator $container) { ->call('setLogger', [service('logger')->ignoreOnInvalid()]) ->tag('monolog.logger', ['channel' => 'http_client']) ->tag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore']) + + ->set('http_client', HttpClientInterface::class) + ->factory('current') + ->args([[service('http_client.transport')]]) ->tag('http_client.client') ->alias(HttpClientInterface::class, 'http_client') @@ -44,13 +51,17 @@ return static function (ContainerConfigurator $container) { ->alias(ClientInterface::class, 'psr18.http_client') - ->set(\Http\Client\HttpClient::class, HttplugClient::class) + ->set('httplug.http_client', HttplugClient::class) ->args([ service('http_client'), service(ResponseFactoryInterface::class)->ignoreOnInvalid(), service(StreamFactoryInterface::class)->ignoreOnInvalid(), ]) + ->alias(HttpAsyncClient::class, 'httplug.http_client') + ->alias(\Http\Client\HttpClient::class, 'httplug.http_client') + ->deprecate('symfony/framework-bundle', '6.3', 'The "%alias_id%" service is deprecated, use "'.ClientInterface::class.'" instead.') + ->set('http_client.abstract_retry_strategy', GenericRetryStrategy::class) ->abstract() ->args([ @@ -60,5 +71,31 @@ return static function (ContainerConfigurator $container) { abstract_arg('max delay ms'), abstract_arg('jitter'), ]) + + ->set('http_client.uri_template', UriTemplateHttpClient::class) + ->decorate('http_client', null, 7) // Between TraceableHttpClient (5) and RetryableHttpClient (10) + ->args([ + service('.inner'), + service('http_client.uri_template_expander')->nullOnInvalid(), + abstract_arg('default vars'), + ]) + + ->set('http_client.uri_template_expander.guzzle', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [\GuzzleHttp\UriTemplate\UriTemplate::class, 'expand'], + ]) + + ->set('http_client.uri_template_expander.rize', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [inline_service(\Rize\UriTemplate::class), 'expand'], + ]) + + ->set('http_client.messenger.ping_webhook_handler', PingWebhookMessageHandler::class) + ->args([ + service('http_client'), + ]) + ->tag('messenger.message_handler') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/identity_translator.php b/lib/symfony/framework-bundle/Resources/config/identity_translator.php index a9066e1f0..5b8889915 100644 --- a/lib/symfony/framework-bundle/Resources/config/identity_translator.php +++ b/lib/symfony/framework-bundle/Resources/config/identity_translator.php @@ -17,8 +17,6 @@ use Symfony\Contracts\Translation\TranslatorInterface; return static function (ContainerConfigurator $container) { $container->services() ->set('translator', IdentityTranslator::class) - ->public() - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->alias(TranslatorInterface::class, 'translator') ->set('identity_translator', IdentityTranslator::class) diff --git a/lib/symfony/framework-bundle/Resources/config/mailer.php b/lib/symfony/framework-bundle/Resources/config/mailer.php index b15b29270..9eb545ca2 100644 --- a/lib/symfony/framework-bundle/Resources/config/mailer.php +++ b/lib/symfony/framework-bundle/Resources/config/mailer.php @@ -11,9 +11,11 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Mailer\Command\MailerTestCommand; use Symfony\Component\Mailer\EventListener\EnvelopeListener; use Symfony\Component\Mailer\EventListener\MessageListener; use Symfony\Component\Mailer\EventListener\MessageLoggerListener; +use Symfony\Component\Mailer\EventListener\MessengerTransportListener; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\Messenger\MessageHandler; @@ -69,13 +71,17 @@ return static function (ContainerConfigurator $container) { ]) ->tag('kernel.event_subscriber') - ->set('mailer.logger_message_listener', MessageLoggerListener::class) - ->tag('kernel.event_subscriber') - ->tag('kernel.reset', ['method' => 'reset']) - ->deprecate('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "mailer.message_logger_listener" instead.') - ->set('mailer.message_logger_listener', MessageLoggerListener::class) ->tag('kernel.event_subscriber') ->tag('kernel.reset', ['method' => 'reset']) + + ->set('mailer.messenger_transport_listener', MessengerTransportListener::class) + ->tag('kernel.event_subscriber') + + ->set('console.command.mailer_test', MailerTestCommand::class) + ->args([ + service('mailer.transports'), + ]) + ->tag('console.command') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/mailer_transports.php b/lib/symfony/framework-bundle/Resources/config/mailer_transports.php index 7bddfa756..ed6e644a5 100644 --- a/lib/symfony/framework-bundle/Resources/config/mailer_transports.php +++ b/lib/symfony/framework-bundle/Resources/config/mailer_transports.php @@ -12,12 +12,17 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory; use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\MailerSend\Transport\MailerSendTransportFactory; use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; +use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; use Symfony\Component\Mailer\Transport\AbstractTransportFactory; @@ -41,10 +46,22 @@ return static function (ContainerConfigurator $container) { ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.brevo', BrevoTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.gmail', GmailTransportFactory::class) ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.infobip', InfobipTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.mailersend', MailerSendTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.mailchimp', MandrillTransportFactory::class) ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') @@ -57,6 +74,10 @@ return static function (ContainerConfigurator $container) { ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.mailpace', MailPaceTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.postmark', PostmarkTransportFactory::class) ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') @@ -69,13 +90,17 @@ return static function (ContainerConfigurator $container) { ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.scaleway', ScalewayTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.sendmail', SendmailTransportFactory::class) ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') ->set('mailer.transport_factory.sendinblue', SendinblueTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') ->set('mailer.transport_factory.ohmysmtp', OhMySmtpTransportFactory::class) ->parent('mailer.transport_factory.abstract') diff --git a/lib/symfony/framework-bundle/Resources/config/mailer_webhook.php b/lib/symfony/framework-bundle/Resources/config/mailer_webhook.php new file mode 100644 index 000000000..30ea50dad --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/mailer_webhook.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\Bridge\Mailgun\RemoteEvent\MailgunPayloadConverter; +use Symfony\Component\Mailer\Bridge\Mailgun\Webhook\MailgunRequestParser; +use Symfony\Component\Mailer\Bridge\Postmark\RemoteEvent\PostmarkPayloadConverter; +use Symfony\Component\Mailer\Bridge\Postmark\Webhook\PostmarkRequestParser; +use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter; +use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.payload_converter.mailgun', MailgunPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailgun', MailgunRequestParser::class) + ->args([service('mailer.payload_converter.mailgun')]) + ->alias(MailgunRequestParser::class, 'mailer.webhook.request_parser.mailgun') + + ->set('mailer.payload_converter.postmark', PostmarkPayloadConverter::class) + ->set('mailer.webhook.request_parser.postmark', PostmarkRequestParser::class) + ->args([service('mailer.payload_converter.postmark')]) + ->alias(PostmarkRequestParser::class, 'mailer.webhook.request_parser.postmark') + + ->set('mailer.payload_converter.sendgrid', SendgridPayloadConverter::class) + ->set('mailer.webhook.request_parser.sendgrid', SendgridRequestParser::class) + ->args([service('mailer.payload_converter.sendgrid')]) + ->alias(SendgridRequestParser::class, 'mailer.webhook.request_parser.sendgrid') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/messenger.php b/lib/symfony/framework-bundle/Resources/config/messenger.php index 813d50300..5e4726265 100644 --- a/lib/symfony/framework-bundle/Resources/config/messenger.php +++ b/lib/symfony/framework-bundle/Resources/config/messenger.php @@ -23,7 +23,8 @@ use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener; use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener; use Symfony\Component\Messenger\EventListener\StopWorkerOnCustomStopExceptionListener; use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener; -use Symfony\Component\Messenger\EventListener\StopWorkerOnSigtermSignalListener; +use Symfony\Component\Messenger\EventListener\StopWorkerOnSignalsListener; +use Symfony\Component\Messenger\Handler\RedispatchMessageHandler; use Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware; use Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware; use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware; @@ -35,7 +36,7 @@ use Symfony\Component\Messenger\Middleware\TraceableMiddleware; use Symfony\Component\Messenger\Middleware\ValidationMiddleware; use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy; use Symfony\Component\Messenger\RoutableMessageBus; -use Symfony\Component\Messenger\Transport\InMemoryTransportFactory; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory; use Symfony\Component\Messenger\Transport\Sender\SendersLocator; use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; @@ -55,6 +56,7 @@ return static function (ContainerConfigurator $container) { abstract_arg('senders service locator'), ]) ->set('messenger.middleware.send_message', SendMessageMiddleware::class) + ->abstract() ->args([ service('messenger.senders_locator'), service('event_dispatcher'), @@ -74,6 +76,7 @@ return static function (ContainerConfigurator $container) { ->tag('serializer.normalizer', ['priority' => -880]) ->set('messenger.transport.native_php_serializer', PhpSerializer::class) + ->alias('messenger.default_serializer', 'messenger.transport.native_php_serializer') // Middleware ->set('messenger.middleware.handle_message', HandleMessageMiddleware::class) @@ -140,6 +143,7 @@ return static function (ContainerConfigurator $container) { ->args([ service('logger')->ignoreOnInvalid(), ]) + ->tag('monolog.logger', ['channel' => 'messenger']) ->set('messenger.transport.beanstalkd.factory', BeanstalkdTransportFactory::class) @@ -159,6 +163,11 @@ return static function (ContainerConfigurator $container) { abstract_arg('max delay ms'), ]) + // rate limiter + ->set('messenger.rate_limiter_locator', ServiceLocator::class) + ->args([[]]) + ->tag('container.service_locator') + // worker event listener ->set('messenger.retry.send_failed_message_for_retry_listener', SendFailedMessageForRetryListener::class) ->args([ @@ -192,11 +201,17 @@ return static function (ContainerConfigurator $container) { ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'messenger']) - ->set('messenger.listener.stop_worker_on_sigterm_signal_listener', StopWorkerOnSigtermSignalListener::class) + ->set('messenger.listener.stop_worker_signals_listener', StopWorkerOnSignalsListener::class) + ->deprecate('6.4', 'symfony/messenger', 'The "%service_id%" service is deprecated, use the "Symfony\Component\Console\Command\SignalableCommandInterface" instead.') ->args([ + null, service('logger')->ignoreOnInvalid(), ]) ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->alias('messenger.listener.stop_worker_on_sigterm_signal_listener', 'messenger.listener.stop_worker_signals_listener') + ->deprecate('6.3', 'symfony/messenger', 'The "%alias_id%" service is deprecated, use the "Symfony\Component\Console\Command\SignalableCommandInterface" instead.') ->set('messenger.listener.stop_worker_on_stop_exception_listener', StopWorkerOnCustomStopExceptionListener::class) ->tag('kernel.event_subscriber') @@ -211,5 +226,11 @@ return static function (ContainerConfigurator $container) { abstract_arg('message bus locator'), service('messenger.default_bus'), ]) + + ->set('messenger.redispatch_message_handler', RedispatchMessageHandler::class) + ->args([ + service('messenger.default_bus'), + ]) + ->tag('messenger.message_handler') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/notifier.php b/lib/symfony/framework-bundle/Resources/config/notifier.php index 73beb2c34..6ce674148 100644 --- a/lib/symfony/framework-bundle/Resources/config/notifier.php +++ b/lib/symfony/framework-bundle/Resources/config/notifier.php @@ -22,6 +22,7 @@ use Symfony\Component\Notifier\Chatter; use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\EventListener\NotificationLoggerListener; use Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener; +use Symfony\Component\Notifier\FlashMessage\DefaultFlashMessageImportanceMapper; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\PushMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -43,20 +44,32 @@ return static function (ContainerConfigurator $container) { ->set('notifier.channel_policy', ChannelPolicy::class) ->args([[]]) + ->set('notifier.flash_message_importance_mapper', DefaultFlashMessageImportanceMapper::class) + ->args([[]]) + ->set('notifier.channel.browser', BrowserChannel::class) - ->args([service('request_stack')]) + ->args([service('request_stack'), service('notifier.flash_message_importance_mapper')]) ->tag('notifier.channel', ['channel' => 'browser']) ->set('notifier.channel.chat', ChatChannel::class) - ->args([service('chatter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('chatter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'chat']) ->set('notifier.channel.sms', SmsChannel::class) - ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('texter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'sms']) ->set('notifier.channel.email', EmailChannel::class) - ->args([service('mailer.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('mailer.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'email']) ->set('notifier.channel.push', PushChannel::class) @@ -72,7 +85,7 @@ return static function (ContainerConfigurator $container) { ->set('chatter', Chatter::class) ->args([ service('chatter.transports'), - service('messenger.default_bus')->ignoreOnInvalid(), + abstract_arg('message bus'), service('event_dispatcher')->ignoreOnInvalid(), ]) @@ -92,7 +105,7 @@ return static function (ContainerConfigurator $container) { ->set('texter', Texter::class) ->args([ service('texter.transports'), - service('messenger.default_bus')->ignoreOnInvalid(), + abstract_arg('message bus'), service('event_dispatcher')->ignoreOnInvalid(), ]) @@ -113,7 +126,11 @@ return static function (ContainerConfigurator $container) { ->args([service('texter.transports')]) ->tag('messenger.message_handler', ['handles' => PushMessage::class]) - ->set('notifier.logger_notification_listener', NotificationLoggerListener::class) + ->set('notifier.notification_logger_listener', NotificationLoggerListener::class) ->tag('kernel.event_subscriber') + + ->alias('notifier.logger_notification_listener', 'notifier.notification_logger_listener') + ->deprecate('symfony/framework-bundle', '6.3', 'The "%alias_id%" service is deprecated, use "notifier.notification_logger_listener" instead.') + ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/notifier_debug.php b/lib/symfony/framework-bundle/Resources/config/notifier_debug.php index 6147d34e4..47eab26f9 100644 --- a/lib/symfony/framework-bundle/Resources/config/notifier_debug.php +++ b/lib/symfony/framework-bundle/Resources/config/notifier_debug.php @@ -16,7 +16,7 @@ use Symfony\Component\Notifier\DataCollector\NotificationDataCollector; return static function (ContainerConfigurator $container) { $container->services() ->set('notifier.data_collector', NotificationDataCollector::class) - ->args([service('notifier.logger_notification_listener')]) + ->args([service('notifier.notification_logger_listener')]) ->tag('data_collector', ['template' => '@WebProfiler/Collector/notifier.html.twig', 'id' => 'notifier']) ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/notifier_transports.php b/lib/symfony/framework-bundle/Resources/config/notifier_transports.php index c07028847..1a8936361 100644 --- a/lib/symfony/framework-bundle/Resources/config/notifier_transports.php +++ b/lib/symfony/framework-bundle/Resources/config/notifier_transports.php @@ -11,212 +11,162 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; -use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; -use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; -use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; -use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; -use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; -use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; -use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; -use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; -use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; -use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; -use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; -use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; -use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; -use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; -use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; -use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; -use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; -use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; -use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory; -use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; -use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; -use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; -use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; -use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; -use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; -use Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; -use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; -use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; -use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; -use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; -use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; -use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; -use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; -use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; -use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; -use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; +use Symfony\Component\Notifier\Bridge; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\NullTransportFactory; return static function (ContainerConfigurator $container) { $container->services() - ->alias('notifier.transport_factory.allmysms', 'notifier.transport_factory.all-my-sms') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.all-my-sms" instead.') - ->alias('notifier.transport_factory.fakechat', 'notifier.transport_factory.fake-chat') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.fake-chat" instead.') - ->alias('notifier.transport_factory.fakesms', 'notifier.transport_factory.fake-sms') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.fake-sms" instead.') - ->alias('notifier.transport_factory.freemobile', 'notifier.transport_factory.free-mobile') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.free-mobile" instead.') - ->alias('notifier.transport_factory.gatewayapi', 'notifier.transport_factory.gateway-api') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.gateway-api" instead.') - ->alias('notifier.transport_factory.googlechat', 'notifier.transport_factory.google-chat') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.google-chat" instead.') - ->alias('notifier.transport_factory.lightsms', 'notifier.transport_factory.light-sms') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.light-sms" instead.') - ->alias('notifier.transport_factory.linkedin', 'notifier.transport_factory.linked-in') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.linked-in" instead.') - ->alias('notifier.transport_factory.microsoftteams', 'notifier.transport_factory.microsoft-teams') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.microsoft-teams" instead.') - ->alias('notifier.transport_factory.onesignal', 'notifier.transport_factory.one-signal') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.one-signal" instead.') - ->alias('notifier.transport_factory.ovhcloud', 'notifier.transport_factory.ovh-cloud') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.ovh-cloud" instead.') - ->alias('notifier.transport_factory.rocketchat', 'notifier.transport_factory.rocket-chat') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.rocket-chat" instead.') - ->alias('notifier.transport_factory.spothit', 'notifier.transport_factory.spot-hit') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.spot-hit" instead.') ->set('notifier.transport_factory.abstract', AbstractTransportFactory::class) ->abstract() ->args([service('event_dispatcher'), service('http_client')->ignoreOnInvalid()]) - ->set('notifier.transport_factory.slack', SlackTransportFactory::class) + ->set('notifier.transport_factory.brevo', Bridge\Brevo\BrevoTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.slack', Bridge\Slack\SlackTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.linked-in', LinkedInTransportFactory::class) + ->set('notifier.transport_factory.linked-in', Bridge\LinkedIn\LinkedInTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.telegram', TelegramTransportFactory::class) + ->set('notifier.transport_factory.telegram', Bridge\Telegram\TelegramTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.mattermost', MattermostTransportFactory::class) + ->set('notifier.transport_factory.mattermost', Bridge\Mattermost\MattermostTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.nexmo', NexmoTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - ->deprecate('symfony/framework-bundle', '5.4', 'The "%service_id% service is deprecated, use "notifier.transport_factory.vonage" instead.') - - ->set('notifier.transport_factory.vonage', VonageTransportFactory::class) + ->set('notifier.transport_factory.vonage', Bridge\Vonage\VonageTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.rocket-chat', RocketChatTransportFactory::class) + ->set('notifier.transport_factory.rocket-chat', Bridge\RocketChat\RocketChatTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.google-chat', GoogleChatTransportFactory::class) + ->set('notifier.transport_factory.google-chat', Bridge\GoogleChat\GoogleChatTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.twilio', TwilioTransportFactory::class) + ->set('notifier.transport_factory.twilio', Bridge\Twilio\TwilioTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.all-my-sms', AllMySmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.firebase', FirebaseTransportFactory::class) + ->set('notifier.transport_factory.twitter', Bridge\Twitter\TwitterTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.free-mobile', FreeMobileTransportFactory::class) + ->set('notifier.transport_factory.all-my-sms', Bridge\AllMySms\AllMySmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.spot-hit', SpotHitTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.fake-chat', FakeChatTransportFactory::class) + ->set('notifier.transport_factory.firebase', Bridge\Firebase\FirebaseTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.fake-sms', FakeSmsTransportFactory::class) + ->set('notifier.transport_factory.forty-six-elks', Bridge\FortySixElks\FortySixElksTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.ovh-cloud', OvhCloudTransportFactory::class) + ->set('notifier.transport_factory.free-mobile', Bridge\FreeMobile\FreeMobileTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sinch', SinchTransportFactory::class) + ->set('notifier.transport_factory.spot-hit', Bridge\SpotHit\SpotHitTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.zulip', ZulipTransportFactory::class) + ->set('notifier.transport_factory.fake-chat', Bridge\FakeChat\FakeChatTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.infobip', InfobipTransportFactory::class) + ->set('notifier.transport_factory.fake-sms', Bridge\FakeSms\FakeSmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.mobyt', MobytTransportFactory::class) + ->set('notifier.transport_factory.ovh-cloud', Bridge\OvhCloud\OvhCloudTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.smsapi', SmsapiTransportFactory::class) + ->set('notifier.transport_factory.sinch', Bridge\Sinch\SinchTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.esendex', EsendexTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.sendinblue', SendinblueTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.iqsms', IqsmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.octopush', OctopushTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.discord', DiscordTransportFactory::class) + ->set('notifier.transport_factory.zulip', Bridge\Zulip\ZulipTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.microsoft-teams', MicrosoftTeamsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.gateway-api', GatewayApiTransportFactory::class) + ->set('notifier.transport_factory.infobip', Bridge\Infobip\InfobipTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.mercure', MercureTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.gitter', GitterTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.clickatell', ClickatellTransportFactory::class) + ->set('notifier.transport_factory.isendpro', Bridge\Isendpro\IsendproTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.amazon-sns', AmazonSnsTransportFactory::class) + ->set('notifier.transport_factory.mobyt', Bridge\Mobyt\MobytTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.smsapi', Bridge\Smsapi\SmsapiTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.esendex', Bridge\Esendex\EsendexTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sendberry', Bridge\Sendberry\SendberryTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sendinblue', Bridge\Sendinblue\SendinblueTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.iqsms', Bridge\Iqsms\IqsmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.octopush', Bridge\Octopush\OctopushTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.discord', Bridge\Discord\DiscordTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.microsoft-teams', Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.gateway-api', Bridge\GatewayApi\GatewayApiTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.mercure', Bridge\Mercure\MercureTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.gitter', Bridge\Gitter\GitterTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.clickatell', Bridge\Clickatell\ClickatellTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.contact-everyone', Bridge\ContactEveryone\ContactEveryoneTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.amazon-sns', Bridge\AmazonSns\AmazonSnsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') ->tag('chatter.transport_factory') @@ -226,51 +176,134 @@ return static function (ContainerConfigurator $container) { ->tag('chatter.transport_factory') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.light-sms', LightSmsTransportFactory::class) + ->set('notifier.transport_factory.light-sms', Bridge\LightSms\LightSmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sms-biuras', SmsBiurasTransportFactory::class) + ->set('notifier.transport_factory.sms-biuras', Bridge\SmsBiuras\SmsBiurasTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.smsc', SmscTransportFactory::class) + ->set('notifier.transport_factory.smsc', Bridge\Smsc\SmscTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.message-bird', MessageBirdTransportFactory::class) + ->set('notifier.transport_factory.sms-factor', Bridge\SmsFactor\SmsFactorTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.message-media', MessageMediaTransportFactory::class) + ->set('notifier.transport_factory.message-bird', Bridge\MessageBird\MessageBirdTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.telnyx', TelnyxTransportFactory::class) + ->set('notifier.transport_factory.message-media', Bridge\MessageMedia\MessageMediaTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.mailjet', MailjetTransportFactory::class) + ->set('notifier.transport_factory.telnyx', Bridge\Telnyx\TelnyxTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.yunpian', YunpianTransportFactory::class) + ->set('notifier.transport_factory.mailjet', Bridge\Mailjet\MailjetTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.turbo-sms', TurboSmsTransportFactory::class) + ->set('notifier.transport_factory.yunpian', Bridge\Yunpian\YunpianTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sms77', Sms77TransportFactory::class) + ->set('notifier.transport_factory.turbo-sms', Bridge\TurboSms\TurboSmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.one-signal', OneSignalTransportFactory::class) + ->set('notifier.transport_factory.sms77', Bridge\Sms77\Sms77TransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.expo', ExpoTransportFactory::class) + ->set('notifier.transport_factory.one-signal', Bridge\OneSignal\OneSignalTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.orange-sms', Bridge\OrangeSms\OrangeSmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.expo', Bridge\Expo\ExpoTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.kaz-info-teh', Bridge\KazInfoTeh\KazInfoTehTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.engagespot', Bridge\Engagespot\EngagespotTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.zendesk', Bridge\Zendesk\ZendeskTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.chatwork', Bridge\Chatwork\ChatworkTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.termii', Bridge\Termii\TermiiTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.ring-central', Bridge\RingCentral\RingCentralTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.plivo', Bridge\Plivo\PlivoTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.bandwidth', Bridge\Bandwidth\BandwidthTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.line-notify', Bridge\LineNotify\LineNotifyTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.mastodon', Bridge\Mastodon\MastodonTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.pager-duty', Bridge\PagerDuty\PagerDutyTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.pushover', Bridge\Pushover\PushoverTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.simple-textin', Bridge\SimpleTextin\SimpleTextinTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.click-send', Bridge\ClickSend\ClickSendTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.smsmode', Bridge\Smsmode\SmsmodeTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.novu', Bridge\Novu\NovuTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.ntfy', Bridge\Ntfy\NtfyTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.redlink', Bridge\Redlink\RedlinkTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.go-ip', Bridge\GoIp\GoIpTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') ; diff --git a/lib/symfony/framework-bundle/Resources/config/notifier_webhook.php b/lib/symfony/framework-bundle/Resources/config/notifier_webhook.php new file mode 100644 index 000000000..87dfc6c6a --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/notifier_webhook.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Notifier\Bridge\Twilio\Webhook\TwilioRequestParser; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('notifier.webhook.request_parser.twilio', TwilioRequestParser::class) + ->alias(TwilioRequestParser::class, 'notifier.webhook.request_parser.twilio') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/process.php b/lib/symfony/framework-bundle/Resources/config/process.php new file mode 100644 index 000000000..909b84831 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/process.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Process\Messenger\RunProcessMessageHandler; + +return static function (ContainerConfigurator $container) { + $container + ->services() + ->set('process.messenger.process_message_handler', RunProcessMessageHandler::class) + ->tag('messenger.message_handler') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/profiling.php b/lib/symfony/framework-bundle/Resources/config/profiling.php index 221217896..ec764d837 100644 --- a/lib/symfony/framework-bundle/Resources/config/profiling.php +++ b/lib/symfony/framework-bundle/Resources/config/profiling.php @@ -11,6 +11,8 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Bundle\FrameworkBundle\EventListener\ConsoleProfilerListener; +use Symfony\Component\HttpKernel\Debug\VirtualRequestStack; use Symfony\Component\HttpKernel\EventListener\ProfilerListener; use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; use Symfony\Component\HttpKernel\Profiler\Profiler; @@ -35,5 +37,18 @@ return static function (ContainerConfigurator $container) { param('profiler_listener.only_main_requests'), ]) ->tag('kernel.event_subscriber') + + ->set('console_profiler_listener', ConsoleProfilerListener::class) + ->args([ + service('profiler'), + service('.virtual_request_stack'), + service('debug.stopwatch'), + service('router'), + ]) + ->tag('kernel.event_subscriber') + + ->set('.virtual_request_stack', VirtualRequestStack::class) + ->args([service('request_stack')]) + ->public() ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/remote_event.php b/lib/symfony/framework-bundle/Resources/config/remote_event.php new file mode 100644 index 000000000..41209314b --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/remote_event.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\RemoteEvent\Messenger\ConsumeRemoteEventHandler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('remote_event.messenger.handler', ConsumeRemoteEventHandler::class) + ->args([ + tagged_locator('remote_event.consumer', 'consumer'), + ]) + ->tag('messenger.message_handler') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/routing.php b/lib/symfony/framework-bundle/Resources/config/routing.php index 09e340ff8..5fc0cbb3e 100644 --- a/lib/symfony/framework-bundle/Resources/config/routing.php +++ b/lib/symfony/framework-bundle/Resources/config/routing.php @@ -15,6 +15,7 @@ use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer; use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Bundle\FrameworkBundle\Routing\AttributeRouteControllerLoader; use Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader; use Symfony\Bundle\FrameworkBundle\Routing\RedirectableCompiledUrlMatcher; use Symfony\Bundle\FrameworkBundle\Routing\Router; @@ -23,10 +24,13 @@ use Symfony\Component\HttpKernel\EventListener\RouterListener; use Symfony\Component\Routing\Generator\CompiledUrlGenerator; use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Loader\AttributeDirectoryLoader; +use Symfony\Component\Routing\Loader\AttributeFileLoader; use Symfony\Component\Routing\Loader\ContainerLoader; use Symfony\Component\Routing\Loader\DirectoryLoader; use Symfony\Component\Routing\Loader\GlobFileLoader; use Symfony\Component\Routing\Loader\PhpFileLoader; +use Symfony\Component\Routing\Loader\Psr4DirectoryLoader; use Symfony\Component\Routing\Loader\XmlFileLoader; use Symfony\Component\Routing\Loader\YamlFileLoader; use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; @@ -88,6 +92,41 @@ return static function (ContainerConfigurator $container) { ]) ->tag('routing.loader') + ->set('routing.loader.attribute', AttributeRouteControllerLoader::class) + ->args([ + '%kernel.environment%', + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->alias('routing.loader.annotation', 'routing.loader.attribute') + ->deprecate('symfony/routing', '6.4', 'The "%alias_id%" service is deprecated, use the "routing.loader.attribute" service instead.') + + ->set('routing.loader.attribute.directory', AttributeDirectoryLoader::class) + ->args([ + service('file_locator'), + service('routing.loader.attribute'), + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->alias('routing.loader.annotation.directory', 'routing.loader.attribute.directory') + ->deprecate('symfony/routing', '6.4', 'The "%alias_id%" service is deprecated, use the "routing.loader.attribute.directory" service instead.') + + ->set('routing.loader.attribute.file', AttributeFileLoader::class) + ->args([ + service('file_locator'), + service('routing.loader.attribute'), + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->alias('routing.loader.annotation.file', 'routing.loader.attribute.file') + ->deprecate('symfony/routing', '6.4', 'The "%alias_id%" service is deprecated, use the "routing.loader.attribute.file" service instead.') + + ->set('routing.loader.psr4', Psr4DirectoryLoader::class) + ->args([ + service('file_locator'), + ]) + ->tag('routing.loader', ['priority' => -10]) + ->set('routing.loader', DelegatingLoader::class) ->public() ->args([ @@ -101,7 +140,7 @@ return static function (ContainerConfigurator $container) { service(ContainerInterface::class), param('router.resource'), [ - 'cache_dir' => param('kernel.cache_dir'), + 'cache_dir' => param('router.cache_dir'), 'debug' => param('kernel.debug'), 'generator_class' => CompiledUrlGenerator::class, 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, diff --git a/lib/symfony/framework-bundle/Resources/config/routing/webhook.xml b/lib/symfony/framework-bundle/Resources/config/routing/webhook.xml new file mode 100644 index 000000000..dfa95cfac --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/routing/webhook.xml @@ -0,0 +1,11 @@ + + + + + + webhook.controller::handle + .+ + + diff --git a/lib/symfony/framework-bundle/Resources/config/scheduler.php b/lib/symfony/framework-bundle/Resources/config/scheduler.php new file mode 100644 index 000000000..7b2856d82 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/scheduler.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Scheduler\EventListener\DispatchSchedulerEventListener; +use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; +use Symfony\Component\Scheduler\Messenger\ServiceCallMessageHandler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('scheduler.messenger.service_call_message_handler', ServiceCallMessageHandler::class) + ->args([ + tagged_locator('scheduler.task'), + ]) + ->tag('messenger.message_handler') + ->set('scheduler.messenger_transport_factory', SchedulerTransportFactory::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), + service('clock'), + ]) + ->tag('messenger.transport_factory') + ->set('scheduler.event_listener', DispatchSchedulerEventListener::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), + service('event_dispatcher'), + ]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd b/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd index d4be9fb6f..6483732ef 100644 --- a/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd +++ b/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd @@ -10,6 +10,7 @@ + @@ -24,6 +25,7 @@ + @@ -32,6 +34,7 @@ + @@ -39,10 +42,15 @@ + + + + + @@ -101,6 +109,7 @@ + @@ -111,6 +120,7 @@ + @@ -178,11 +188,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -191,6 +238,7 @@ + @@ -223,6 +271,7 @@ + @@ -265,6 +314,10 @@ + + + + @@ -272,6 +325,7 @@ + @@ -301,6 +355,7 @@ + @@ -398,8 +453,6 @@ - - @@ -439,7 +492,7 @@ - + @@ -449,6 +502,12 @@ + + + + + + @@ -505,6 +564,21 @@ + + + + + + + + + + + + + + + @@ -552,6 +626,7 @@ + @@ -564,10 +639,11 @@ + - + @@ -593,6 +669,7 @@ + @@ -618,6 +695,7 @@ + @@ -721,6 +799,7 @@ + @@ -748,7 +827,7 @@ - + @@ -826,4 +905,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/symfony/framework-bundle/Resources/config/security_csrf.php b/lib/symfony/framework-bundle/Resources/config/security_csrf.php index 9644d5b44..bad2284bf 100644 --- a/lib/symfony/framework-bundle/Resources/config/security_csrf.php +++ b/lib/symfony/framework-bundle/Resources/config/security_csrf.php @@ -32,13 +32,11 @@ return static function (ContainerConfigurator $container) { ->alias(TokenStorageInterface::class, 'security.csrf.token_storage') ->set('security.csrf.token_manager', CsrfTokenManager::class) - ->public() ->args([ service('security.csrf.token_generator'), service('security.csrf.token_storage'), service('request_stack')->ignoreOnInvalid(), ]) - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->alias(CsrfTokenManagerInterface::class, 'security.csrf.token_manager') diff --git a/lib/symfony/framework-bundle/Resources/config/semaphore.php b/lib/symfony/framework-bundle/Resources/config/semaphore.php new file mode 100644 index 000000000..ce35c2508 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/semaphore.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Semaphore\SemaphoreFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('semaphore.factory.abstract', SemaphoreFactory::class)->abstract() + ->args([abstract_arg('Store')]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'semaphore']) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/serializer.php b/lib/symfony/framework-bundle/Resources/config/serializer.php index ca0cf9b53..2311df0fc 100644 --- a/lib/symfony/framework-bundle/Resources/config/serializer.php +++ b/lib/symfony/framework-bundle/Resources/config/serializer.php @@ -42,10 +42,12 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Normalizer\TranslatableNormalizer; use Symfony\Component\Serializer\Normalizer\UidNormalizer; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use Symfony\Component\Serializer\Serializer; @@ -58,9 +60,7 @@ return static function (ContainerConfigurator $container) { $container->services() ->set('serializer', Serializer::class) - ->public() ->args([[], []]) - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->alias(SerializerInterface::class, 'serializer') ->alias(NormalizerInterface::class, 'serializer') @@ -104,7 +104,7 @@ return static function (ContainerConfigurator $container) { ->tag('serializer.normalizer', ['priority' => -950]) ->set('serializer.normalizer.problem', ProblemNormalizer::class) - ->args([param('kernel.debug')]) + ->args([param('kernel.debug'), '$translator' => service('translator')->nullOnInvalid()]) ->tag('serializer.normalizer', ['priority' => -890]) ->set('serializer.denormalizer.unwrapping', UnwrappingDenormalizer::class) @@ -114,6 +114,10 @@ return static function (ContainerConfigurator $container) { ->set('serializer.normalizer.uid', UidNormalizer::class) ->tag('serializer.normalizer', ['priority' => -890]) + ->set('serializer.normalizer.translatable', TranslatableNormalizer::class) + ->args(['$translator' => service('translator')]) + ->tag('serializer.normalizer', ['priority' => -890]) + ->set('serializer.normalizer.form_error', FormErrorNormalizer::class) ->tag('serializer.normalizer', ['priority' => -915]) @@ -129,6 +133,7 @@ return static function (ContainerConfigurator $container) { ->tag('serializer.normalizer', ['priority' => -1000]) ->alias(ObjectNormalizer::class, 'serializer.normalizer.object') + ->deprecate('symfony/serializer', '6.2', 'The "%alias_id%" service alias is deprecated, type-hint against "'.NormalizerInterface::class.'" or implement "'.NormalizerAwareInterface::class.'" instead.') ->set('serializer.normalizer.property', PropertyNormalizer::class) ->args([ @@ -140,6 +145,7 @@ return static function (ContainerConfigurator $container) { ]) ->alias(PropertyNormalizer::class, 'serializer.normalizer.property') + ->deprecate('symfony/serializer', '6.2', 'The "%alias_id%" service alias is deprecated, type-hint against "'.NormalizerInterface::class.'" or implement "'.NormalizerAwareInterface::class.'" instead.') ->set('serializer.denormalizer.array', ArrayDenormalizer::class) ->tag('serializer.normalizer', ['priority' => -990]) @@ -211,12 +217,8 @@ return static function (ContainerConfigurator $container) { ->factory([HtmlErrorRenderer::class, 'isDebug']) ->args([service('request_stack'), param('kernel.debug')]), ]) - ; - if (interface_exists(\BackedEnum::class)) { - $container->services() - ->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class) + ->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class) ->tag('serializer.normalizer', ['priority' => -915]) - ; - } + ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/serializer_debug.php b/lib/symfony/framework-bundle/Resources/config/serializer_debug.php new file mode 100644 index 000000000..45b764fdd --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/serializer_debug.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Debug\TraceableSerializer; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.serializer', TraceableSerializer::class) + ->decorate('serializer') + ->args([ + service('debug.serializer.inner'), + service('serializer.data_collector'), + ]) + + ->set('serializer.data_collector', SerializerDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/serializer.html.twig', + 'id' => 'serializer', + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/services.php b/lib/symfony/framework-bundle/Resources/config/services.php index a26dfb5ad..905e16f9b 100644 --- a/lib/symfony/framework-bundle/Resources/config/services.php +++ b/lib/symfony/framework-bundle/Resources/config/services.php @@ -11,8 +11,13 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Psr\Clock\ClockInterface as PsrClockInterface; +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; +use Symfony\Component\Clock\Clock; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; use Symfony\Component\Config\ResourceCheckerConfigCacheFactory; use Symfony\Component\Console\ConsoleEvents; @@ -26,7 +31,11 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface as EventDispatcherInterfaceComponentAlias; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Form\FormEvents; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\UriSigner; use Symfony\Component\HttpFoundation\UrlHelper; use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; @@ -39,7 +48,7 @@ use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\HttpKernel\UriSigner as HttpKernelUriSigner; use Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner; use Symfony\Component\Runtime\Runner\Symfony\ResponseRunner; use Symfony\Component\Runtime\SymfonyRuntime; @@ -73,6 +82,7 @@ return static function (ContainerConfigurator $container) { ->tag('event_dispatcher.dispatcher', ['name' => 'event_dispatcher']) ->alias(EventDispatcherInterfaceComponentAlias::class, 'event_dispatcher') ->alias(EventDispatcherInterface::class, 'event_dispatcher') + ->alias(PsrEventDispatcherInterface::class, 'event_dispatcher') ->set('http_kernel', HttpKernel::class) ->public() @@ -81,6 +91,7 @@ return static function (ContainerConfigurator $container) { service('controller_resolver'), service('request_stack'), service('argument_resolver'), + false, ]) ->tag('container.hot_path') ->tag('container.preload', ['class' => HttpKernelRunner::class]) @@ -110,7 +121,7 @@ return static function (ContainerConfigurator $container) { ->set('url_helper', UrlHelper::class) ->args([ service('request_stack'), - service('router.request_context')->ignoreOnInvalid(), + service('router')->ignoreOnInvalid(), ]) ->alias(UrlHelper::class, 'url_helper') @@ -124,11 +135,9 @@ return static function (ContainerConfigurator $container) { ->tag('container.no_preload') ->set('cache_clearer', ChainCacheClearer::class) - ->public() ->args([ tagged_iterator('kernel.cache_clearer'), ]) - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->set('kernel') ->synthetic() @@ -136,8 +145,6 @@ return static function (ContainerConfigurator $container) { ->alias(KernelInterface::class, 'kernel') ->set('filesystem', Filesystem::class) - ->public() - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->alias(Filesystem::class, 'filesystem') ->set('file_locator', FileLocator::class) @@ -151,6 +158,8 @@ return static function (ContainerConfigurator $container) { param('kernel.secret'), ]) ->alias(UriSigner::class, 'uri_signer') + ->alias(HttpKernelUriSigner::class, 'uri_signer') + ->deprecate('symfony/framework-bundle', '6.4', 'The "%alias_id%" alias is deprecated, use "'.UriSigner::class.'" instead.') ->set('config_cache_factory', ResourceCheckerConfigCacheFactory::class) ->args([ @@ -204,6 +213,14 @@ return static function (ContainerConfigurator $container) { ]) ->tag('routing.expression_language_function', ['function' => 'env']) + ->set('container.get_routing_condition_service', \Closure::class) + ->public() + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [tagged_locator('routing.condition_service', 'alias'), 'get'], + ]) + ->tag('routing.expression_language_function', ['function' => 'service']) + // inherit from this service to lazily access env vars ->set('container.env', LazyString::class) ->abstract() @@ -214,5 +231,15 @@ return static function (ContainerConfigurator $container) { ->set('config_builder.warmer', ConfigBuilderCacheWarmer::class) ->args([service(KernelInterface::class), service('logger')->nullOnInvalid()]) ->tag('kernel.cache_warmer') + + ->set('clock', Clock::class) + ->alias(ClockInterface::class, 'clock') + ->alias(PsrClockInterface::class, 'clock') + + // register as abstract and excluded, aka not-autowirable types + ->set(LoaderInterface::class)->abstract()->tag('container.excluded') + ->set(Request::class)->abstract()->tag('container.excluded') + ->set(Response::class)->abstract()->tag('container.excluded') + ->set(SessionInterface::class)->abstract()->tag('container.excluded') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/session.php b/lib/symfony/framework-bundle/Resources/config/session.php index 43c0000dd..907b1b584 100644 --- a/lib/symfony/framework-bundle/Resources/config/session.php +++ b/lib/symfony/framework-bundle/Resources/config/session.php @@ -11,14 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use Symfony\Bundle\FrameworkBundle\Session\DeprecatedSessionFactory; -use Symfony\Bundle\FrameworkBundle\Session\ServiceSessionFactory; -use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; -use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; -use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; -use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\SessionFactory; -use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller; use Symfony\Component\HttpFoundation\Session\Storage\Handler\MarshallingSessionHandler; @@ -26,21 +19,15 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHa use Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerFactory; use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; -use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorageFactory; -use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorageFactory; -use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorageFactory; -use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; use Symfony\Component\HttpKernel\EventListener\SessionListener; return static function (ContainerConfigurator $container) { $container->parameters()->set('session.metadata.storage_key', '_sf2_meta'); $container->services() - ->set('.session.do-not-use', Session::class) // to be removed in 6.0 - ->factory([service('session.factory'), 'createSession']) ->set('session.factory', SessionFactory::class) ->args([ service('request_stack'), @@ -79,59 +66,13 @@ return static function (ContainerConfigurator $container) { param('session.metadata.update_threshold'), ]), ]) - ->set('session.storage.factory.service', ServiceSessionFactory::class) - ->args([ - service('session.storage'), - ]) - ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.native", "session.storage.factory.php_bridge" or "session.storage.factory.mock_file" instead.') - ->set('.session.deprecated', SessionInterface::class) // to be removed in 6.0 - ->factory([inline_service(DeprecatedSessionFactory::class)->args([service('request_stack')]), 'getSession']) - ->alias(SessionInterface::class, '.session.do-not-use') - ->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" and "SessionInterface" aliases are deprecated, use "$requestStack->getSession()" instead.') - ->alias(SessionStorageInterface::class, 'session.storage') - ->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use "session.storage.factory" instead.') ->alias(\SessionHandlerInterface::class, 'session.handler') - ->set('session.storage.metadata_bag', MetadataBag::class) + ->set('session.handler.native', StrictSessionHandler::class) ->args([ - param('session.metadata.storage_key'), - param('session.metadata.update_threshold'), + inline_service(\SessionHandler::class), ]) - ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, create your own "session.storage.factory" instead.') - - ->set('session.storage.native', NativeSessionStorage::class) - ->args([ - param('session.storage.options'), - service('session.handler'), - service('session.storage.metadata_bag'), - ]) - ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.native" instead.') - - ->set('session.storage.php_bridge', PhpBridgeSessionStorage::class) - ->args([ - service('session.handler'), - service('session.storage.metadata_bag'), - ]) - ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.php_bridge" instead.') - - ->set('session.flash_bag', FlashBag::class) - ->factory([service('.session.do-not-use'), 'getFlashBag']) - ->deprecate('symfony/framework-bundle', '5.1', 'The "%service_id%" service is deprecated, use "$session->getFlashBag()" instead.') - ->alias(FlashBagInterface::class, 'session.flash_bag') - - ->set('session.attribute_bag', AttributeBag::class) - ->factory([service('.session.do-not-use'), 'getBag']) - ->args(['attributes']) - ->deprecate('symfony/framework-bundle', '5.1', 'The "%service_id%" service is deprecated, use "$session->getAttributeBag()" instead.') - - ->set('session.storage.mock_file', MockFileSessionStorage::class) - ->args([ - param('kernel.cache_dir').'/sessions', - 'MOCKSESSID', - service('session.storage.metadata_bag'), - ]) - ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.mock_file" instead.') ->set('session.handler.native_file', StrictSessionHandler::class) ->args([ @@ -141,14 +82,12 @@ return static function (ContainerConfigurator $container) { ->set('session.abstract_handler', AbstractSessionHandler::class) ->factory([SessionHandlerFactory::class, 'createHandler']) - ->args([abstract_arg('A string or a connection object')]) + ->args([abstract_arg('A string or a connection object'), []]) ->set('session_listener', SessionListener::class) ->args([ service_locator([ 'session_factory' => service('session.factory')->ignoreOnInvalid(), - 'session' => service('.session.do-not-use')->ignoreOnInvalid(), - 'initialized_session' => service('.session.do-not-use')->ignoreOnUninitialized(), 'logger' => service('logger')->ignoreOnInvalid(), 'session_collector' => service('data_collector.request.session_collector')->ignoreOnInvalid(), ]), @@ -158,10 +97,6 @@ return static function (ContainerConfigurator $container) { ->tag('kernel.event_subscriber') ->tag('kernel.reset', ['method' => 'reset']) - // for BC - ->alias('session.storage.filesystem', 'session.storage.mock_file') - ->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use "session.storage.factory.mock_file" instead.') - ->set('session.marshaller', IdentityMarshaller::class) ->set('session.marshalling_handler', MarshallingSessionHandler::class) diff --git a/lib/symfony/framework-bundle/Resources/config/test.php b/lib/symfony/framework-bundle/Resources/config/test.php index 76709595b..cef5dfc4c 100644 --- a/lib/symfony/framework-bundle/Resources/config/test.php +++ b/lib/symfony/framework-bundle/Resources/config/test.php @@ -38,7 +38,6 @@ return static function (ContainerConfigurator $container) { ->set('test.session.listener', SessionListener::class) ->args([ service_locator([ - 'session' => service('.session.do-not-use')->ignoreOnInvalid(), 'session_factory' => service('session.factory')->ignoreOnInvalid(), ]), param('kernel.debug'), diff --git a/lib/symfony/framework-bundle/Resources/config/translation.php b/lib/symfony/framework-bundle/Resources/config/translation.php index 706e4928e..dcfa2bc15 100644 --- a/lib/symfony/framework-bundle/Resources/config/translation.php +++ b/lib/symfony/framework-bundle/Resources/config/translation.php @@ -26,7 +26,11 @@ use Symfony\Component\Translation\Dumper\XliffFileDumper; use Symfony\Component\Translation\Dumper\YamlFileDumper; use Symfony\Component\Translation\Extractor\ChainExtractor; use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\Extractor\PhpAstExtractor; use Symfony\Component\Translation\Extractor\PhpExtractor; +use Symfony\Component\Translation\Extractor\Visitor\ConstraintVisitor; +use Symfony\Component\Translation\Extractor\Visitor\TranslatableMessageVisitor; +use Symfony\Component\Translation\Extractor\Visitor\TransMethodVisitor; use Symfony\Component\Translation\Formatter\MessageFormatter; use Symfony\Component\Translation\Loader\CsvFileLoader; use Symfony\Component\Translation\Loader\IcuDatFileLoader; @@ -39,11 +43,13 @@ use Symfony\Component\Translation\Loader\PoFileLoader; use Symfony\Component\Translation\Loader\QtFileLoader; use Symfony\Component\Translation\Loader\XliffFileLoader; use Symfony\Component\Translation\Loader\YamlFileLoader; +use Symfony\Component\Translation\LocaleSwitcher; use Symfony\Component\Translation\LoggingTranslator; use Symfony\Component\Translation\Reader\TranslationReader; use Symfony\Component\Translation\Reader\TranslationReaderInterface; use Symfony\Component\Translation\Writer\TranslationWriter; use Symfony\Component\Translation\Writer\TranslationWriterInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; return static function (ContainerConfigurator $container) { @@ -114,6 +120,10 @@ return static function (ContainerConfigurator $container) { ->set('translation.dumper.xliff', XliffFileDumper::class) ->tag('translation.dumper', ['alias' => 'xlf']) + ->set('translation.dumper.xliff.xliff', XliffFileDumper::class) + ->args(['xliff']) + ->tag('translation.dumper', ['alias' => 'xliff']) + ->set('translation.dumper.po', PoFileDumper::class) ->tag('translation.dumper', ['alias' => 'po']) @@ -143,8 +153,22 @@ return static function (ContainerConfigurator $container) { ->tag('translation.dumper', ['alias' => 'res']) ->set('translation.extractor.php', PhpExtractor::class) + ->deprecate('symfony/framework-bundle', '6.2', 'The "%service_id%" service is deprecated, use "translation.extractor.php_ast" instead.') ->tag('translation.extractor', ['alias' => 'php']) + ->set('translation.extractor.php_ast', PhpAstExtractor::class) + ->args([tagged_iterator('translation.extractor.visitor')]) + ->tag('translation.extractor', ['alias' => 'php']) + + ->set('translation.extractor.visitor.trans_method', TransMethodVisitor::class) + ->tag('translation.extractor.visitor') + + ->set('translation.extractor.visitor.translatable_message', TranslatableMessageVisitor::class) + ->tag('translation.extractor.visitor') + + ->set('translation.extractor.visitor.constraint', ConstraintVisitor::class) + ->tag('translation.extractor.visitor') + ->set('translation.reader', TranslationReader::class) ->alias(TranslationReaderInterface::class, 'translation.reader') @@ -158,5 +182,16 @@ return static function (ContainerConfigurator $container) { ->args([service(ContainerInterface::class)]) ->tag('container.service_subscriber', ['id' => 'translator']) ->tag('kernel.cache_warmer') + + ->set('translation.locale_switcher', LocaleSwitcher::class) + ->args([ + param('kernel.default_locale'), + tagged_iterator('kernel.locale_aware', exclude: 'translation.locale_switcher'), + service('router.request_context')->ignoreOnInvalid(), + ]) + ->tag('kernel.reset', ['method' => 'reset']) + ->tag('kernel.locale_aware') + ->alias(LocaleAwareInterface::class, 'translation.locale_switcher') + ->alias(LocaleSwitcher::class, 'translation.locale_switcher') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/translation_providers.php b/lib/symfony/framework-bundle/Resources/config/translation_providers.php index cd140f077..ccb682084 100644 --- a/lib/symfony/framework-bundle/Resources/config/translation_providers.php +++ b/lib/symfony/framework-bundle/Resources/config/translation_providers.php @@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; +use Symfony\Component\Translation\Bridge\Phrase\PhraseProviderFactory; use Symfony\Component\Translation\Provider\NullProviderFactory; use Symfony\Component\Translation\Provider\TranslationProviderCollection; use Symfony\Component\Translation\Provider\TranslationProviderCollectionFactory; @@ -51,6 +52,7 @@ return static function (ContainerConfigurator $container) { service('logger'), param('kernel.default_locale'), service('translation.loader.xliff'), + service('translator'), ]) ->tag('translation.provider_factory') @@ -62,5 +64,16 @@ return static function (ContainerConfigurator $container) { service('translation.loader.xliff'), ]) ->tag('translation.provider_factory') + + ->set('translation.provider_factory.phrase', PhraseProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + service('translation.loader.xliff'), + service('translation.dumper.xliff'), + service('cache.app'), + param('kernel.default_locale'), + ]) + ->tag('translation.provider_factory') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/validator.php b/lib/symfony/framework-bundle/Resources/config/validator.php index 700ea69ed..adde2de23 100644 --- a/lib/symfony/framework-bundle/Resources/config/validator.php +++ b/lib/symfony/framework-bundle/Resources/config/validator.php @@ -15,8 +15,11 @@ use Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer; use Symfony\Component\Cache\Adapter\PhpArrayAdapter; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Validator\Constraints\EmailValidator; +use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider; use Symfony\Component\Validator\Constraints\ExpressionValidator; +use Symfony\Component\Validator\Constraints\NoSuspiciousCharactersValidator; use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; +use Symfony\Component\Validator\Constraints\WhenValidator; use Symfony\Component\Validator\ContainerConstraintValidatorFactory; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Validation; @@ -27,11 +30,11 @@ return static function (ContainerConfigurator $container) { $container->parameters() ->set('validator.mapping.cache.file', param('kernel.cache_dir').'/validation.php'); + $validatorsDir = \dirname((new \ReflectionClass(EmailValidator::class))->getFileName()); + $container->services() ->set('validator', ValidatorInterface::class) - ->public() ->factory([service('validator.builder'), 'getValidator']) - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) ->alias(ValidatorInterface::class, 'validator') ->set('validator.builder', ValidatorBuilder::class) @@ -39,6 +42,9 @@ return static function (ContainerConfigurator $container) { ->call('setConstraintValidatorFactory', [ service('validator.validator_factory'), ]) + ->call('setGroupProviderLocator', [ + tagged_locator('validator.group_provider'), + ]) ->call('setTranslator', [ service('translator')->ignoreOnInvalid(), ]) @@ -66,6 +72,12 @@ return static function (ContainerConfigurator $container) { abstract_arg('Constraint validators locator'), ]) + ->load('Symfony\Component\Validator\Constraints\\', $validatorsDir.'/*Validator.php') + ->exclude($validatorsDir.'/ExpressionLanguageSyntaxValidator.php') + ->abstract() + ->tag('container.excluded') + ->tag('validator.constraint_validator') + ->set('validator.expression', ExpressionValidator::class) ->args([service('validator.expression_language')->nullOnInvalid()]) ->tag('validator.constraint_validator', [ @@ -74,18 +86,21 @@ return static function (ContainerConfigurator $container) { ->set('validator.expression_language', ExpressionLanguage::class) ->args([service('cache.validator_expression_language')->nullOnInvalid()]) + ->call('registerProvider', [ + service('validator.expression_language_provider')->ignoreOnInvalid(), + ]) ->set('cache.validator_expression_language') ->parent('cache.system') ->tag('cache.pool') + ->set('validator.expression_language_provider', ExpressionLanguageProvider::class) + ->set('validator.email', EmailValidator::class) ->args([ abstract_arg('Default mode'), ]) - ->tag('validator.constraint_validator', [ - 'alias' => EmailValidator::class, - ]) + ->tag('validator.constraint_validator') ->set('validator.not_compromised_password', NotCompromisedPasswordValidator::class) ->args([ @@ -93,8 +108,16 @@ return static function (ContainerConfigurator $container) { param('kernel.charset'), false, ]) + ->tag('validator.constraint_validator') + + ->set('validator.when', WhenValidator::class) + ->args([service('validator.expression_language')->nullOnInvalid()]) + ->tag('validator.constraint_validator') + + ->set('validator.no_suspicious_characters', NoSuspiciousCharactersValidator::class) + ->args([param('kernel.enabled_locales')]) ->tag('validator.constraint_validator', [ - 'alias' => NotCompromisedPasswordValidator::class, + 'alias' => NoSuspiciousCharactersValidator::class, ]) ->set('validator.property_info_loader', PropertyInfoLoader::class) diff --git a/lib/symfony/framework-bundle/Resources/config/web.php b/lib/symfony/framework-bundle/Resources/config/web.php index 53613d3b5..6710dabda 100644 --- a/lib/symfony/framework-bundle/Resources/config/web.php +++ b/lib/symfony/framework-bundle/Resources/config/web.php @@ -11,21 +11,28 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\UidValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use Symfony\Component\HttpKernel\Controller\ErrorController; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\EventListener\CacheAttributeListener; use Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener; use Symfony\Component\HttpKernel\EventListener\ErrorListener; use Symfony\Component\HttpKernel\EventListener\LocaleListener; use Symfony\Component\HttpKernel\EventListener\ResponseListener; -use Symfony\Component\HttpKernel\EventListener\StreamedResponseListener; use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; return static function (ContainerConfigurator $container) { @@ -35,6 +42,7 @@ return static function (ContainerConfigurator $container) { service('service_container'), service('logger')->ignoreOnInvalid(), ]) + ->call('allowControllers', [[AbstractController::class, TemplateController::class]]) ->tag('monolog.logger', ['channel' => 'request']) ->set('argument_metadata_factory', ArgumentMetadataFactory::class) @@ -43,28 +51,54 @@ return static function (ContainerConfigurator $container) { ->args([ service('argument_metadata_factory'), abstract_arg('argument value resolvers'), + abstract_arg('targeted value resolvers'), ]) + ->set('argument_resolver.backed_enum_resolver', BackedEnumValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => BackedEnumValueResolver::class]) + + ->set('argument_resolver.uid', UidValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => UidValueResolver::class]) + + ->set('argument_resolver.datetime', DateTimeValueResolver::class) + ->args([ + service('clock')->nullOnInvalid(), + ]) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => DateTimeValueResolver::class]) + + ->set('argument_resolver.request_payload', RequestPayloadValueResolver::class) + ->args([ + service('serializer'), + service('validator')->nullOnInvalid(), + service('translator')->nullOnInvalid(), + ]) + ->tag('controller.targeted_value_resolver', ['name' => RequestPayloadValueResolver::class]) + ->tag('kernel.event_subscriber') + ->lazy() + ->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 100]) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => RequestAttributeValueResolver::class]) ->set('argument_resolver.request', RequestValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 50]) + ->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => RequestValueResolver::class]) ->set('argument_resolver.session', SessionValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 50]) + ->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => SessionValueResolver::class]) ->set('argument_resolver.service', ServiceValueResolver::class) ->args([ abstract_arg('service locator, set in RegisterControllerArgumentLocatorsPass'), ]) - ->tag('controller.argument_value_resolver', ['priority' => -50]) + ->tag('controller.argument_value_resolver', ['priority' => -50, 'name' => ServiceValueResolver::class]) ->set('argument_resolver.default', DefaultValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => -100]) + ->tag('controller.argument_value_resolver', ['priority' => -100, 'name' => DefaultValueResolver::class]) ->set('argument_resolver.variadic', VariadicValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => -150]) + ->tag('controller.argument_value_resolver', ['priority' => -150, 'name' => VariadicValueResolver::class]) + + ->set('argument_resolver.query_parameter_value_resolver', QueryParameterValueResolver::class) + ->tag('controller.targeted_value_resolver', ['name' => QueryParameterValueResolver::class]) ->set('response_listener', ResponseListener::class) ->args([ @@ -73,9 +107,6 @@ return static function (ContainerConfigurator $container) { ]) ->tag('kernel.event_subscriber') - ->set('streamed_response_listener', StreamedResponseListener::class) - ->tag('kernel.event_subscriber') - ->set('locale_listener', LocaleListener::class) ->args([ service('request_stack'), @@ -109,5 +140,9 @@ return static function (ContainerConfigurator $container) { ]) ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'request']) + + ->set('controller.cache_attribute_listener', CacheAttributeListener::class) + ->tag('kernel.event_subscriber') + ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/web_link.php b/lib/symfony/framework-bundle/Resources/config/web_link.php index 0b0e79db8..64345cc99 100644 --- a/lib/symfony/framework-bundle/Resources/config/web_link.php +++ b/lib/symfony/framework-bundle/Resources/config/web_link.php @@ -12,10 +12,18 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\HttpHeaderSerializer; return static function (ContainerConfigurator $container) { $container->services() + + ->set('web_link.http_header_serializer', HttpHeaderSerializer::class) + ->alias(HttpHeaderSerializer::class, 'web_link.http_header_serializer') + ->set('web_link.add_link_header_listener', AddLinkHeaderListener::class) + ->args([ + service('web_link.http_header_serializer'), + ]) ->tag('kernel.event_subscriber') ; }; diff --git a/lib/symfony/framework-bundle/Resources/config/webhook.php b/lib/symfony/framework-bundle/Resources/config/webhook.php new file mode 100644 index 000000000..a7e9d58ce --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/webhook.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Webhook\Client\RequestParser; +use Symfony\Component\Webhook\Controller\WebhookController; +use Symfony\Component\Webhook\Messenger\SendWebhookHandler; +use Symfony\Component\Webhook\Server\HeadersConfigurator; +use Symfony\Component\Webhook\Server\HeaderSignatureConfigurator; +use Symfony\Component\Webhook\Server\JsonBodyConfigurator; +use Symfony\Component\Webhook\Server\Transport; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('webhook.transport', Transport::class) + ->args([ + service('http_client'), + service('webhook.headers_configurator'), + service('webhook.body_configurator.json'), + service('webhook.signer'), + ]) + + ->set('webhook.headers_configurator', HeadersConfigurator::class) + + ->set('webhook.body_configurator.json', JsonBodyConfigurator::class) + ->args([ + service('serializer'), + ]) + + ->set('webhook.signer', HeaderSignatureConfigurator::class) + + ->set('webhook.messenger.send_handler', SendWebhookHandler::class) + ->args([ + service('webhook.transport'), + ]) + ->tag('messenger.message_handler') + + ->set('webhook.request_parser', RequestParser::class) + ->alias(RequestParser::class, 'webhook.request_parser') + + ->set('webhook.controller', WebhookController::class) + ->public() + ->args([ + abstract_arg('user defined parsers'), + abstract_arg('message bus'), + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/workflow.php b/lib/symfony/framework-bundle/Resources/config/workflow.php index 909bb5aca..b6c784bdb 100644 --- a/lib/symfony/framework-bundle/Resources/config/workflow.php +++ b/lib/symfony/framework-bundle/Resources/config/workflow.php @@ -28,8 +28,6 @@ return static function (ContainerConfigurator $container) { abstract_arg('events to dispatch'), ]) ->abstract() - ->public() - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.3']) ->set('state_machine.abstract', StateMachine::class) ->args([ abstract_arg('workflow definition'), @@ -39,8 +37,6 @@ return static function (ContainerConfigurator $container) { abstract_arg('events to dispatch'), ]) ->abstract() - ->public() - ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.3']) ->set('workflow.marking_store.method', MethodMarkingStore::class) ->abstract() ->set('workflow.registry', Registry::class) diff --git a/lib/symfony/framework-bundle/Resources/config/workflow_debug.php b/lib/symfony/framework-bundle/Resources/config/workflow_debug.php new file mode 100644 index 000000000..4909b7d69 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/workflow_debug.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Workflow\DataCollector\WorkflowDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.workflow', WorkflowDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/workflow.html.twig', + 'id' => 'workflow', + ]) + ->args([ + tagged_iterator('workflow', 'name'), + service('event_dispatcher'), + service('debug.file_link_formatter'), + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php b/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php index 511e87058..9fb17d8d7 100644 --- a/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php +++ b/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php @@ -11,42 +11,15 @@ namespace Symfony\Bundle\FrameworkBundle\Routing; -use Symfony\Component\Routing\Loader\AnnotationClassLoader; -use Symfony\Component\Routing\Route; +trigger_deprecation('symfony/framework-bundle', '6.4', 'The "%s" class is deprecated, use "%s" instead.', AnnotatedRouteControllerLoader::class, AttributeRouteControllerLoader::class); -/** - * AnnotatedRouteControllerLoader is an implementation of AnnotationClassLoader - * that sets the '_controller' default based on the class and method names. - * - * @author Fabien Potencier - */ -class AnnotatedRouteControllerLoader extends AnnotationClassLoader -{ +class_exists(AttributeRouteControllerLoader::class); + +if (false) { /** - * Configures the _controller default parameter of a given Route instance. + * @deprecated since Symfony 6.4, to be removed in 7.0, use {@link AttributeRouteControllerLoader} instead */ - protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) + class AnnotatedRouteControllerLoader { - if ('__invoke' === $method->getName()) { - $route->setDefault('_controller', $class->getName()); - } else { - $route->setDefault('_controller', $class->getName().'::'.$method->getName()); - } - } - - /** - * Makes the default route name more sane by removing common keywords. - * - * @return string - */ - protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) - { - $name = preg_replace('/(bundle|controller)_/', '_', parent::getDefaultRouteName($class, $method)); - - if (str_ends_with($method->name, 'Action') || str_ends_with($method->name, '_action')) { - $name = preg_replace('/action(_\d+)?$/', '\\1', $name); - } - - return str_replace('__', '_', $name); } } diff --git a/lib/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php b/lib/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php new file mode 100644 index 000000000..d1f1a5f34 --- /dev/null +++ b/lib/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Attribute; + +use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; + +/** + * Service tag to autoconfigure routing condition services. + * + * You can tag a service: + * + * #[AsRoutingConditionService('foo')] + * class SomeFooService + * { + * public function bar(): bool + * { + * // ... + * } + * } + * + * Then you can use the tagged service in the routing condition: + * + * class PageController + * { + * #[Route('/page', condition: "service('foo').bar()")] + * public function page(): Response + * { + * // ... + * } + * } + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsRoutingConditionService extends AutoconfigureTag +{ + public function __construct( + string $alias = null, + int $priority = 0, + ) { + parent::__construct('routing.condition_service', ['alias' => $alias, 'priority' => $priority]); + } +} diff --git a/lib/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php b/lib/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php new file mode 100644 index 000000000..a629f4387 --- /dev/null +++ b/lib/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +use Symfony\Component\Routing\Loader\AttributeClassLoader; +use Symfony\Component\Routing\Route; + +/** + * AttributeRouteControllerLoader is an implementation of AttributeClassLoader + * that sets the '_controller' default based on the class and method names. + * + * @author Fabien Potencier + * @author Alexandre Daubois + */ +class AttributeRouteControllerLoader extends AttributeClassLoader +{ + /** + * Configures the _controller default parameter of a given Route instance. + * + * @return void + */ + protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) + { + if ('__invoke' === $method->getName()) { + $route->setDefault('_controller', $class->getName()); + } else { + $route->setDefault('_controller', $class->getName().'::'.$method->getName()); + } + } + + /** + * Makes the default route name more sane by removing common keywords. + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method): string + { + $name = preg_replace('/(bundle|controller)_/', '_', parent::getDefaultRouteName($class, $method)); + + if (str_ends_with($method->name, 'Action') || str_ends_with($method->name, '_action')) { + $name = preg_replace('/action(_\d+)?$/', '\\1', $name); + } + + return str_replace('__', '_', $name); + } +} + +if (!class_exists(AnnotatedRouteControllerLoader::class, false)) { + class_alias(AttributeRouteControllerLoader::class, AnnotatedRouteControllerLoader::class); +} diff --git a/lib/symfony/framework-bundle/Routing/DelegatingLoader.php b/lib/symfony/framework-bundle/Routing/DelegatingLoader.php index e130bd2fa..df7e9348c 100644 --- a/lib/symfony/framework-bundle/Routing/DelegatingLoader.php +++ b/lib/symfony/framework-bundle/Routing/DelegatingLoader.php @@ -28,9 +28,9 @@ use Symfony\Component\Routing\RouteCollection; */ class DelegatingLoader extends BaseDelegatingLoader { - private $loading = false; - private $defaultOptions; - private $defaultRequirements; + private bool $loading = false; + private array $defaultOptions; + private array $defaultRequirements; public function __construct(LoaderResolverInterface $resolver, array $defaultOptions = [], array $defaultRequirements = []) { @@ -40,10 +40,7 @@ class DelegatingLoader extends BaseDelegatingLoader parent::__construct($resolver); } - /** - * {@inheritdoc} - */ - public function load($resource, string $type = null): RouteCollection + public function load(mixed $resource, string $type = null): RouteCollection { if ($this->loading) { // This can happen if a fatal error occurs in parent::load(). @@ -58,7 +55,7 @@ class DelegatingLoader extends BaseDelegatingLoader // the fatal error from occurring a second time, // otherwise the PHP process would be killed immediately; // - while rendering the exception page, the router can be required - // (by e.g. the web profiler that needs to generate an URL); + // (by e.g. the web profiler that needs to generate a URL); // - this handles the case and prevents the second fatal error // by triggering an exception beforehand. diff --git a/lib/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php b/lib/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php index dba9d6d96..538427aae 100644 --- a/lib/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php +++ b/lib/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php @@ -21,9 +21,6 @@ use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; */ class RedirectableCompiledUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface { - /** - * {@inheritdoc} - */ public function redirect(string $path, string $route, string $scheme = null): array { return [ diff --git a/lib/symfony/framework-bundle/Routing/Router.php b/lib/symfony/framework-bundle/Routing/Router.php index 8e36efe0a..3367ecec2 100644 --- a/lib/symfony/framework-bundle/Routing/Router.php +++ b/lib/symfony/framework-bundle/Routing/Router.php @@ -33,14 +33,14 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; */ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberInterface { - private $container; - private $collectedParameters = []; - private $paramFetcher; + private ContainerInterface $container; + private array $collectedParameters = []; + private \Closure $paramFetcher; /** * @param mixed $resource The main resource to load */ - public function __construct(ContainerInterface $container, $resource, array $options = [], RequestContext $context = null, ContainerInterface $parameters = null, LoggerInterface $logger = null, string $defaultLocale = null) + public function __construct(ContainerInterface $container, mixed $resource, array $options = [], RequestContext $context = null, ContainerInterface $parameters = null, LoggerInterface $logger = null, string $defaultLocale = null) { $this->container = $container; $this->resource = $resource; @@ -49,9 +49,9 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI $this->setOptions($options); if ($parameters) { - $this->paramFetcher = \Closure::fromCallable([$parameters, 'get']); + $this->paramFetcher = $parameters->get(...); } elseif ($container instanceof SymfonyContainerInterface) { - $this->paramFetcher = \Closure::fromCallable([$container, 'getParameter']); + $this->paramFetcher = $container->getParameter(...); } else { throw new \LogicException(sprintf('You should either pass a "%s" instance or provide the $parameters argument of the "%s" method.', SymfonyContainerInterface::class, __METHOD__)); } @@ -59,12 +59,9 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI $this->defaultLocale = $defaultLocale; } - /** - * {@inheritdoc} - */ - public function getRouteCollection() + public function getRouteCollection(): RouteCollection { - if (null === $this->collection) { + if (!isset($this->collection)) { $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']); $this->resolveParameters($this->collection); $this->collection->addResource(new ContainerParametersResource($this->collectedParameters)); @@ -76,7 +73,7 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI } else { $this->collection->addResource(new FileExistenceResource($containerFile)); } - } catch (ParameterNotFoundException $exception) { + } catch (ParameterNotFoundException) { } } @@ -84,11 +81,9 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI } /** - * {@inheritdoc} - * - * @return string[] A list of classes to preload on PHP 7.4+ + * @param string|null $buildDir */ - public function warmUp(string $cacheDir) + public function warmUp(string $cacheDir /* , string $buildDir = null */): array { $currentDir = $this->getOption('cache_dir'); @@ -114,7 +109,7 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI * - the route schemes, * - the route methods. */ - private function resolveParameters(RouteCollection $collection) + private function resolveParameters(RouteCollection $collection): void { foreach ($collection as $route) { foreach ($route->getDefaults() as $name => $value) { @@ -144,17 +139,12 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI } /** - * Recursively replaces placeholders with the service container parameters. - * - * @param mixed $value The source which might contain "%placeholders%" - * - * @return mixed The source with the placeholders replaced by the container - * parameters. Arrays are resolved recursively. + * Recursively replaces %placeholders% with the service container parameters. * * @throws ParameterNotFoundException When a placeholder does not exist as a container parameter * @throws RuntimeException When a container value is not a string or a numeric value */ - private function resolve($value) + private function resolve(mixed $value): mixed { if (\is_array($value)) { foreach ($value as $key => $val) { @@ -198,10 +188,7 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI return str_replace('%%', '%', $escapedValue); } - /** - * {@inheritdoc} - */ - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ 'routing.loader' => LoaderInterface::class, diff --git a/lib/symfony/framework-bundle/Secrets/AbstractVault.php b/lib/symfony/framework-bundle/Secrets/AbstractVault.php index eeecbbb68..b3eb0c6bc 100644 --- a/lib/symfony/framework-bundle/Secrets/AbstractVault.php +++ b/lib/symfony/framework-bundle/Secrets/AbstractVault.php @@ -13,8 +13,6 @@ namespace Symfony\Bundle\FrameworkBundle\Secrets; /** * @author Nicolas Grekas - * - * @internal */ abstract class AbstractVault { @@ -42,6 +40,9 @@ abstract class AbstractVault } } + /** + * @return string + */ protected function getPrettyPath(string $path) { return str_replace(getcwd().\DIRECTORY_SEPARATOR, '', $path); diff --git a/lib/symfony/framework-bundle/Secrets/DotenvVault.php b/lib/symfony/framework-bundle/Secrets/DotenvVault.php index 7c6f6987e..994b31d18 100644 --- a/lib/symfony/framework-bundle/Secrets/DotenvVault.php +++ b/lib/symfony/framework-bundle/Secrets/DotenvVault.php @@ -13,12 +13,10 @@ namespace Symfony\Bundle\FrameworkBundle\Secrets; /** * @author Nicolas Grekas - * - * @internal */ class DotenvVault extends AbstractVault { - private $dotenvFile; + private string $dotenvFile; public function __construct(string $dotenvFile) { @@ -54,9 +52,9 @@ class DotenvVault extends AbstractVault { $this->lastMessage = null; $this->validateName($name); - $v = \is_string($_SERVER[$name] ?? null) && !str_starts_with($name, 'HTTP_') ? $_SERVER[$name] : ($_ENV[$name] ?? null); + $v = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null)); - if (null === $v) { + if ('' === ($v ?? '')) { $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); return null; @@ -91,13 +89,13 @@ class DotenvVault extends AbstractVault $secrets = []; foreach ($_ENV as $k => $v) { - if (preg_match('/^\w+$/D', $k)) { + if ('' !== ($v ?? '') && preg_match('/^\w+$/D', $k)) { $secrets[$k] = $reveal ? $v : null; } } foreach ($_SERVER as $k => $v) { - if (\is_string($v) && preg_match('/^\w+$/D', $k)) { + if ('' !== ($v ?? '') && preg_match('/^\w+$/D', $k)) { $secrets[$k] = $reveal ? $v : null; } } diff --git a/lib/symfony/framework-bundle/Secrets/SodiumVault.php b/lib/symfony/framework-bundle/Secrets/SodiumVault.php index a9c86ab04..b6bb058b3 100644 --- a/lib/symfony/framework-bundle/Secrets/SodiumVault.php +++ b/lib/symfony/framework-bundle/Secrets/SodiumVault.php @@ -18,26 +18,20 @@ use Symfony\Component\VarExporter\VarExporter; * @author Tobias Schultze * @author JĂ©rĂ©my DerussĂ© * @author Nicolas Grekas - * - * @internal */ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface { - private $encryptionKey; - private $decryptionKey; - private $pathPrefix; - private $secretsDir; + private ?string $encryptionKey = null; + private string|\Stringable|null $decryptionKey = null; + private string $pathPrefix; + private ?string $secretsDir; /** - * @param string|\Stringable|null $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault - * or null to store generated keys in the provided $secretsDir + * @param $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault + * or null to store generated keys in the provided $secretsDir */ - public function __construct(string $secretsDir, $decryptionKey = null) + public function __construct(string $secretsDir, #[\SensitiveParameter] string|\Stringable $decryptionKey = null) { - if (null !== $decryptionKey && !\is_string($decryptionKey) && !(\is_object($decryptionKey) && method_exists($decryptionKey, '__toString'))) { - throw new \TypeError(sprintf('Decryption key should be a string or an object that implements the __toString() method, "%s" given.', get_debug_type($decryptionKey))); - } - $this->pathPrefix = rtrim(strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.basename($secretsDir).'.'; $this->decryptionKey = $decryptionKey; $this->secretsDir = $secretsDir; @@ -55,7 +49,7 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface try { $this->loadKeys(); - } catch (\RuntimeException $e) { + } catch (\RuntimeException) { // ignore failures to load keys } diff --git a/lib/symfony/framework-bundle/Session/DeprecatedSessionFactory.php b/lib/symfony/framework-bundle/Session/DeprecatedSessionFactory.php deleted file mode 100644 index faa29a1c7..000000000 --- a/lib/symfony/framework-bundle/Session/DeprecatedSessionFactory.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Session; - -use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; - -/** - * Provides session and trigger deprecation. - * - * Used by service that should trigger deprecation when accessed by the user. - * - * @author JĂ©rĂ©my DerussĂ© - * - * @internal to be removed in 6.0 - */ -class DeprecatedSessionFactory -{ - private $requestStack; - - public function __construct(RequestStack $requestStack) - { - $this->requestStack = $requestStack; - } - - public function getSession(): ?SessionInterface - { - trigger_deprecation('symfony/framework-bundle', '5.3', 'The "session" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); - - try { - return $this->requestStack->getSession(); - } catch (SessionNotFoundException $e) { - return null; - } - } -} diff --git a/lib/symfony/framework-bundle/Session/ServiceSessionFactory.php b/lib/symfony/framework-bundle/Session/ServiceSessionFactory.php deleted file mode 100644 index c05737501..000000000 --- a/lib/symfony/framework-bundle/Session/ServiceSessionFactory.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Session; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; -use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; -use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; - -/** - * @author JĂ©rĂ©my DerussĂ© - * - * @internal to be removed in Symfony 6 - */ -final class ServiceSessionFactory implements SessionStorageFactoryInterface -{ - private $storage; - - public function __construct(SessionStorageInterface $storage) - { - $this->storage = $storage; - } - - public function createStorage(?Request $request): SessionStorageInterface - { - if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { - $this->storage->setOptions(['cookie_secure' => true]); - } - - return $this->storage; - } -} diff --git a/lib/symfony/framework-bundle/Translation/Translator.php b/lib/symfony/framework-bundle/Translation/Translator.php index 5173f8a8e..04b56308f 100644 --- a/lib/symfony/framework-bundle/Translation/Translator.php +++ b/lib/symfony/framework-bundle/Translation/Translator.php @@ -38,7 +38,7 @@ class Translator extends BaseTranslator implements WarmableInterface /** * @var list */ - private $resourceLocales; + private array $resourceLocales; /** * Holds parameters from addResource() calls so we can defer the actual @@ -46,22 +46,22 @@ class Translator extends BaseTranslator implements WarmableInterface * * @var array[] */ - private $resources = []; + private array $resources = []; /** * @var string[][] */ - private $resourceFiles; + private array $resourceFiles; /** * @var string[] */ - private $scannedDirectories; + private array $scannedDirectories; /** * @var string[] */ - private $enabledLocales; + private array $enabledLocales; /** * Constructor. @@ -95,11 +95,9 @@ class Translator extends BaseTranslator implements WarmableInterface } /** - * {@inheritdoc} - * - * @return string[] + * @param string|null $buildDir */ - public function warmUp(string $cacheDir) + public function warmUp(string $cacheDir /* , string $buildDir = null */): array { // skip warmUp when translator doesn't use cache if (null === $this->options['cache_dir']) { @@ -120,7 +118,7 @@ class Translator extends BaseTranslator implements WarmableInterface return []; } - public function addResource(string $format, $resource, string $locale, string $domain = null) + public function addResource(string $format, mixed $resource, string $locale, string $domain = null): void { if ($this->resourceFiles) { $this->addResourceFiles(); @@ -128,10 +126,7 @@ class Translator extends BaseTranslator implements WarmableInterface $this->resources[] = [$format, $resource, $locale, $domain]; } - /** - * {@inheritdoc} - */ - protected function initializeCatalogue(string $locale) + protected function initializeCatalogue(string $locale): void { $this->initialize(); parent::initializeCatalogue($locale); @@ -150,6 +145,9 @@ class Translator extends BaseTranslator implements WarmableInterface } } + /** + * @return void + */ protected function initialize() { if ($this->resourceFiles) { diff --git a/lib/symfony/framework-bundle/composer.json b/lib/symfony/framework-bundle/composer.json index 089e2e082..e63a672e8 100644 --- a/lib/symfony/framework-bundle/composer.json +++ b/lib/symfony/framework-bundle/composer.json @@ -16,97 +16,94 @@ } ], "require": { - "php": ">=7.2.5", + "php": ">=8.1", + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "symfony/cache": "^5.2|^6.0", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^5.4.5|^6.0.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher": "^5.1|^6.0", - "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^5.4|^6.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/routing": "^5.3|^6.0" + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/routing": "^6.4|^7.0" }, "require-dev": { "doctrine/annotations": "^1.13.1|^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/persistence": "^1.3|^2|^3", - "symfony/asset": "^5.3|^6.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/console": "^5.4.9|^6.0.9", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", - "symfony/dotenv": "^5.1|^6.0", + "dragonmantank/cron-expression": "^3.1", + "seld/jsonlint": "^1.10", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4.9|^6.0.9|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^5.4|^6.0|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/form": "^5.2|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/mailer": "^5.2|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/notifier": "^5.4|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/string": "^5.0|^6.0", - "symfony/translation": "^5.3|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/validator": "^5.2|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", - "symfony/property-info": "^4.4|^5.0|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-client": "^6.3|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/mailer": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.3|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0", + "symfony/scheduler": "^6.4|^7.0", + "symfony/security-bundle": "^5.4|^6.0|^7.0", + "symfony/semaphore": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/string": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "twig/twig": "^2.10|^3.0" }, "conflict": { "doctrine/annotations": "<1.13.1", - "doctrine/cache": "<1.11", "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "phpunit/phpunit": "<5.4.3", - "symfony/asset": "<5.3", - "symfony/console": "<5.2.5", - "symfony/dotenv": "<5.1", - "symfony/dom-crawler": "<4.4", - "symfony/http-client": "<4.4", - "symfony/form": "<5.2", - "symfony/lock": "<4.4", - "symfony/mailer": "<5.2", - "symfony/messenger": "<5.4", - "symfony/mime": "<4.4", - "symfony/property-info": "<4.4", - "symfony/property-access": "<5.3", - "symfony/serializer": "<5.2", - "symfony/service-contracts": ">=3.0", - "symfony/security-csrf": "<5.3", - "symfony/stopwatch": "<4.4", - "symfony/translation": "<5.3", - "symfony/twig-bridge": "<4.4", - "symfony/twig-bundle": "<4.4", - "symfony/validator": "<5.2", - "symfony/web-profiler-bundle": "<4.4", - "symfony/workflow": "<5.2" - }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/yaml": "For using the debug:config and lint:yaml commands", - "symfony/property-info": "For using the property_info service", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering" + "symfony/asset": "<5.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/dom-crawler": "<6.4", + "symfony/http-client": "<6.3", + "symfony/form": "<5.4", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.4", + "symfony/property-info": "<5.4", + "symfony/property-access": "<5.4", + "symfony/scheduler": "<6.4", + "symfony/serializer": "<6.4", + "symfony/security-csrf": "<5.4", + "symfony/security-core": "<5.4", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/workflow": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" }, diff --git a/lib/symfony/http-foundation/AcceptHeader.php b/lib/symfony/http-foundation/AcceptHeader.php index 057c6b530..853c000e0 100644 --- a/lib/symfony/http-foundation/AcceptHeader.php +++ b/lib/symfony/http-foundation/AcceptHeader.php @@ -27,12 +27,9 @@ class AcceptHeader /** * @var AcceptHeaderItem[] */ - private $items = []; + private array $items = []; - /** - * @var bool - */ - private $sorted = true; + private bool $sorted = true; /** * @param AcceptHeaderItem[] $items @@ -46,16 +43,13 @@ class AcceptHeader /** * Builds an AcceptHeader instance from a string. - * - * @return self */ - public static function fromString(?string $headerValue) + public static function fromString(?string $headerValue): self { - $index = 0; - $parts = HeaderUtils::split($headerValue ?? '', ',;='); - return new self(array_map(function ($subParts) use (&$index) { + return new self(array_map(function ($subParts) { + static $index = 0; $part = array_shift($subParts); $attributes = HeaderUtils::combine($subParts); @@ -68,30 +62,24 @@ class AcceptHeader /** * Returns header value's string representation. - * - * @return string */ - public function __toString() + public function __toString(): string { return implode(',', $this->items); } /** * Tests if header has given value. - * - * @return bool */ - public function has(string $value) + public function has(string $value): bool { return isset($this->items[$value]); } /** * Returns given value's item, if exists. - * - * @return AcceptHeaderItem|null */ - public function get(string $value) + public function get(string $value): ?AcceptHeaderItem { return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null; } @@ -101,7 +89,7 @@ class AcceptHeader * * @return $this */ - public function add(AcceptHeaderItem $item) + public function add(AcceptHeaderItem $item): static { $this->items[$item->getValue()] = $item; $this->sorted = false; @@ -114,7 +102,7 @@ class AcceptHeader * * @return AcceptHeaderItem[] */ - public function all() + public function all(): array { $this->sort(); @@ -123,26 +111,20 @@ class AcceptHeader /** * Filters items on their value using given regex. - * - * @return self */ - public function filter(string $pattern) + public function filter(string $pattern): self { - return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { - return preg_match($pattern, $item->getValue()); - })); + return new self(array_filter($this->items, fn (AcceptHeaderItem $item) => preg_match($pattern, $item->getValue()))); } /** * Returns first item. - * - * @return AcceptHeaderItem|null */ - public function first() + public function first(): ?AcceptHeaderItem { $this->sort(); - return !empty($this->items) ? reset($this->items) : null; + return $this->items ? reset($this->items) : null; } /** diff --git a/lib/symfony/http-foundation/AcceptHeaderItem.php b/lib/symfony/http-foundation/AcceptHeaderItem.php index 8b86eee67..35ecd4ea2 100644 --- a/lib/symfony/http-foundation/AcceptHeaderItem.php +++ b/lib/symfony/http-foundation/AcceptHeaderItem.php @@ -18,10 +18,10 @@ namespace Symfony\Component\HttpFoundation; */ class AcceptHeaderItem { - private $value; - private $quality = 1.0; - private $index = 0; - private $attributes = []; + private string $value; + private float $quality = 1.0; + private int $index = 0; + private array $attributes = []; public function __construct(string $value, array $attributes = []) { @@ -33,10 +33,8 @@ class AcceptHeaderItem /** * Builds an AcceptHeaderInstance instance from a string. - * - * @return self */ - public static function fromString(?string $itemValue) + public static function fromString(?string $itemValue): self { $parts = HeaderUtils::split($itemValue ?? '', ';='); @@ -48,10 +46,8 @@ class AcceptHeaderItem /** * Returns header value's string representation. - * - * @return string */ - public function __toString() + public function __toString(): string { $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); if (\count($this->attributes) > 0) { @@ -66,7 +62,7 @@ class AcceptHeaderItem * * @return $this */ - public function setValue(string $value) + public function setValue(string $value): static { $this->value = $value; @@ -75,10 +71,8 @@ class AcceptHeaderItem /** * Returns the item value. - * - * @return string */ - public function getValue() + public function getValue(): string { return $this->value; } @@ -88,7 +82,7 @@ class AcceptHeaderItem * * @return $this */ - public function setQuality(float $quality) + public function setQuality(float $quality): static { $this->quality = $quality; @@ -97,10 +91,8 @@ class AcceptHeaderItem /** * Returns the item quality. - * - * @return float */ - public function getQuality() + public function getQuality(): float { return $this->quality; } @@ -110,7 +102,7 @@ class AcceptHeaderItem * * @return $this */ - public function setIndex(int $index) + public function setIndex(int $index): static { $this->index = $index; @@ -119,42 +111,32 @@ class AcceptHeaderItem /** * Returns the item index. - * - * @return int */ - public function getIndex() + public function getIndex(): int { return $this->index; } /** * Tests if an attribute exists. - * - * @return bool */ - public function hasAttribute(string $name) + public function hasAttribute(string $name): bool { return isset($this->attributes[$name]); } /** * Returns an attribute by its name. - * - * @param mixed $default - * - * @return mixed */ - public function getAttribute(string $name, $default = null) + public function getAttribute(string $name, mixed $default = null): mixed { return $this->attributes[$name] ?? $default; } /** * Returns all attributes. - * - * @return array */ - public function getAttributes() + public function getAttributes(): array { return $this->attributes; } @@ -164,7 +146,7 @@ class AcceptHeaderItem * * @return $this */ - public function setAttribute(string $name, string $value) + public function setAttribute(string $name, string $value): static { if ('q' === $name) { $this->quality = (float) $value; diff --git a/lib/symfony/http-foundation/BinaryFileResponse.php b/lib/symfony/http-foundation/BinaryFileResponse.php index 6d7b80ad1..ca18c92f1 100644 --- a/lib/symfony/http-foundation/BinaryFileResponse.php +++ b/lib/symfony/http-foundation/BinaryFileResponse.php @@ -34,18 +34,18 @@ class BinaryFileResponse extends Response protected $offset = 0; protected $maxlen = -1; protected $deleteFileAfterSend = false; - protected $chunkSize = 8 * 1024; + protected $chunkSize = 16 * 1024; /** * @param \SplFileInfo|string $file The file to stream - * @param int $status The response status code + * @param int $status The response status code (200 "OK" by default) * @param array $headers An array of response headers * @param bool $public Files are public by default * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename * @param bool $autoEtag Whether the ETag header should be automatically set * @param bool $autoLastModified Whether the Last-Modified header should be automatically set */ - public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + public function __construct(\SplFileInfo|string $file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) { parent::__construct(null, $status, $headers); @@ -56,36 +56,14 @@ class BinaryFileResponse extends Response } } - /** - * @param \SplFileInfo|string $file The file to stream - * @param int $status The response status code - * @param array $headers An array of response headers - * @param bool $public Files are public by default - * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename - * @param bool $autoEtag Whether the ETag header should be automatically set - * @param bool $autoLastModified Whether the Last-Modified header should be automatically set - * - * @return static - * - * @deprecated since Symfony 5.2, use __construct() instead. - */ - public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) - { - trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); - - return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); - } - /** * Sets the file to stream. * - * @param \SplFileInfo|string $file The file to stream - * * @return $this * * @throws FileException */ - public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + public function setFile(\SplFileInfo|string $file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true): static { if (!$file instanceof File) { if ($file instanceof \SplFileInfo) { @@ -118,10 +96,8 @@ class BinaryFileResponse extends Response /** * Gets the file. - * - * @return File */ - public function getFile() + public function getFile(): File { return $this->file; } @@ -131,7 +107,7 @@ class BinaryFileResponse extends Response * * @return $this */ - public function setChunkSize(int $chunkSize): self + public function setChunkSize(int $chunkSize): static { if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) { throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.'); @@ -147,9 +123,9 @@ class BinaryFileResponse extends Response * * @return $this */ - public function setAutoLastModified() + public function setAutoLastModified(): static { - $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + $this->setLastModified(\DateTimeImmutable::createFromFormat('U', $this->file->getMTime())); return $this; } @@ -159,7 +135,7 @@ class BinaryFileResponse extends Response * * @return $this */ - public function setAutoEtag() + public function setAutoEtag(): static { $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true))); @@ -175,7 +151,7 @@ class BinaryFileResponse extends Response * * @return $this */ - public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '') + public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = ''): static { if ('' === $filename) { $filename = $this->file->getFilename(); @@ -201,10 +177,7 @@ class BinaryFileResponse extends Response return $this; } - /** - * {@inheritdoc} - */ - public function prepare(Request $request) + public function prepare(Request $request): static { if ($this->isInformational() || $this->isEmpty()) { parent::prepare($request); @@ -248,7 +221,7 @@ class BinaryFileResponse extends Response $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); foreach ($parts as $part) { [$pathPrefix, $location] = $part; - if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { + if (str_starts_with($path, $pathPrefix)) { $path = $location.substr($path, \strlen($pathPrefix)); // Only set X-Accel-Redirect header if a valid URI can be produced // as nginx does not serve arbitrary file paths. @@ -267,7 +240,7 @@ class BinaryFileResponse extends Response $range = $request->headers->get('Range'); if (str_starts_with($range, 'bytes=')) { - [$start, $end] = explode('-', substr($range, 6), 2) + [0]; + [$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0]; $end = ('' === $end) ? $fileSize - 1 : (int) $end; @@ -316,14 +289,11 @@ class BinaryFileResponse extends Response return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; } - /** - * {@inheritdoc} - */ - public function sendContent() + public function sendContent(): static { try { if (!$this->isSuccessful()) { - return parent::sendContent(); + return $this; } if (0 === $this->maxlen) { @@ -341,14 +311,21 @@ class BinaryFileResponse extends Response $length = $this->maxlen; while ($length && !feof($file)) { - $read = ($length > $this->chunkSize) ? $this->chunkSize : $length; - $length -= $read; + $read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length; - stream_copy_to_stream($file, $out, $read); - - if (connection_aborted()) { + if (false === $data = fread($file, $read)) { break; } + while ('' !== $data) { + $read = fwrite($out, $data); + if (false === $read || connection_aborted()) { + break 2; + } + if (0 < $length) { + $length -= $read; + } + $data = substr($data, $read); + } } fclose($out); @@ -363,11 +340,9 @@ class BinaryFileResponse extends Response } /** - * {@inheritdoc} - * * @throws \LogicException when the content is not null */ - public function setContent(?string $content) + public function setContent(?string $content): static { if (null !== $content) { throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); @@ -376,16 +351,15 @@ class BinaryFileResponse extends Response return $this; } - /** - * {@inheritdoc} - */ - public function getContent() + public function getContent(): string|false { return false; } /** * Trust X-Sendfile-Type header. + * + * @return void */ public static function trustXSendfileTypeHeader() { @@ -398,7 +372,7 @@ class BinaryFileResponse extends Response * * @return $this */ - public function deleteFileAfterSend(bool $shouldDelete = true) + public function deleteFileAfterSend(bool $shouldDelete = true): static { $this->deleteFileAfterSend = $shouldDelete; diff --git a/lib/symfony/http-foundation/CHANGELOG.md b/lib/symfony/http-foundation/CHANGELOG.md index ad7607add..3f09854ac 100644 --- a/lib/symfony/http-foundation/CHANGELOG.md +++ b/lib/symfony/http-foundation/CHANGELOG.md @@ -1,6 +1,65 @@ CHANGELOG ========= +6.4 +--- + + * Make `HeaderBag::getDate()`, `Response::getDate()`, `getExpires()` and `getLastModified()` return a `DateTimeImmutable` + * Support root-level `Generator` in `StreamedJsonResponse` + * Add `UriSigner` from the HttpKernel component + * Add `partitioned` flag to `Cookie` (CHIPS Cookie) + * Add argument `bool $flush = true` to `Response::send()` +* Make `MongoDbSessionHandler` instantiable with the mongodb extension directly + +6.3 +--- + + * Calling `ParameterBag::getDigit()`, `getAlnum()`, `getAlpha()` on an `array` throws a `UnexpectedValueException` instead of a `TypeError` + * Add `ParameterBag::getString()` to convert a parameter into string and throw an exception if the value is invalid + * Add `ParameterBag::getEnum()` + * Create migration for session table when pdo handler is used + * Add support for Relay PHP extension for Redis + * The `Response::sendHeaders()` method now takes an optional HTTP status code as parameter, allowing to send informational responses such as Early Hints responses (103 status code) + * Add `IpUtils::isPrivateIp()` + * Add `Request::getPayload(): InputBag` + * Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()`, + * Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set + +6.2 +--- + + * Add `StreamedJsonResponse` class for efficient JSON streaming + * The HTTP cache store uses the `xxh128` algorithm + * Deprecate calling `JsonResponse::setCallback()`, `Response::setExpires/setLastModified/setEtag()`, `MockArraySessionStorage/NativeSessionStorage::setMetadataBag()`, `NativeSessionStorage::setSaveHandler()` without arguments + * Add request matchers under the `Symfony\Component\HttpFoundation\RequestMatcher` namespace + * Deprecate `RequestMatcher` in favor of `ChainRequestMatcher` + * Deprecate `Symfony\Component\HttpFoundation\ExpressionRequestMatcher` in favor of `Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher` + +6.1 +--- + + * Add stale while revalidate and stale if error cache header + * Allow dynamic session "ttl" when using a remote storage + * Deprecate `Request::getContentType()`, use `Request::getContentTypeFormat()` instead + +6.0 +--- + + * Remove the `NamespacedAttributeBag` class + * Removed `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, `StreamedResponse::create()` and + `BinaryFileResponse::create()` methods (use `__construct()` instead) + * Not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()` throws an `\InvalidArgumentException`; wrap your filter in a closure instead + * Not passing a `Closure` together with `FILTER_CALLBACK` to `InputBag::filter()` throws an `\InvalidArgumentException`; wrap your filter in a closure instead + * Removed the `Request::HEADER_X_FORWARDED_ALL` constant, use either `Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO` or `Request::HEADER_X_FORWARDED_AWS_ELB` or `Request::HEADER_X_FORWARDED_TRAEFIK`constants instead + * Rename `RequestStack::getMasterRequest()` to `getMainRequest()` + * Not passing `FILTER_REQUIRE_ARRAY` or `FILTER_FORCE_ARRAY` flags to `InputBag::filter()` when filtering an array will throw `BadRequestException` + * Removed the `Request::HEADER_X_FORWARDED_ALL` constant + * Retrieving non-scalar values using `InputBag::get()` will throw `BadRequestException` (use `InputBad::all()` instead to retrieve an array) + * Passing non-scalar default value as the second argument `InputBag::get()` will throw `\InvalidArgumentException` + * Passing non-scalar, non-array value as the second argument `InputBag::set()` will throw `\InvalidArgumentException` + * Passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()` is not supported anymore. + 5.4 --- @@ -70,7 +129,7 @@ CHANGELOG make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to update your database. * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, - make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database + make sure to run `CREATE INDEX expiry ON sessions (sess_lifetime)` to update your database to speed up garbage collection of expired sessions. * added `SessionHandlerFactory` to create session handlers with a DSN * added `IpUtils::anonymize()` to help with GDPR compliance. diff --git a/lib/symfony/http-foundation/ChainRequestMatcher.php b/lib/symfony/http-foundation/ChainRequestMatcher.php new file mode 100644 index 000000000..29486fc8d --- /dev/null +++ b/lib/symfony/http-foundation/ChainRequestMatcher.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ChainRequestMatcher verifies that all checks match against a Request instance. + * + * @author Fabien Potencier + */ +class ChainRequestMatcher implements RequestMatcherInterface +{ + /** + * @param iterable $matchers + */ + public function __construct(private iterable $matchers) + { + } + + public function matches(Request $request): bool + { + foreach ($this->matchers as $matcher) { + if (!$matcher->matches($request)) { + return false; + } + } + + return true; + } +} diff --git a/lib/symfony/http-foundation/Cookie.php b/lib/symfony/http-foundation/Cookie.php index b4b26c015..706f5ca25 100644 --- a/lib/symfony/http-foundation/Cookie.php +++ b/lib/symfony/http-foundation/Cookie.php @@ -30,9 +30,10 @@ class Cookie protected $secure; protected $httpOnly; - private $raw; - private $sameSite; - private $secureDefault = false; + private bool $raw; + private ?string $sameSite = null; + private bool $partitioned = false; + private bool $secureDefault = false; private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f"; private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; @@ -40,10 +41,8 @@ class Cookie /** * Creates cookie from raw header string. - * - * @return static */ - public static function fromString(string $cookie, bool $decode = false) + public static function fromString(string $cookie, bool $decode = false): static { $data = [ 'expires' => 0, @@ -53,6 +52,7 @@ class Cookie 'httponly' => false, 'raw' => !$decode, 'samesite' => null, + 'partitioned' => false, ]; $parts = HeaderUtils::split($cookie, ';='); @@ -68,28 +68,36 @@ class Cookie $data['expires'] = time() + (int) $data['max-age']; } - return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite'], $data['partitioned']); } - public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self + /** + * @see self::__construct + * + * @param self::SAMESITE_*|''|null $sameSite + * @param bool $partitioned + */ + public static function create(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX /* , bool $partitioned = false */): self { - return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite); + $partitioned = 9 < \func_num_args() ? func_get_arg(9) : false; + + return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite, $partitioned); } /** * @param string $name The name of the cookie * @param string|null $value The value of the cookie * @param int|string|\DateTimeInterface $expire The time the cookie expires - * @param string $path The path on the server in which the cookie will be available on + * @param string|null $path The path on the server in which the cookie will be available on * @param string|null $domain The domain that the cookie is available to * @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol * @param bool $raw Whether the cookie value should be sent with no url encoding - * @param string|null $sameSite Whether the cookie will be available for cross-site requests + * @param self::SAMESITE_*|''|null $sameSite Whether the cookie will be available for cross-site requests * * @throws \InvalidArgumentException */ - public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax') + public function __construct(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false) { // from PHP source code if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) { @@ -109,14 +117,13 @@ class Cookie $this->httpOnly = $httpOnly; $this->raw = $raw; $this->sameSite = $this->withSameSite($sameSite)->sameSite; + $this->partitioned = $partitioned; } /** * Creates a cookie copy with a new value. - * - * @return static */ - public function withValue(?string $value): self + public function withValue(?string $value): static { $cookie = clone $this; $cookie->value = $value; @@ -126,10 +133,8 @@ class Cookie /** * Creates a cookie copy with a new domain that the cookie is available to. - * - * @return static */ - public function withDomain(?string $domain): self + public function withDomain(?string $domain): static { $cookie = clone $this; $cookie->domain = $domain; @@ -139,12 +144,8 @@ class Cookie /** * Creates a cookie copy with a new time the cookie expires. - * - * @param int|string|\DateTimeInterface $expire - * - * @return static */ - public function withExpires($expire = 0): self + public function withExpires(int|string|\DateTimeInterface $expire = 0): static { $cookie = clone $this; $cookie->expire = self::expiresTimestamp($expire); @@ -154,10 +155,8 @@ class Cookie /** * Converts expires formats to a unix timestamp. - * - * @param int|string|\DateTimeInterface $expire */ - private static function expiresTimestamp($expire = 0): int + private static function expiresTimestamp(int|string|\DateTimeInterface $expire = 0): int { // convert expiration time to a Unix timestamp if ($expire instanceof \DateTimeInterface) { @@ -175,10 +174,8 @@ class Cookie /** * Creates a cookie copy with a new path on the server in which the cookie will be available on. - * - * @return static */ - public function withPath(string $path): self + public function withPath(string $path): static { $cookie = clone $this; $cookie->path = '' === $path ? '/' : $path; @@ -188,10 +185,8 @@ class Cookie /** * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client. - * - * @return static */ - public function withSecure(bool $secure = true): self + public function withSecure(bool $secure = true): static { $cookie = clone $this; $cookie->secure = $secure; @@ -201,10 +196,8 @@ class Cookie /** * Creates a cookie copy that be accessible only through the HTTP protocol. - * - * @return static */ - public function withHttpOnly(bool $httpOnly = true): self + public function withHttpOnly(bool $httpOnly = true): static { $cookie = clone $this; $cookie->httpOnly = $httpOnly; @@ -214,10 +207,8 @@ class Cookie /** * Creates a cookie copy that uses no url encoding. - * - * @return static */ - public function withRaw(bool $raw = true): self + public function withRaw(bool $raw = true): static { if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) { throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name)); @@ -232,9 +223,9 @@ class Cookie /** * Creates a cookie copy with SameSite attribute. * - * @return static + * @param self::SAMESITE_*|''|null $sameSite */ - public function withSameSite(?string $sameSite): self + public function withSameSite(?string $sameSite): static { if ('' === $sameSite) { $sameSite = null; @@ -253,11 +244,20 @@ class Cookie } /** - * Returns the cookie as a string. - * - * @return string + * Creates a cookie copy that is tied to the top-level site in cross-site context. */ - public function __toString() + public function withPartitioned(bool $partitioned = true): static + { + $cookie = clone $this; + $cookie->partitioned = $partitioned; + + return $cookie; + } + + /** + * Returns the cookie as a string. + */ + public function __toString(): string { if ($this->isRaw()) { $str = $this->getName(); @@ -268,12 +268,12 @@ class Cookie $str .= '='; if ('' === (string) $this->getValue()) { - $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; + $str .= 'deleted; expires='.gmdate('D, d M Y H:i:s T', time() - 31536001).'; Max-Age=0'; } else { $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); if (0 !== $this->getExpiresTime()) { - $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); + $str .= '; expires='.gmdate('D, d M Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); } } @@ -285,11 +285,11 @@ class Cookie $str .= '; domain='.$this->getDomain(); } - if (true === $this->isSecure()) { + if ($this->isSecure()) { $str .= '; secure'; } - if (true === $this->isHttpOnly()) { + if ($this->isHttpOnly()) { $str .= '; httponly'; } @@ -297,55 +297,49 @@ class Cookie $str .= '; samesite='.$this->getSameSite(); } + if ($this->isPartitioned()) { + $str .= '; partitioned'; + } + return $str; } /** * Gets the name of the cookie. - * - * @return string */ - public function getName() + public function getName(): string { return $this->name; } /** * Gets the value of the cookie. - * - * @return string|null */ - public function getValue() + public function getValue(): ?string { return $this->value; } /** * Gets the domain that the cookie is available to. - * - * @return string|null */ - public function getDomain() + public function getDomain(): ?string { return $this->domain; } /** * Gets the time the cookie expires. - * - * @return int */ - public function getExpiresTime() + public function getExpiresTime(): int { return $this->expire; } /** * Gets the max-age attribute. - * - * @return int */ - public function getMaxAge() + public function getMaxAge(): int { $maxAge = $this->expire - time(); @@ -354,60 +348,56 @@ class Cookie /** * Gets the path on the server in which the cookie will be available on. - * - * @return string */ - public function getPath() + public function getPath(): string { return $this->path; } /** * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. - * - * @return bool */ - public function isSecure() + public function isSecure(): bool { return $this->secure ?? $this->secureDefault; } /** * Checks whether the cookie will be made accessible only through the HTTP protocol. - * - * @return bool */ - public function isHttpOnly() + public function isHttpOnly(): bool { return $this->httpOnly; } /** * Whether this cookie is about to be cleared. - * - * @return bool */ - public function isCleared() + public function isCleared(): bool { return 0 !== $this->expire && $this->expire < time(); } /** * Checks if the cookie value should be sent with no url encoding. - * - * @return bool */ - public function isRaw() + public function isRaw(): bool { return $this->raw; } /** - * Gets the SameSite attribute. - * - * @return string|null + * Checks whether the cookie should be tied to the top-level site in cross-site context. */ - public function getSameSite() + public function isPartitioned(): bool + { + return $this->partitioned; + } + + /** + * @return self::SAMESITE_*|null + */ + public function getSameSite(): ?string { return $this->sameSite; } diff --git a/lib/symfony/http-foundation/Exception/BadRequestException.php b/lib/symfony/http-foundation/Exception/BadRequestException.php index e4bb309c4..505e1cfde 100644 --- a/lib/symfony/http-foundation/Exception/BadRequestException.php +++ b/lib/symfony/http-foundation/Exception/BadRequestException.php @@ -14,6 +14,6 @@ namespace Symfony\Component\HttpFoundation\Exception; /** * Raised when a user sends a malformed request. */ -class BadRequestException extends \UnexpectedValueException implements RequestExceptionInterface +class BadRequestException extends UnexpectedValueException implements RequestExceptionInterface { } diff --git a/lib/symfony/http-foundation/Exception/ConflictingHeadersException.php b/lib/symfony/http-foundation/Exception/ConflictingHeadersException.php index 5fcf5b426..77aa0e1ee 100644 --- a/lib/symfony/http-foundation/Exception/ConflictingHeadersException.php +++ b/lib/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -16,6 +16,6 @@ namespace Symfony\Component\HttpFoundation\Exception; * * @author Magnus Nordlander */ -class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface +class ConflictingHeadersException extends UnexpectedValueException implements RequestExceptionInterface { } diff --git a/lib/symfony/http-foundation/Exception/JsonException.php b/lib/symfony/http-foundation/Exception/JsonException.php index 5990e760e..6d1e0aecb 100644 --- a/lib/symfony/http-foundation/Exception/JsonException.php +++ b/lib/symfony/http-foundation/Exception/JsonException.php @@ -16,6 +16,6 @@ namespace Symfony\Component\HttpFoundation\Exception; * * @author Tobias Nyholm */ -final class JsonException extends \UnexpectedValueException implements RequestExceptionInterface +final class JsonException extends UnexpectedValueException implements RequestExceptionInterface { } diff --git a/lib/symfony/http-foundation/Exception/SuspiciousOperationException.php b/lib/symfony/http-foundation/Exception/SuspiciousOperationException.php index ae7a5f133..4818ef2c8 100644 --- a/lib/symfony/http-foundation/Exception/SuspiciousOperationException.php +++ b/lib/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -15,6 +15,6 @@ namespace Symfony\Component\HttpFoundation\Exception; * Raised when a user has performed an operation that should be considered * suspicious from a security perspective. */ -class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface +class SuspiciousOperationException extends UnexpectedValueException implements RequestExceptionInterface { } diff --git a/lib/symfony/http-foundation/Exception/UnexpectedValueException.php b/lib/symfony/http-foundation/Exception/UnexpectedValueException.php new file mode 100644 index 000000000..c3e6c9d6d --- /dev/null +++ b/lib/symfony/http-foundation/Exception/UnexpectedValueException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +class UnexpectedValueException extends \UnexpectedValueException +{ +} diff --git a/lib/symfony/http-foundation/ExpressionRequestMatcher.php b/lib/symfony/http-foundation/ExpressionRequestMatcher.php index 26bed7d37..fe65e920d 100644 --- a/lib/symfony/http-foundation/ExpressionRequestMatcher.php +++ b/lib/symfony/http-foundation/ExpressionRequestMatcher.php @@ -11,28 +11,37 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher as NewExpressionRequestMatcher; + +trigger_deprecation('symfony/http-foundation', '6.2', 'The "%s" class is deprecated, use "%s" instead.', ExpressionRequestMatcher::class, NewExpressionRequestMatcher::class); /** * ExpressionRequestMatcher uses an expression to match a Request. * * @author Fabien Potencier + * + * @deprecated since Symfony 6.2, use "Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher" instead */ class ExpressionRequestMatcher extends RequestMatcher { - private $language; - private $expression; + private ExpressionLanguage $language; + private Expression|string $expression; - public function setExpression(ExpressionLanguage $language, $expression) + /** + * @return void + */ + public function setExpression(ExpressionLanguage $language, Expression|string $expression) { $this->language = $language; $this->expression = $expression; } - public function matches(Request $request) + public function matches(Request $request): bool { - if (!$this->language) { - throw new \LogicException('Unable to match the request as the expression language is not available.'); + if (!isset($this->language)) { + throw new \LogicException('Unable to match the request as the expression language is not available. Try running "composer require symfony/expression-language".'); } return $this->language->evaluate($this->expression, [ diff --git a/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php index 8533f99a8..905bd5962 100644 --- a/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php +++ b/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -13,7 +13,7 @@ namespace Symfony\Component\HttpFoundation\File\Exception; class UnexpectedTypeException extends FileException { - public function __construct($value, string $expectedType) + public function __construct(mixed $value, string $expectedType) { parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value))); } diff --git a/lib/symfony/http-foundation/File/File.php b/lib/symfony/http-foundation/File/File.php index d941577d2..e8ce4bcf8 100644 --- a/lib/symfony/http-foundation/File/File.php +++ b/lib/symfony/http-foundation/File/File.php @@ -47,12 +47,10 @@ class File extends \SplFileInfo * This method uses the mime type as guessed by getMimeType() * to guess the file extension. * - * @return string|null - * * @see MimeTypes * @see getMimeType() */ - public function guessExtension() + public function guessExtension(): ?string { if (!class_exists(MimeTypes::class)) { throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); @@ -68,11 +66,9 @@ class File extends \SplFileInfo * which uses finfo_file() then the "file" system binary, * depending on which of those are available. * - * @return string|null - * * @see MimeTypes */ - public function getMimeType() + public function getMimeType(): ?string { if (!class_exists(MimeTypes::class)) { throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".'); @@ -84,11 +80,9 @@ class File extends \SplFileInfo /** * Moves the file to a new location. * - * @return self - * * @throws FileException if the target file could not be created */ - public function move(string $directory, string $name = null) + public function move(string $directory, string $name = null): self { $target = $this->getTargetFile($directory, $name); @@ -118,10 +112,7 @@ class File extends \SplFileInfo return $content; } - /** - * @return self - */ - protected function getTargetFile(string $directory, string $name = null) + protected function getTargetFile(string $directory, string $name = null): self { if (!is_dir($directory)) { if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { @@ -138,10 +129,8 @@ class File extends \SplFileInfo /** * Returns locale independent base name of the given path. - * - * @return string */ - protected function getName(string $name) + protected function getName(string $name): string { $originalName = str_replace('\\', '/', $name); $pos = strrpos($originalName, '/'); diff --git a/lib/symfony/http-foundation/File/Stream.php b/lib/symfony/http-foundation/File/Stream.php index cef3e0397..2c156b2e4 100644 --- a/lib/symfony/http-foundation/File/Stream.php +++ b/lib/symfony/http-foundation/File/Stream.php @@ -18,13 +18,7 @@ namespace Symfony\Component\HttpFoundation\File; */ class Stream extends File { - /** - * {@inheritdoc} - * - * @return int|false - */ - #[\ReturnTypeWillChange] - public function getSize() + public function getSize(): int|false { return false; } diff --git a/lib/symfony/http-foundation/File/UploadedFile.php b/lib/symfony/http-foundation/File/UploadedFile.php index fcc629913..e27cf3812 100644 --- a/lib/symfony/http-foundation/File/UploadedFile.php +++ b/lib/symfony/http-foundation/File/UploadedFile.php @@ -31,10 +31,10 @@ use Symfony\Component\Mime\MimeTypes; */ class UploadedFile extends File { - private $test; - private $originalName; - private $mimeType; - private $error; + private bool $test; + private string $originalName; + private string $mimeType; + private int $error; /** * Accepts the information of the uploaded file as provided by the PHP global $_FILES. @@ -74,11 +74,9 @@ class UploadedFile extends File * Returns the original file name. * * It is extracted from the request from which the file has been uploaded. - * Then it should not be considered as a safe value. - * - * @return string + * This should not be considered as a safe value to use for a file name on your servers. */ - public function getClientOriginalName() + public function getClientOriginalName(): string { return $this->originalName; } @@ -87,11 +85,9 @@ class UploadedFile extends File * Returns the original file extension. * * It is extracted from the original file name that was uploaded. - * Then it should not be considered as a safe value. - * - * @return string + * This should not be considered as a safe value to use for a file name on your servers. */ - public function getClientOriginalExtension() + public function getClientOriginalExtension(): string { return pathinfo($this->originalName, \PATHINFO_EXTENSION); } @@ -105,11 +101,9 @@ class UploadedFile extends File * For a trusted mime type, use getMimeType() instead (which guesses the mime * type based on the file content). * - * @return string - * * @see getMimeType() */ - public function getClientMimeType() + public function getClientMimeType(): string { return $this->mimeType; } @@ -126,12 +120,10 @@ class UploadedFile extends File * For a trusted extension, use guessExtension() instead (which guesses * the extension based on the guessed mime type for the file). * - * @return string|null - * * @see guessExtension() * @see getClientMimeType() */ - public function guessClientExtension() + public function guessClientExtension(): ?string { if (!class_exists(MimeTypes::class)) { throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); @@ -145,20 +137,16 @@ class UploadedFile extends File * * If the upload was successful, the constant UPLOAD_ERR_OK is returned. * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. - * - * @return int */ - public function getError() + public function getError(): int { return $this->error; } /** * Returns whether the file has been uploaded with HTTP and no error occurred. - * - * @return bool */ - public function isValid() + public function isValid(): bool { $isOk = \UPLOAD_ERR_OK === $this->error; @@ -168,11 +156,9 @@ class UploadedFile extends File /** * Moves the file to a new location. * - * @return File - * * @throws FileException if, for any reason, the file could not have been moved */ - public function move(string $directory, string $name = null) + public function move(string $directory, string $name = null): File { if ($this->isValid()) { if ($this->test) { @@ -221,7 +207,7 @@ class UploadedFile extends File * * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX) */ - public static function getMaxFilesize() + public static function getMaxFilesize(): int|float { $sizePostMax = self::parseFilesize(\ini_get('post_max_size')); $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize')); @@ -229,12 +215,7 @@ class UploadedFile extends File return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); } - /** - * Returns the given size from an ini value in bytes. - * - * @return int|float Returns float if size > PHP_INT_MAX - */ - private static function parseFilesize(string $size) + private static function parseFilesize(string $size): int|float { if ('' === $size) { return 0; @@ -266,10 +247,8 @@ class UploadedFile extends File /** * Returns an informative upload error message. - * - * @return string */ - public function getErrorMessage() + public function getErrorMessage(): string { static $errors = [ \UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', diff --git a/lib/symfony/http-foundation/FileBag.php b/lib/symfony/http-foundation/FileBag.php index ff5ab7778..b74a02e2e 100644 --- a/lib/symfony/http-foundation/FileBag.php +++ b/lib/symfony/http-foundation/FileBag.php @@ -32,7 +32,7 @@ class FileBag extends ParameterBag } /** - * {@inheritdoc} + * @return void */ public function replace(array $files = []) { @@ -41,9 +41,9 @@ class FileBag extends ParameterBag } /** - * {@inheritdoc} + * @return void */ - public function set(string $key, $value) + public function set(string $key, mixed $value) { if (!\is_array($value) && !$value instanceof UploadedFile) { throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); @@ -53,7 +53,7 @@ class FileBag extends ParameterBag } /** - * {@inheritdoc} + * @return void */ public function add(array $files = []) { @@ -65,11 +65,9 @@ class FileBag extends ParameterBag /** * Converts uploaded files to UploadedFile instances. * - * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information - * * @return UploadedFile[]|UploadedFile|null */ - protected function convertFileInformation($file) + protected function convertFileInformation(array|UploadedFile $file): array|UploadedFile|null { if ($file instanceof UploadedFile) { return $file; @@ -86,7 +84,7 @@ class FileBag extends ParameterBag $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false); } } else { - $file = array_map(function ($v) { return $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v; }, $file); + $file = array_map(fn ($v) => $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v, $file); if (array_keys($keys) === $keys) { $file = array_filter($file); } @@ -106,10 +104,8 @@ class FileBag extends ParameterBag * * It's safe to pass an already converted array, in which case this method * just returns the original array unmodified. - * - * @return array */ - protected function fixPhpFilesArray(array $data) + protected function fixPhpFilesArray(array $data): array { // Remove extra key added by PHP 8.1. unset($data['full_path']); diff --git a/lib/symfony/http-foundation/HeaderBag.php b/lib/symfony/http-foundation/HeaderBag.php index 4683a6840..9a3d5549f 100644 --- a/lib/symfony/http-foundation/HeaderBag.php +++ b/lib/symfony/http-foundation/HeaderBag.php @@ -18,7 +18,7 @@ namespace Symfony\Component\HttpFoundation; * * @implements \IteratorAggregate> */ -class HeaderBag implements \IteratorAggregate, \Countable +class HeaderBag implements \IteratorAggregate, \Countable, \Stringable { protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; @@ -38,10 +38,8 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns the headers as a string. - * - * @return string */ - public function __toString() + public function __toString(): string { if (!$headers = $this->all()) { return ''; @@ -65,9 +63,9 @@ class HeaderBag implements \IteratorAggregate, \Countable * * @param string|null $key The name of the headers to return or null to get them all * - * @return array>|array + * @return ($key is null ? array> : list) */ - public function all(string $key = null) + public function all(string $key = null): array { if (null !== $key) { return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; @@ -81,13 +79,15 @@ class HeaderBag implements \IteratorAggregate, \Countable * * @return string[] */ - public function keys() + public function keys(): array { return array_keys($this->all()); } /** * Replaces the current HTTP headers by a new set. + * + * @return void */ public function replace(array $headers = []) { @@ -97,6 +97,8 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Adds new headers the current HTTP headers set. + * + * @return void */ public function add(array $headers) { @@ -107,10 +109,8 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns the first header by name or the default one. - * - * @return string|null */ - public function get(string $key, string $default = null) + public function get(string $key, string $default = null): ?string { $headers = $this->all($key); @@ -130,8 +130,10 @@ class HeaderBag implements \IteratorAggregate, \Countable * * @param string|string[]|null $values The value or an array of values * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return void */ - public function set(string $key, $values, bool $replace = true) + public function set(string $key, string|array|null $values, bool $replace = true) { $key = strtr($key, self::UPPER, self::LOWER); @@ -158,26 +160,24 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns true if the HTTP header is defined. - * - * @return bool */ - public function has(string $key) + public function has(string $key): bool { return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all()); } /** * Returns true if the given HTTP header contains the given value. - * - * @return bool */ - public function contains(string $key, string $value) + public function contains(string $key, string $value): bool { return \in_array($value, $this->all($key)); } /** * Removes a header. + * + * @return void */ public function remove(string $key) { @@ -193,17 +193,17 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns the HTTP header value converted to a date. * - * @return \DateTimeInterface|null + * @return \DateTimeImmutable|null * * @throws \RuntimeException When the HTTP header is not parseable */ - public function getDate(string $key, \DateTime $default = null) + public function getDate(string $key, \DateTimeInterface $default = null): ?\DateTimeInterface { if (null === $value = $this->get($key)) { - return $default; + return null !== $default ? \DateTimeImmutable::createFromInterface($default) : null; } - if (false === $date = \DateTime::createFromFormat(\DATE_RFC2822, $value)) { + if (false === $date = \DateTimeImmutable::createFromFormat(\DATE_RFC2822, $value)) { throw new \RuntimeException(sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value)); } @@ -213,9 +213,9 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Adds a custom Cache-Control directive. * - * @param bool|string $value The Cache-Control directive value + * @return void */ - public function addCacheControlDirective(string $key, $value = true) + public function addCacheControlDirective(string $key, bool|string $value = true) { $this->cacheControl[$key] = $value; @@ -224,26 +224,24 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns true if the Cache-Control directive is defined. - * - * @return bool */ - public function hasCacheControlDirective(string $key) + public function hasCacheControlDirective(string $key): bool { return \array_key_exists($key, $this->cacheControl); } /** * Returns a Cache-Control directive value by name. - * - * @return bool|string|null */ - public function getCacheControlDirective(string $key) + public function getCacheControlDirective(string $key): bool|string|null { return $this->cacheControl[$key] ?? null; } /** * Removes a Cache-Control directive. + * + * @return void */ public function removeCacheControlDirective(string $key) { @@ -257,23 +255,22 @@ class HeaderBag implements \IteratorAggregate, \Countable * * @return \ArrayIterator> */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->headers); } /** * Returns the number of headers. - * - * @return int */ - #[\ReturnTypeWillChange] - public function count() + public function count(): int { return \count($this->headers); } + /** + * @return string + */ protected function getCacheControlHeader() { ksort($this->cacheControl); @@ -283,10 +280,8 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Parses a Cache-Control HTTP header. - * - * @return array */ - protected function parseCacheControl(string $header) + protected function parseCacheControl(string $header): array { $parts = HeaderUtils::split($header, ',='); diff --git a/lib/symfony/http-foundation/HeaderUtils.php b/lib/symfony/http-foundation/HeaderUtils.php index 1d56be080..3456edace 100644 --- a/lib/symfony/http-foundation/HeaderUtils.php +++ b/lib/symfony/http-foundation/HeaderUtils.php @@ -33,17 +33,21 @@ class HeaderUtils * * Example: * - * HeaderUtils::split("da, en-gb;q=0.8", ",;") + * HeaderUtils::split('da, en-gb;q=0.8', ',;') * // => ['da'], ['en-gb', 'q=0.8']] * * @param string $separators List of characters to split on, ordered by - * precedence, e.g. ",", ";=", or ",;=" + * precedence, e.g. ',', ';=', or ',;=' * * @return array Nested array with as many levels as there are characters in * $separators */ public static function split(string $header, string $separators): array { + if ('' === $separators) { + throw new \InvalidArgumentException('At least one separator must be specified.'); + } + $quotedSeparators = preg_quote($separators, '/'); preg_match_all(' @@ -77,8 +81,8 @@ class HeaderUtils * * Example: * - * HeaderUtils::combine([["foo", "abc"], ["bar"]]) - * // => ["foo" => "abc", "bar" => true] + * HeaderUtils::combine([['foo', 'abc'], ['bar']]) + * // => ['foo' => 'abc', 'bar' => true] */ public static function combine(array $parts): array { @@ -95,13 +99,13 @@ class HeaderUtils /** * Joins an associative array into a string for use in an HTTP header. * - * The key and value of each entry are joined with "=", and all entries + * The key and value of each entry are joined with '=', and all entries * are joined with the specified separator and an additional space (for * readability). Values are quoted if necessary. * * Example: * - * HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",") + * HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',') * // => 'foo=abc, bar, baz="a b c"' */ public static function toString(array $assoc, string $separator): string @@ -138,7 +142,7 @@ class HeaderUtils * Decodes a quoted string. * * If passed an unquoted string that matches the "token" construct (as - * defined in the HTTP specification), it is passed through verbatimly. + * defined in the HTTP specification), it is passed through verbatim. */ public static function unquote(string $s): string { @@ -252,40 +256,37 @@ class HeaderUtils private static function groupParts(array $matches, string $separators, bool $first = true): array { $separator = $separators[0]; - $partSeparators = substr($separators, 1); - + $separators = substr($separators, 1) ?: ''; $i = 0; + + if ('' === $separators && !$first) { + $parts = ['']; + + foreach ($matches as $match) { + if (!$i && isset($match['separator'])) { + $i = 1; + $parts[1] = ''; + } else { + $parts[$i] .= self::unquote($match[0]); + } + } + + return $parts; + } + + $parts = []; $partMatches = []; - $previousMatchWasSeparator = false; + foreach ($matches as $match) { - if (!$first && $previousMatchWasSeparator && isset($match['separator']) && $match['separator'] === $separator) { - $previousMatchWasSeparator = true; - $partMatches[$i][] = $match; - } elseif (isset($match['separator']) && $match['separator'] === $separator) { - $previousMatchWasSeparator = true; + if (($match['separator'] ?? null) === $separator) { ++$i; } else { - $previousMatchWasSeparator = false; $partMatches[$i][] = $match; } } - $parts = []; - if ($partSeparators) { - foreach ($partMatches as $matches) { - $parts[] = self::groupParts($matches, $partSeparators, false); - } - } else { - foreach ($partMatches as $matches) { - $parts[] = self::unquote($matches[0][0]); - } - - if (!$first && 2 < \count($parts)) { - $parts = [ - $parts[0], - implode($separator, \array_slice($parts, 1)), - ]; - } + foreach ($partMatches as $matches) { + $parts[] = '' === $separators ? self::unquote($matches[0][0]) : self::groupParts($matches, $separators, false); } return $parts; diff --git a/lib/symfony/http-foundation/InputBag.php b/lib/symfony/http-foundation/InputBag.php index a9d3cd82a..7676d9fe7 100644 --- a/lib/symfony/http-foundation/InputBag.php +++ b/lib/symfony/http-foundation/InputBag.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use Symfony\Component\HttpFoundation\Exception\UnexpectedValueException; /** * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE. @@ -24,36 +25,26 @@ final class InputBag extends ParameterBag * Returns a scalar input value by name. * * @param string|int|float|bool|null $default The default value if the input key does not exist - * - * @return string|int|float|bool|null */ - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): string|int|float|bool|null { - if (null !== $default && !\is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) { - trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__); + if (null !== $default && !\is_scalar($default) && !$default instanceof \Stringable) { + throw new \InvalidArgumentException(sprintf('Expected a scalar value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default))); } $value = parent::get($key, $this); - if (null !== $value && $this !== $value && !\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { - trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-scalar value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__); + if (null !== $value && $this !== $value && !\is_scalar($value) && !$value instanceof \Stringable) { + throw new BadRequestException(sprintf('Input value "%s" contains a non-scalar value.', $key)); } return $this === $value ? $default : $value; } - /** - * {@inheritdoc} - */ - public function all(string $key = null): array - { - return parent::all($key); - } - /** * Replaces the current input values by a new set. */ - public function replace(array $inputs = []) + public function replace(array $inputs = []): void { $this->parameters = []; $this->add($inputs); @@ -62,7 +53,7 @@ final class InputBag extends ParameterBag /** * Adds input values. */ - public function add(array $inputs = []) + public function add(array $inputs = []): void { foreach ($inputs as $input => $value) { $this->set($input, $value); @@ -74,19 +65,44 @@ final class InputBag extends ParameterBag * * @param string|int|float|bool|array|null $value */ - public function set(string $key, $value) + public function set(string $key, mixed $value): void { - if (null !== $value && !\is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) { - trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__); + if (null !== $value && !\is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) { + throw new \InvalidArgumentException(sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value))); } $this->parameters[$key] = $value; } /** - * {@inheritdoc} + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T */ - public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) + public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + { + try { + return parent::getEnum($key, $class, $default); + } catch (UnexpectedValueException $e) { + throw new BadRequestException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the parameter value converted to string. + */ + public function getString(string $key, string $default = ''): string + { + // Shortcuts the parent method because the validation on scalar is already done in get(). + return (string) $this->get($key, $default); + } + + public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed { $value = $this->has($key) ? $this->all()[$key] : $default; @@ -96,18 +112,29 @@ final class InputBag extends ParameterBag } if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) { - trigger_deprecation('symfony/http-foundation', '5.1', 'Filtering an array value with "%s()" without passing the FILTER_REQUIRE_ARRAY or FILTER_FORCE_ARRAY flag is deprecated', __METHOD__); - - if (!isset($options['flags'])) { - $options['flags'] = \FILTER_REQUIRE_ARRAY; - } + throw new BadRequestException(sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key)); } if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { - trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); - // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); } - return filter_var($value, $filter, $options); + $options['flags'] ??= 0; + $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE; + $options['flags'] |= \FILTER_NULL_ON_FAILURE; + + $value = filter_var($value, $filter, $options); + + if (null !== $value || $nullOnFailure) { + return $value; + } + + $method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1]; + $method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter'; + $hint = 'filter' === $method ? 'pass' : 'use method "filter()" with'; + + trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw a "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, BadRequestException::class); + + return false; } } diff --git a/lib/symfony/http-foundation/IpUtils.php b/lib/symfony/http-foundation/IpUtils.php index 2f31284e3..ceab620c2 100644 --- a/lib/symfony/http-foundation/IpUtils.php +++ b/lib/symfony/http-foundation/IpUtils.php @@ -18,7 +18,22 @@ namespace Symfony\Component\HttpFoundation; */ class IpUtils { - private static $checkedIps = []; + public const PRIVATE_SUBNETS = [ + '127.0.0.0/8', // RFC1700 (Loopback) + '10.0.0.0/8', // RFC1918 + '192.168.0.0/16', // RFC1918 + '172.16.0.0/12', // RFC1918 + '169.254.0.0/16', // RFC3927 + '0.0.0.0/8', // RFC5735 + '240.0.0.0/4', // RFC1112 + '::1/128', // Loopback + 'fc00::/7', // Unique Local Address + 'fe80::/10', // Link Local Address + '::ffff:0:0/96', // IPv4 translations + '::/128', // Unspecified address + ]; + + private static array $checkedIps = []; /** * This class should not be instantiated. @@ -31,17 +46,9 @@ class IpUtils * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. * * @param string|array $ips List of IPs or subnets (can be a string if only a single one) - * - * @return bool */ - public static function checkIp(?string $requestIp, $ips) + public static function checkIp(string $requestIp, string|array $ips): bool { - if (null === $requestIp) { - trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - return false; - } - if (!\is_array($ips)) { $ips = [$ips]; } @@ -65,32 +72,26 @@ class IpUtils * * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet */ - public static function checkIp4(?string $requestIp, string $ip) + public static function checkIp4(string $requestIp, string $ip): bool { - if (null === $requestIp) { - trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - return false; - } - - $cacheKey = $requestIp.'-'.$ip; - if (isset(self::$checkedIps[$cacheKey])) { - return self::$checkedIps[$cacheKey]; + $cacheKey = $requestIp.'-'.$ip.'-v4'; + if (null !== $cacheValue = self::getCacheResult($cacheKey)) { + return $cacheValue; } if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } if (str_contains($ip, '/')) { [$address, $netmask] = explode('/', $ip, 2); if ('0' === $netmask) { - return self::$checkedIps[$cacheKey] = false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); + return self::setCacheResult($cacheKey, false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)); } if ($netmask < 0 || $netmask > 32) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } } else { $address = $ip; @@ -98,10 +99,10 @@ class IpUtils } if (false === ip2long($address)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } - return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + return self::setCacheResult($cacheKey, 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask)); } /** @@ -114,21 +115,13 @@ class IpUtils * * @param string $ip IPv6 address or subnet in CIDR notation * - * @return bool - * * @throws \RuntimeException When IPV6 support is not enabled */ - public static function checkIp6(?string $requestIp, string $ip) + public static function checkIp6(string $requestIp, string $ip): bool { - if (null === $requestIp) { - trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - return false; - } - - $cacheKey = $requestIp.'-'.$ip; - if (isset(self::$checkedIps[$cacheKey])) { - return self::$checkedIps[$cacheKey]; + $cacheKey = $requestIp.'-'.$ip.'-v6'; + if (null !== $cacheValue = self::getCacheResult($cacheKey)) { + return $cacheValue; } if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) { @@ -137,14 +130,14 @@ class IpUtils // Check to see if we were given a IP4 $requestIp or $ip by mistake if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } if (str_contains($ip, '/')) { [$address, $netmask] = explode('/', $ip, 2); if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } if ('0' === $netmask) { @@ -152,11 +145,11 @@ class IpUtils } if ($netmask < 1 || $netmask > 128) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } } else { if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } $address = $ip; @@ -167,7 +160,7 @@ class IpUtils $bytesTest = unpack('n*', @inet_pton($requestIp)); if (!$bytesAddr || !$bytesTest) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { @@ -175,11 +168,11 @@ class IpUtils $left = ($left <= 16) ? $left : 16; $mask = ~(0xFFFF >> $left) & 0xFFFF; if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } } - return self::$checkedIps[$cacheKey] = true; + return self::setCacheResult($cacheKey, true); } /** @@ -190,7 +183,7 @@ class IpUtils public static function anonymize(string $ip): string { $wrappedIPv6 = false; - if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) { + if (str_starts_with($ip, '[') && str_ends_with($ip, ']')) { $wrappedIPv6 = true; $ip = substr($ip, 1, -1); } @@ -213,4 +206,36 @@ class IpUtils return $ip; } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of private IP subnets. + */ + public static function isPrivateIp(string $requestIp): bool + { + return self::checkIp($requestIp, self::PRIVATE_SUBNETS); + } + + private static function getCacheResult(string $cacheKey): ?bool + { + if (isset(self::$checkedIps[$cacheKey])) { + // Move the item last in cache (LRU) + $value = self::$checkedIps[$cacheKey]; + unset(self::$checkedIps[$cacheKey]); + self::$checkedIps[$cacheKey] = $value; + + return self::$checkedIps[$cacheKey]; + } + + return null; + } + + private static function setCacheResult(string $cacheKey, bool $result): bool + { + if (1000 < \count(self::$checkedIps)) { + // stop memory leak if there are many keys + self::$checkedIps = \array_slice(self::$checkedIps, 500, null, true); + } + + return self::$checkedIps[$cacheKey] = $result; + } } diff --git a/lib/symfony/http-foundation/JsonResponse.php b/lib/symfony/http-foundation/JsonResponse.php index 501a6387d..8dd250a36 100644 --- a/lib/symfony/http-foundation/JsonResponse.php +++ b/lib/symfony/http-foundation/JsonResponse.php @@ -34,12 +34,9 @@ class JsonResponse extends Response protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; /** - * @param mixed $data The response data - * @param int $status The response status code - * @param array $headers An array of response headers - * @param bool $json If the data is already a JSON string + * @param bool $json If the data is already a JSON string */ - public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false) + public function __construct(mixed $data = null, int $status = 200, array $headers = [], bool $json = false) { parent::__construct('', $status, $headers); @@ -47,36 +44,11 @@ class JsonResponse extends Response throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data))); } - if (null === $data) { - $data = new \ArrayObject(); - } + $data ??= new \ArrayObject(); $json ? $this->setJson($data) : $this->setData($data); } - /** - * Factory method for chainability. - * - * Example: - * - * return JsonResponse::create(['key' => 'value']) - * ->setSharedMaxAge(300); - * - * @param mixed $data The JSON response data - * @param int $status The response status code - * @param array $headers An array of response headers - * - * @return static - * - * @deprecated since Symfony 5.1, use __construct() instead. - */ - public static function create($data = null, int $status = 200, array $headers = []) - { - trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); - - return new static($data, $status, $headers); - } - /** * Factory method for chainability. * @@ -86,12 +58,10 @@ class JsonResponse extends Response * ->setSharedMaxAge(300); * * @param string $data The JSON response string - * @param int $status The response status code + * @param int $status The response status code (200 "OK" by default) * @param array $headers An array of response headers - * - * @return static */ - public static function fromJsonString(string $data, int $status = 200, array $headers = []) + public static function fromJsonString(string $data, int $status = 200, array $headers = []): static { return new static($data, $status, $headers, true); } @@ -105,8 +75,11 @@ class JsonResponse extends Response * * @throws \InvalidArgumentException When the callback name is not valid */ - public function setCallback(string $callback = null) + public function setCallback(string $callback = null): static { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if (null !== $callback) { // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/ // partially taken from https://github.com/willdurand/JsonpCallbackValidator @@ -136,7 +109,7 @@ class JsonResponse extends Response * * @return $this */ - public function setJson(string $json) + public function setJson(string $json): static { $this->data = $json; @@ -146,24 +119,22 @@ class JsonResponse extends Response /** * Sets the data to be sent as JSON. * - * @param mixed $data - * * @return $this * * @throws \InvalidArgumentException */ - public function setData($data = []) + public function setData(mixed $data = []): static { try { $data = json_encode($data, $this->encodingOptions); } catch (\Exception $e) { - if ('Exception' === \get_class($e) && str_starts_with($e->getMessage(), 'Failed calling ')) { + if ('Exception' === $e::class && str_starts_with($e->getMessage(), 'Failed calling ')) { throw $e->getPrevious() ?: $e; } throw $e; } - if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $this->encodingOptions)) { + if (\JSON_THROW_ON_ERROR & $this->encodingOptions) { return $this->setJson($data); } @@ -176,10 +147,8 @@ class JsonResponse extends Response /** * Returns options used while encoding data to JSON. - * - * @return int */ - public function getEncodingOptions() + public function getEncodingOptions(): int { return $this->encodingOptions; } @@ -189,7 +158,7 @@ class JsonResponse extends Response * * @return $this */ - public function setEncodingOptions(int $encodingOptions) + public function setEncodingOptions(int $encodingOptions): static { $this->encodingOptions = $encodingOptions; @@ -201,7 +170,7 @@ class JsonResponse extends Response * * @return $this */ - protected function update() + protected function update(): static { if (null !== $this->callback) { // Not using application/javascript for compatibility reasons with older browsers. diff --git a/lib/symfony/http-foundation/LICENSE b/lib/symfony/http-foundation/LICENSE index 008370457..0138f8f07 100644 --- a/lib/symfony/http-foundation/LICENSE +++ b/lib/symfony/http-foundation/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2023 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/http-foundation/ParameterBag.php b/lib/symfony/http-foundation/ParameterBag.php index e1f89d69e..0456e474c 100644 --- a/lib/symfony/http-foundation/ParameterBag.php +++ b/lib/symfony/http-foundation/ParameterBag.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use Symfony\Component\HttpFoundation\Exception\UnexpectedValueException; /** * ParameterBag is a container for key/value pairs. @@ -36,13 +37,9 @@ class ParameterBag implements \IteratorAggregate, \Countable * Returns the parameters. * * @param string|null $key The name of the parameter to return or null to get them all - * - * @return array */ - public function all(/* string $key = null */) + public function all(string $key = null): array { - $key = \func_num_args() > 0 ? func_get_arg(0) : null; - if (null === $key) { return $this->parameters; } @@ -56,16 +53,16 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the parameter keys. - * - * @return array */ - public function keys() + public function keys(): array { return array_keys($this->parameters); } /** * Replaces the current parameters by a new set. + * + * @return void */ public function replace(array $parameters = []) { @@ -74,46 +71,39 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Adds parameters. + * + * @return void */ public function add(array $parameters = []) { $this->parameters = array_replace($this->parameters, $parameters); } - /** - * Returns a parameter by name. - * - * @param mixed $default The default value if the parameter key does not exist - * - * @return mixed - */ - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; } /** - * Sets a parameter by name. - * - * @param mixed $value The value + * @return void */ - public function set(string $key, $value) + public function set(string $key, mixed $value) { $this->parameters[$key] = $value; } /** * Returns true if the parameter is defined. - * - * @return bool */ - public function has(string $key) + public function has(string $key): bool { return \array_key_exists($key, $this->parameters); } /** * Removes a parameter. + * + * @return void */ public function remove(string $key) { @@ -122,67 +112,92 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the alphabetic characters of the parameter value. - * - * @return string */ - public function getAlpha(string $key, string $default = '') + public function getAlpha(string $key, string $default = ''): string { - return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + return preg_replace('/[^[:alpha:]]/', '', $this->getString($key, $default)); } /** * Returns the alphabetic characters and digits of the parameter value. - * - * @return string */ - public function getAlnum(string $key, string $default = '') + public function getAlnum(string $key, string $default = ''): string { - return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + return preg_replace('/[^[:alnum:]]/', '', $this->getString($key, $default)); } /** * Returns the digits of the parameter value. - * - * @return string */ - public function getDigits(string $key, string $default = '') + public function getDigits(string $key, string $default = ''): string { - // we need to remove - and + because they're allowed in the filter - return str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT)); + return preg_replace('/[^[:digit:]]/', '', $this->getString($key, $default)); + } + + /** + * Returns the parameter as string. + */ + public function getString(string $key, string $default = ''): string + { + $value = $this->get($key, $default); + if (!\is_scalar($value) && !$value instanceof \Stringable) { + throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key)); + } + + return (string) $value; } /** * Returns the parameter value converted to integer. - * - * @return int */ - public function getInt(string $key, int $default = 0) + public function getInt(string $key, int $default = 0): int { - return (int) $this->get($key, $default); + // In 7.0 remove the fallback to 0, in case of failure an exception will be thrown + return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR]) ?: 0; } /** * Returns the parameter value converted to boolean. - * - * @return bool */ - public function getBoolean(string $key, bool $default = false) + public function getBoolean(string $key, bool $default = false): bool { - return $this->filter($key, $default, \FILTER_VALIDATE_BOOLEAN); + return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR]); + } + + /** + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T + */ + public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + { + $value = $this->get($key); + + if (null === $value) { + return $default; + } + + try { + return $class::from($value); + } catch (\ValueError|\TypeError $e) { + throw new UnexpectedValueException(sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e); + } } /** * Filter key. * - * @param mixed $default Default = null - * @param int $filter FILTER_* constant - * @param mixed $options Filter options + * @param int $filter FILTER_* constant + * @param int|array{flags?: int, options?: array} $options Flags from FILTER_* constants * * @see https://php.net/filter-var - * - * @return mixed */ - public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) + public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed { $value = $this->get($key, $default); @@ -196,12 +211,31 @@ class ParameterBag implements \IteratorAggregate, \Countable $options['flags'] = \FILTER_REQUIRE_ARRAY; } - if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { - trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); - // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + if (\is_object($value) && !$value instanceof \Stringable) { + throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be filtered.', $key)); } - return filter_var($value, $filter, $options); + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + $options['flags'] ??= 0; + $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE; + $options['flags'] |= \FILTER_NULL_ON_FAILURE; + + $value = filter_var($value, $filter, $options); + + if (null !== $value || $nullOnFailure) { + return $value; + } + + $method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1]; + $method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter'; + $hint = 'filter' === $method ? 'pass' : 'use method "filter()" with'; + + trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw an "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, UnexpectedValueException::class); + + return false; } /** @@ -209,19 +243,15 @@ class ParameterBag implements \IteratorAggregate, \Countable * * @return \ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->parameters); } /** * Returns the number of parameters. - * - * @return int */ - #[\ReturnTypeWillChange] - public function count() + public function count(): int { return \count($this->parameters); } diff --git a/lib/symfony/http-foundation/README.md b/lib/symfony/http-foundation/README.md index 424f2c4f0..5cf900744 100644 --- a/lib/symfony/http-foundation/README.md +++ b/lib/symfony/http-foundation/README.md @@ -4,16 +4,6 @@ HttpFoundation Component The HttpFoundation component defines an object-oriented layer for the HTTP specification. -Sponsor -------- - -The HttpFoundation component for Symfony 5.4/6.0 is [backed][1] by [Laravel][2]. - -Laravel is a PHP web development framework that is passionate about maximum developer -happiness. Laravel is built using a variety of bespoke and Symfony based components. - -Help Symfony by [sponsoring][3] its development! - Resources --------- @@ -22,7 +12,3 @@ Resources * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) - -[1]: https://symfony.com/backers -[2]: https://laravel.com/ -[3]: https://symfony.com/sponsor diff --git a/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php b/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php index a6dd993b7..550090f97 100644 --- a/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php +++ b/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php @@ -17,14 +17,24 @@ use Symfony\Component\RateLimiter\Policy\NoLimiter; use Symfony\Component\RateLimiter\RateLimit; /** - * An implementation of RequestRateLimiterInterface that + * An implementation of PeekableRequestRateLimiterInterface that * fits most use-cases. * * @author Wouter de Jong */ -abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface +abstract class AbstractRequestRateLimiter implements PeekableRequestRateLimiterInterface { public function consume(Request $request): RateLimit + { + return $this->doConsume($request, 1); + } + + public function peek(Request $request): RateLimit + { + return $this->doConsume($request, 0); + } + + private function doConsume(Request $request, int $tokens): RateLimit { $limiters = $this->getLimiters($request); if (0 === \count($limiters)) { @@ -33,7 +43,7 @@ abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface $minimalRateLimit = null; foreach ($limiters as $limiter) { - $rateLimit = $limiter->consume(1); + $rateLimit = $limiter->consume($tokens); $minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit; } diff --git a/lib/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php b/lib/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php new file mode 100644 index 000000000..63471af22 --- /dev/null +++ b/lib/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * A request limiter which allows peeking ahead. + * + * This is valuable to reduce the cache backend load in scenarios + * like a login when we only want to consume a token on login failure, + * and where the majority of requests will be successful and thus not + * need to consume a token. + * + * This way we can peek ahead before allowing the request through, and + * only consume if the request failed (1 backend op). This is compared + * to always consuming and then resetting the limit if the request + * is successful (2 backend ops). + * + * @author Jordi Boggiano + */ +interface PeekableRequestRateLimiterInterface extends RequestRateLimiterInterface +{ + public function peek(Request $request): RateLimit; +} diff --git a/lib/symfony/http-foundation/RedirectResponse.php b/lib/symfony/http-foundation/RedirectResponse.php index 2103280c6..a001df81d 100644 --- a/lib/symfony/http-foundation/RedirectResponse.php +++ b/lib/symfony/http-foundation/RedirectResponse.php @@ -25,7 +25,7 @@ class RedirectResponse extends Response * * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., * but practically every browser redirects on paths only as well - * @param int $status The status code (302 by default) + * @param int $status The HTTP status code (302 "Found" by default) * @param array $headers The headers (Location is always set to the given URL) * * @throws \InvalidArgumentException @@ -47,28 +47,10 @@ class RedirectResponse extends Response } } - /** - * Factory method for chainability. - * - * @param string $url The URL to redirect to - * - * @return static - * - * @deprecated since Symfony 5.1, use __construct() instead. - */ - public static function create($url = '', int $status = 302, array $headers = []) - { - trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); - - return new static($url, $status, $headers); - } - /** * Returns the target URL. - * - * @return string */ - public function getTargetUrl() + public function getTargetUrl(): string { return $this->targetUrl; } @@ -80,7 +62,7 @@ class RedirectResponse extends Response * * @throws \InvalidArgumentException */ - public function setTargetUrl(string $url) + public function setTargetUrl(string $url): static { if ('' === $url) { throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); diff --git a/lib/symfony/http-foundation/Request.php b/lib/symfony/http-foundation/Request.php index 10f779d27..10f3a758f 100644 --- a/lib/symfony/http-foundation/Request.php +++ b/lib/symfony/http-foundation/Request.php @@ -48,8 +48,6 @@ class Request public const HEADER_X_FORWARDED_PORT = 0b010000; public const HEADER_X_FORWARDED_PREFIX = 0b100000; - /** @deprecated since Symfony 5.2, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead. */ - public const HEADER_X_FORWARDED_ALL = 0b1011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy public const HEADER_X_FORWARDED_AWS_ELB = 0b0011010; // AWS ELB doesn't send X-Forwarded-Host public const HEADER_X_FORWARDED_TRAEFIK = 0b0111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy @@ -91,6 +89,8 @@ class Request /** * Request body parameters ($_POST). * + * @see getPayload() for portability between content types + * * @var InputBag */ public $request; @@ -136,62 +136,62 @@ class Request protected $content; /** - * @var array + * @var string[]|null */ protected $languages; /** - * @var array + * @var string[]|null */ protected $charsets; /** - * @var array + * @var string[]|null */ protected $encodings; /** - * @var array + * @var string[]|null */ protected $acceptableContentTypes; /** - * @var string + * @var string|null */ protected $pathInfo; /** - * @var string + * @var string|null */ protected $requestUri; /** - * @var string + * @var string|null */ protected $baseUrl; /** - * @var string + * @var string|null */ protected $basePath; /** - * @var string + * @var string|null */ protected $method; /** - * @var string + * @var string|null */ protected $format; /** - * @var SessionInterface|callable(): SessionInterface + * @var SessionInterface|callable():SessionInterface|null */ protected $session; /** - * @var string + * @var string|null */ protected $locale; @@ -201,25 +201,20 @@ class Request protected $defaultLocale = 'en'; /** - * @var array + * @var array|null */ protected static $formats; protected static $requestFactory; - /** - * @var string|null - */ - private $preferredFormat; - private $isHostValid = true; - private $isForwardedValid = true; + private ?string $preferredFormat = null; + private bool $isHostValid = true; + private bool $isForwardedValid = true; + private bool $isSafeContentPreferred; - /** - * @var bool|null - */ - private $isSafeContentPreferred; + private array $trustedValuesCache = []; - private static $trustedHeaderSet = -1; + private static int $trustedHeaderSet = -1; private const FORWARDED_PARAMS = [ self::HEADER_X_FORWARDED_FOR => 'for', @@ -246,6 +241,9 @@ class Request self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX', ]; + /** @var bool */ + private $isIisRewrite = false; + /** * @param array $query The GET parameters * @param array $request The POST parameters @@ -272,6 +270,8 @@ class Request * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param string|resource|null $content The raw body data + * + * @return void */ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { @@ -298,10 +298,8 @@ class Request /** * Creates a new request with values from PHP's super globals. - * - * @return static */ - public static function createFromGlobals() + public static function createFromGlobals(): static { $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); @@ -328,10 +326,8 @@ class Request * @param array $files The request files ($_FILES) * @param array $server The server parameters ($_SERVER) * @param string|resource|null $content The raw body data - * - * @return static */ - public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null) + public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null): static { $server = array_replace([ 'SERVER_NAME' => 'localhost', @@ -353,6 +349,10 @@ class Request $server['REQUEST_METHOD'] = strtoupper($method); $components = parse_url($uri); + if (false === $components) { + trigger_deprecation('symfony/http-foundation', '6.3', 'Calling "%s()" with an invalid URI is deprecated.', __METHOD__); + $components = []; + } if (isset($components['host'])) { $server['SERVER_NAME'] = $components['host']; $server['HTTP_HOST'] = $components['host']; @@ -430,6 +430,8 @@ class Request * This is mainly useful when you need to override the Request class * to keep BC with an existing system. It should not be used for any * other purpose. + * + * @return void */ public static function setFactory(?callable $callable) { @@ -439,16 +441,14 @@ class Request /** * Clones a request and overrides some of its parameters. * - * @param array $query The GET parameters - * @param array $request The POST parameters - * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) - * @param array $cookies The COOKIE parameters - * @param array $files The FILES parameters - * @param array $server The SERVER parameters - * - * @return static + * @param array|null $query The GET parameters + * @param array|null $request The POST parameters + * @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array|null $cookies The COOKIE parameters + * @param array|null $files The FILES parameters + * @param array|null $server The SERVER parameters */ - public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null): static { $dup = clone $this; if (null !== $query) { @@ -509,12 +509,7 @@ class Request $this->headers = clone $this->headers; } - /** - * Returns the request as a string. - * - * @return string - */ - public function __toString() + public function __toString(): string { $content = $this->getContent(); @@ -541,6 +536,8 @@ class Request * * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. * $_FILES is never overridden, see rfc1867 + * + * @return void */ public function overrideGlobals() { @@ -581,12 +578,11 @@ class Request * * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + * + * @return void */ public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) { - if (self::HEADER_X_FORWARDED_ALL === $trustedHeaderSet) { - trigger_deprecation('symfony/http-foundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); - } self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { if ('REMOTE_ADDR' !== $proxy) { $proxies[] = $proxy; @@ -602,9 +598,9 @@ class Request /** * Gets the list of trusted proxies. * - * @return array + * @return string[] */ - public static function getTrustedProxies() + public static function getTrustedProxies(): array { return self::$trustedProxies; } @@ -614,7 +610,7 @@ class Request * * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies */ - public static function getTrustedHeaderSet() + public static function getTrustedHeaderSet(): int { return self::$trustedHeaderSet; } @@ -625,12 +621,12 @@ class Request * You should only list the hosts you manage using regexs. * * @param array $hostPatterns A list of trusted host patterns + * + * @return void */ public static function setTrustedHosts(array $hostPatterns) { - self::$trustedHostPatterns = array_map(function ($hostPattern) { - return sprintf('{%s}i', $hostPattern); - }, $hostPatterns); + self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns); // we need to reset trusted hosts on trusted host patterns change self::$trustedHosts = []; } @@ -638,9 +634,9 @@ class Request /** * Gets the list of trusted host patterns. * - * @return array + * @return string[] */ - public static function getTrustedHosts() + public static function getTrustedHosts(): array { return self::$trustedHostPatterns; } @@ -650,10 +646,8 @@ class Request * * It builds a normalized query string, where keys/value pairs are alphabetized, * have consistent escaping and unneeded delimiters are removed. - * - * @return string */ - public static function normalizeQueryString(?string $qs) + public static function normalizeQueryString(?string $qs): string { if ('' === ($qs ?? '')) { return ''; @@ -675,6 +669,8 @@ class Request * If these methods are not protected against CSRF, this presents a possible vulnerability. * * The HTTP method can only be overridden when the real HTTP method is POST. + * + * @return void */ public static function enableHttpMethodParameterOverride() { @@ -683,10 +679,8 @@ class Request /** * Checks whether support for the _method request parameter is enabled. - * - * @return bool */ - public static function getHttpMethodParameterOverride() + public static function getHttpMethodParameterOverride(): bool { return self::$httpMethodParameterOverride; } @@ -700,13 +694,9 @@ class Request * * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST * - * @param mixed $default The default value if the parameter key does not exist - * - * @return mixed - * - * @internal since Symfony 5.4, use explicit input sources instead + * @internal use explicit input sources instead */ - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { if ($this !== $result = $this->attributes->get($key, $this)) { return $result; @@ -726,9 +716,9 @@ class Request /** * Gets the Session. * - * @return SessionInterface + * @throws SessionNotFoundException When session is not set properly */ - public function getSession() + public function getSession(): SessionInterface { $session = $this->session; if (!$session instanceof SessionInterface && null !== $session) { @@ -745,10 +735,8 @@ class Request /** * Whether the request contains a Session which was started in one of the * previous requests. - * - * @return bool */ - public function hasPreviousSession() + public function hasPreviousSession(): bool { // the check for $this->session avoids malicious users trying to fake a session cookie with proper name return $this->hasSession() && $this->cookies->has($this->getSession()->getName()); @@ -762,16 +750,15 @@ class Request * is associated with a Session instance. * * @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory` - * - * @return bool */ - public function hasSession(/* bool $skipIfUninitialized = false */) + public function hasSession(bool $skipIfUninitialized = false): bool { - $skipIfUninitialized = \func_num_args() > 0 ? func_get_arg(0) : false; - return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface); } + /** + * @return void + */ public function setSession(SessionInterface $session) { $this->session = $session; @@ -782,9 +769,9 @@ class Request * * @param callable(): SessionInterface $factory */ - public function setSessionFactory(callable $factory) + public function setSessionFactory(callable $factory): void { - $this->session = $factory; + $this->session = $factory(...); } /** @@ -796,11 +783,9 @@ class Request * * Use this method carefully; you should use getClientIp() instead. * - * @return array - * * @see getClientIp() */ - public function getClientIps() + public function getClientIps(): array { $ip = $this->server->get('REMOTE_ADDR'); @@ -824,12 +809,10 @@ class Request * ("Client-Ip" for instance), configure it via the $trustedHeaderSet * argument of the Request::setTrustedProxies() method instead. * - * @return string|null - * * @see getClientIps() * @see https://wikipedia.org/wiki/X-Forwarded-For */ - public function getClientIp() + public function getClientIp(): ?string { $ipAddresses = $this->getClientIps(); @@ -838,10 +821,8 @@ class Request /** * Returns current script name. - * - * @return string */ - public function getScriptName() + public function getScriptName(): string { return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); } @@ -860,13 +841,9 @@ class Request * * @return string The raw path (i.e. not urldecoded) */ - public function getPathInfo() + public function getPathInfo(): string { - if (null === $this->pathInfo) { - $this->pathInfo = $this->preparePathInfo(); - } - - return $this->pathInfo; + return $this->pathInfo ??= $this->preparePathInfo(); } /** @@ -881,13 +858,9 @@ class Request * * @return string The raw path (i.e. not urldecoded) */ - public function getBasePath() + public function getBasePath(): string { - if (null === $this->basePath) { - $this->basePath = $this->prepareBasePath(); - } - - return $this->basePath; + return $this->basePath ??= $this->prepareBasePath(); } /** @@ -900,7 +873,7 @@ class Request * * @return string The raw URL (i.e. not urldecoded) */ - public function getBaseUrl() + public function getBaseUrl(): string { $trustedPrefix = ''; @@ -920,19 +893,13 @@ class Request */ private function getBaseUrlReal(): string { - if (null === $this->baseUrl) { - $this->baseUrl = $this->prepareBaseUrl(); - } - - return $this->baseUrl; + return $this->baseUrl ??= $this->prepareBaseUrl(); } /** * Gets the request's scheme. - * - * @return string */ - public function getScheme() + public function getScheme(): string { return $this->isSecure() ? 'https' : 'http'; } @@ -947,7 +914,7 @@ class Request * * @return int|string|null Can be a string if fetched from the server bag */ - public function getPort() + public function getPort(): int|string|null { if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT)) { $host = $host[0]; @@ -972,20 +939,16 @@ class Request /** * Returns the user. - * - * @return string|null */ - public function getUser() + public function getUser(): ?string { return $this->headers->get('PHP_AUTH_USER'); } /** * Returns the password. - * - * @return string|null */ - public function getPassword() + public function getPassword(): ?string { return $this->headers->get('PHP_AUTH_PW'); } @@ -995,7 +958,7 @@ class Request * * @return string|null A user name if any and, optionally, scheme-specific information about how to gain authorization to access the server */ - public function getUserInfo() + public function getUserInfo(): ?string { $userinfo = $this->getUser(); @@ -1011,15 +974,13 @@ class Request * Returns the HTTP host being requested. * * The port name will be appended to the host if it's non-standard. - * - * @return string */ - public function getHttpHost() + public function getHttpHost(): string { $scheme = $this->getScheme(); $port = $this->getPort(); - if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) { + if (('http' === $scheme && 80 == $port) || ('https' === $scheme && 443 == $port)) { return $this->getHost(); } @@ -1031,13 +992,9 @@ class Request * * @return string The raw URI (i.e. not URI decoded) */ - public function getRequestUri() + public function getRequestUri(): string { - if (null === $this->requestUri) { - $this->requestUri = $this->prepareRequestUri(); - } - - return $this->requestUri; + return $this->requestUri ??= $this->prepareRequestUri(); } /** @@ -1045,10 +1002,8 @@ class Request * * If the URL was called with basic authentication, the user * and the password are not added to the generated string. - * - * @return string */ - public function getSchemeAndHttpHost() + public function getSchemeAndHttpHost(): string { return $this->getScheme().'://'.$this->getHttpHost(); } @@ -1056,11 +1011,9 @@ class Request /** * Generates a normalized URI (URL) for the Request. * - * @return string - * * @see getQueryString() */ - public function getUri() + public function getUri(): string { if (null !== $qs = $this->getQueryString()) { $qs = '?'.$qs; @@ -1073,10 +1026,8 @@ class Request * Generates a normalized URI for the given path. * * @param string $path A path to use instead of the current one - * - * @return string */ - public function getUriForPath(string $path) + public function getUriForPath(string $path): string { return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; } @@ -1095,10 +1046,8 @@ class Request * - "/a/b/" -> "../" * - "/a/b/c/other" -> "other" * - "/a/x/y" -> "../../x/y" - * - * @return string */ - public function getRelativeUriForPath(string $path) + public function getRelativeUriForPath(string $path): string { // be sure that we are dealing with an absolute path if (!isset($path[0]) || '/' !== $path[0]) { @@ -1139,10 +1088,8 @@ class Request * * It builds a normalized query string, where keys/value pairs are alphabetized * and have consistent escaping. - * - * @return string|null */ - public function getQueryString() + public function getQueryString(): ?string { $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); @@ -1156,10 +1103,8 @@ class Request * when trusted proxies were set via "setTrustedProxies()". * * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". - * - * @return bool */ - public function isSecure() + public function isSecure(): bool { if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) { return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true); @@ -1178,11 +1123,9 @@ class Request * * The "X-Forwarded-Host" header must contain the client host name. * - * @return string - * * @throws SuspiciousOperationException when the host name is invalid or not trusted */ - public function getHost() + public function getHost(): string { if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { $host = $host[0]; @@ -1236,6 +1179,8 @@ class Request /** * Sets the request method. + * + * @return void */ public function setMethod(string $method) { @@ -1254,11 +1199,9 @@ class Request * * The method is always an uppercased string. * - * @return string - * * @see getRealMethod() */ - public function getMethod() + public function getMethod(): string { if (null !== $this->method) { return $this->method; @@ -1296,21 +1239,17 @@ class Request /** * Gets the "real" request method. * - * @return string - * * @see getMethod() */ - public function getRealMethod() + public function getRealMethod(): string { return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); } /** * Gets the mime type associated with the format. - * - * @return string|null */ - public function getMimeType(string $format) + public function getMimeType(string $format): ?string { if (null === static::$formats) { static::initializeFormats(); @@ -1322,9 +1261,9 @@ class Request /** * Gets the mime types associated with the format. * - * @return array + * @return string[] */ - public static function getMimeTypes(string $format) + public static function getMimeTypes(string $format): array { if (null === static::$formats) { static::initializeFormats(); @@ -1335,10 +1274,8 @@ class Request /** * Gets the format associated with the mime type. - * - * @return string|null */ - public function getFormat(?string $mimeType) + public function getFormat(?string $mimeType): ?string { $canonicalMimeType = null; if ($mimeType && false !== $pos = strpos($mimeType, ';')) { @@ -1364,9 +1301,11 @@ class Request /** * Associates a format with mime types. * - * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + * @param string|string[] $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + * + * @return void */ - public function setFormat(?string $format, $mimeTypes) + public function setFormat(?string $format, string|array $mimeTypes) { if (null === static::$formats) { static::initializeFormats(); @@ -1385,20 +1324,18 @@ class Request * * $default * * @see getPreferredFormat - * - * @return string|null */ - public function getRequestFormat(?string $default = 'html') + public function getRequestFormat(?string $default = 'html'): ?string { - if (null === $this->format) { - $this->format = $this->attributes->get('_format'); - } + $this->format ??= $this->attributes->get('_format'); return $this->format ?? $default; } /** * Sets the request format. + * + * @return void */ public function setRequestFormat(?string $format) { @@ -1406,17 +1343,31 @@ class Request } /** - * Gets the format associated with the request. + * Gets the usual name of the format associated with the request's media type (provided in the Content-Type header). * - * @return string|null + * @deprecated since Symfony 6.2, use getContentTypeFormat() instead */ - public function getContentType() + public function getContentType(): ?string + { + trigger_deprecation('symfony/http-foundation', '6.2', 'The "%s()" method is deprecated, use "getContentTypeFormat()" instead.', __METHOD__); + + return $this->getContentTypeFormat(); + } + + /** + * Gets the usual name of the format associated with the request's media type (provided in the Content-Type header). + * + * @see Request::$formats + */ + public function getContentTypeFormat(): ?string { return $this->getFormat($this->headers->get('CONTENT_TYPE', '')); } /** * Sets the default locale. + * + * @return void */ public function setDefaultLocale(string $locale) { @@ -1429,16 +1380,16 @@ class Request /** * Get the default locale. - * - * @return string */ - public function getDefaultLocale() + public function getDefaultLocale(): string { return $this->defaultLocale; } /** * Sets the locale. + * + * @return void */ public function setLocale(string $locale) { @@ -1447,22 +1398,18 @@ class Request /** * Get the locale. - * - * @return string */ - public function getLocale() + public function getLocale(): string { - return null === $this->locale ? $this->defaultLocale : $this->locale; + return $this->locale ?? $this->defaultLocale; } /** * Checks if the request method is of specified type. * * @param string $method Uppercase request method (GET, POST etc) - * - * @return bool */ - public function isMethod(string $method) + public function isMethod(string $method): bool { return $this->getMethod() === strtoupper($method); } @@ -1471,20 +1418,16 @@ class Request * Checks whether or not the method is safe. * * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 - * - * @return bool */ - public function isMethodSafe() + public function isMethodSafe(): bool { return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); } /** * Checks whether or not the method is idempotent. - * - * @return bool */ - public function isMethodIdempotent() + public function isMethodIdempotent(): bool { return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']); } @@ -1493,10 +1436,8 @@ class Request * Checks whether the method is cacheable or not. * * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 - * - * @return bool */ - public function isMethodCacheable() + public function isMethodCacheable(): bool { return \in_array($this->getMethod(), ['GET', 'HEAD']); } @@ -1509,10 +1450,8 @@ class Request * server might be different. This returns the former (from the "Via" header) * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns * the latter (from the "SERVER_PROTOCOL" server parameter). - * - * @return string|null */ - public function getProtocolVersion() + public function getProtocolVersion(): ?string { if ($this->isFromTrustedProxy()) { preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches); @@ -1531,6 +1470,8 @@ class Request * @param bool $asResource If true, a resource will be returned * * @return string|resource + * + * @psalm-return ($asResource is true ? resource : string) */ public function getContent(bool $asResource = false) { @@ -1571,28 +1512,52 @@ class Request } /** - * Gets the request body decoded as array, typically from a JSON payload. + * Gets the decoded form or json request body. * * @throws JsonException When the body cannot be decoded to an array - * - * @return array */ - public function toArray() + public function getPayload(): InputBag + { + if ($this->request->count()) { + return clone $this->request; + } + + if ('' === $content = $this->getContent()) { + return new InputBag([]); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return new InputBag($content); + } + + /** + * Gets the request body decoded as array, typically from a JSON payload. + * + * @see getPayload() for portability between content types + * + * @throws JsonException When the body cannot be decoded to an array + */ + public function toArray(): array { if ('' === $content = $this->getContent()) { throw new JsonException('Request body is empty.'); } try { - $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); } catch (\JsonException $e) { throw new JsonException('Could not decode request body.', $e->getCode(), $e); } - if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) { - throw new JsonException('Could not decode request body: '.json_last_error_msg(), json_last_error()); - } - if (!\is_array($content)) { throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); } @@ -1602,18 +1567,13 @@ class Request /** * Gets the Etags. - * - * @return array */ - public function getETags() + public function getETags(): array { return preg_split('/\s*,\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY); } - /** - * @return bool - */ - public function isNoCache() + public function isNoCache(): bool { return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); } @@ -1628,7 +1588,7 @@ class Request */ public function getPreferredFormat(?string $default = 'html'): ?string { - if (null !== $this->preferredFormat || null !== $this->preferredFormat = $this->getRequestFormat(null)) { + if ($this->preferredFormat ??= $this->getRequestFormat(null)) { return $this->preferredFormat; } @@ -1645,10 +1605,8 @@ class Request * Returns the preferred language. * * @param string[] $locales An array of ordered available locales - * - * @return string|null */ - public function getPreferredLanguage(array $locales = null) + public function getPreferredLanguage(array $locales = null): ?string { $preferredLanguages = $this->getLanguages(); @@ -1679,9 +1637,9 @@ class Request /** * Gets a list of languages acceptable by the client browser ordered in the user browser preferences. * - * @return array + * @return string[] */ - public function getLanguages() + public function getLanguages(): array { if (null !== $this->languages) { return $this->languages; @@ -1720,43 +1678,31 @@ class Request /** * Gets a list of charsets acceptable by the client browser in preferable order. * - * @return array + * @return string[] */ - public function getCharsets() + public function getCharsets(): array { - if (null !== $this->charsets) { - return $this->charsets; - } - - return $this->charsets = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all())); + return $this->charsets ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all())); } /** * Gets a list of encodings acceptable by the client browser in preferable order. * - * @return array + * @return string[] */ - public function getEncodings() + public function getEncodings(): array { - if (null !== $this->encodings) { - return $this->encodings; - } - - return $this->encodings = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all())); + return $this->encodings ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all())); } /** * Gets a list of content types acceptable by the client browser in preferable order. * - * @return array + * @return string[] */ - public function getAcceptableContentTypes() + public function getAcceptableContentTypes(): array { - if (null !== $this->acceptableContentTypes) { - return $this->acceptableContentTypes; - } - - return $this->acceptableContentTypes = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all())); + return $this->acceptableContentTypes ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all())); } /** @@ -1766,10 +1712,8 @@ class Request * It is known to work with common JavaScript frameworks: * * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript - * - * @return bool */ - public function isXmlHttpRequest() + public function isXmlHttpRequest(): bool { return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); } @@ -1781,7 +1725,7 @@ class Request */ public function preferSafeContent(): bool { - if (null !== $this->isSafeContentPreferred) { + if (isset($this->isSafeContentPreferred)) { return $this->isSafeContentPreferred; } @@ -1801,15 +1745,17 @@ class Request * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/) */ + /** + * @return string + */ protected function prepareRequestUri() { $requestUri = ''; - if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) { + if ($this->isIisRewrite() && '' != $this->server->get('UNENCODED_URL')) { // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) $requestUri = $this->server->get('UNENCODED_URL'); $this->server->remove('UNENCODED_URL'); - $this->server->remove('IIS_WasUrlRewritten'); } elseif ($this->server->has('REQUEST_URI')) { $requestUri = $this->server->get('REQUEST_URI'); @@ -1848,10 +1794,8 @@ class Request /** * Prepares the base URL. - * - * @return string */ - protected function prepareBaseUrl() + protected function prepareBaseUrl(): string { $filename = basename($this->server->get('SCRIPT_FILENAME', '')); @@ -1917,10 +1861,8 @@ class Request /** * Prepares the base path. - * - * @return string */ - protected function prepareBasePath() + protected function prepareBasePath(): string { $baseUrl = $this->getBaseUrl(); if (empty($baseUrl)) { @@ -1943,10 +1885,8 @@ class Request /** * Prepares the path info. - * - * @return string */ - protected function preparePathInfo() + protected function preparePathInfo(): string { if (null === ($requestUri = $this->getRequestUri())) { return '/'; @@ -1975,6 +1915,8 @@ class Request /** * Initializes HTTP request formats. + * + * @return void */ protected static function initializeFormats() { @@ -2002,7 +1944,7 @@ class Request if (class_exists(\Locale::class, false)) { \Locale::setDefault($locale); } - } catch (\Exception $e) { + } catch (\Exception) { } } @@ -2012,7 +1954,13 @@ class Request */ private function getUrlencodedPrefix(string $string, string $prefix): ?string { - if (!str_starts_with(rawurldecode($string), $prefix)) { + if ($this->isIisRewrite()) { + // ISS with UrlRewriteModule might report SCRIPT_NAME/PHP_SELF with wrong case + // see https://github.com/php/php-src/issues/11981 + if (0 !== stripos(rawurldecode($string), $prefix)) { + return null; + } + } elseif (!str_starts_with(rawurldecode($string), $prefix)) { return null; } @@ -2025,7 +1973,7 @@ class Request return null; } - private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self + private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): static { if (self::$requestFactory) { $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); @@ -2045,16 +1993,26 @@ class Request * * This can be useful to determine whether or not to trust the * contents of a proxy-specific header. - * - * @return bool */ - public function isFromTrustedProxy() + public function isFromTrustedProxy(): bool { return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies); } + /** + * This method is rather heavy because it splits and merges headers, and it's called by many other methods such as + * getPort(), isSecure(), getHost(), getClientIps(), getBaseUrl() etc. Thus, we try to cache the results for + * best performance. + */ private function getTrustedValues(int $type, string $ip = null): array { + $cacheKey = $type."\0".((self::$trustedHeaderSet & $type) ? $this->headers->get(self::TRUSTED_HEADERS[$type]) : ''); + $cacheKey .= "\0".$ip."\0".$this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); + + if (isset($this->trustedValuesCache[$cacheKey])) { + return $this->trustedValuesCache[$cacheKey]; + } + $clientValues = []; $forwardedValues = []; @@ -2067,7 +2025,6 @@ class Request if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) { $forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); $parts = HeaderUtils::split($forwarded, ',;='); - $forwardedValues = []; $param = self::FORWARDED_PARAMS[$type]; foreach ($parts as $subParts) { if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) { @@ -2089,15 +2046,15 @@ class Request } if ($forwardedValues === $clientValues || !$clientValues) { - return $forwardedValues; + return $this->trustedValuesCache[$cacheKey] = $forwardedValues; } if (!$forwardedValues) { - return $clientValues; + return $this->trustedValuesCache[$cacheKey] = $clientValues; } if (!$this->isForwardedValid) { - return null !== $ip ? ['0.0.0.0', $ip] : []; + return $this->trustedValuesCache[$cacheKey] = null !== $ip ? ['0.0.0.0', $ip] : []; } $this->isForwardedValid = false; @@ -2136,13 +2093,27 @@ class Request unset($clientIps[$key]); // Fallback to this when the client IP falls into the range of trusted proxies - if (null === $firstTrustedIp) { - $firstTrustedIp = $clientIp; - } + $firstTrustedIp ??= $clientIp; } } // Now the IP chain contains only untrusted proxies and the client IP return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp]; } + + /** + * Is this IIS with UrlRewriteModule? + * + * This method consumes, caches and removed the IIS_WasUrlRewritten env var, + * so we don't inherit it to sub-requests. + */ + private function isIisRewrite(): bool + { + if (1 === $this->server->getInt('IIS_WasUrlRewritten')) { + $this->isIisRewrite = true; + $this->server->remove('IIS_WasUrlRewritten'); + } + + return $this->isIisRewrite; + } } diff --git a/lib/symfony/http-foundation/RequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher.php index f2645f9ae..8c5f1d813 100644 --- a/lib/symfony/http-foundation/RequestMatcher.php +++ b/lib/symfony/http-foundation/RequestMatcher.php @@ -11,54 +11,47 @@ namespace Symfony\Component\HttpFoundation; +trigger_deprecation('symfony/http-foundation', '6.2', 'The "%s" class is deprecated, use "%s" instead.', RequestMatcher::class, ChainRequestMatcher::class); + /** * RequestMatcher compares a pre-defined set of checks against a Request instance. * * @author Fabien Potencier + * + * @deprecated since Symfony 6.2, use ChainRequestMatcher instead */ class RequestMatcher implements RequestMatcherInterface { - /** - * @var string|null - */ - private $path; - - /** - * @var string|null - */ - private $host; - - /** - * @var int|null - */ - private $port; + private ?string $path = null; + private ?string $host = null; + private ?int $port = null; /** * @var string[] */ - private $methods = []; + private array $methods = []; /** * @var string[] */ - private $ips = []; - - /** - * @var array - */ - private $attributes = []; + private array $ips = []; /** * @var string[] */ - private $schemes = []; + private array $attributes = []; + + /** + * @var string[] + */ + private array $schemes = []; /** * @param string|string[]|null $methods * @param string|string[]|null $ips * @param string|string[]|null $schemes */ - public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null) + public function __construct(string $path = null, string $host = null, string|array $methods = null, string|array $ips = null, array $attributes = [], string|array $schemes = null, int $port = null) { $this->matchPath($path); $this->matchHost($host); @@ -76,14 +69,18 @@ class RequestMatcher implements RequestMatcherInterface * Adds a check for the HTTP scheme. * * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + * + * @return void */ - public function matchScheme($scheme) + public function matchScheme(string|array|null $scheme) { $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : []; } /** * Adds a check for the URL host name. + * + * @return void */ public function matchHost(?string $regexp) { @@ -94,6 +91,8 @@ class RequestMatcher implements RequestMatcherInterface * Adds a check for the the URL port. * * @param int|null $port The port number to connect to + * + * @return void */ public function matchPort(?int $port) { @@ -102,6 +101,8 @@ class RequestMatcher implements RequestMatcherInterface /** * Adds a check for the URL path info. + * + * @return void */ public function matchPath(?string $regexp) { @@ -112,6 +113,8 @@ class RequestMatcher implements RequestMatcherInterface * Adds a check for the client IP. * * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + * + * @return void */ public function matchIp(string $ip) { @@ -122,38 +125,39 @@ class RequestMatcher implements RequestMatcherInterface * Adds a check for the client IP. * * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + * + * @return void */ - public function matchIps($ips) + public function matchIps(string|array|null $ips) { $ips = null !== $ips ? (array) $ips : []; - $this->ips = array_reduce($ips, static function (array $ips, string $ip) { - return array_merge($ips, preg_split('/\s*,\s*/', $ip)); - }, []); + $this->ips = array_reduce($ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); } /** * Adds a check for the HTTP method. * * @param string|string[]|null $method An HTTP method or an array of HTTP methods + * + * @return void */ - public function matchMethod($method) + public function matchMethod(string|array|null $method) { $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : []; } /** * Adds a check for request attribute. + * + * @return void */ public function matchAttribute(string $key, string $regexp) { $this->attributes[$key] = $regexp; } - /** - * {@inheritdoc} - */ - public function matches(Request $request) + public function matches(Request $request): bool { if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) { return false; diff --git a/lib/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php new file mode 100644 index 000000000..09d6f49dc --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request attributes matches all regular expressions. + * + * @author Fabien Potencier + */ +class AttributesRequestMatcher implements RequestMatcherInterface +{ + /** + * @param array $regexps + */ + public function __construct(private array $regexps) + { + } + + public function matches(Request $request): bool + { + foreach ($this->regexps as $key => $regexp) { + $attribute = $request->attributes->get($key); + if (!\is_string($attribute)) { + return false; + } + if (!preg_match('{'.$regexp.'}', $attribute)) { + return false; + } + } + + return true; + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php new file mode 100644 index 000000000..935853f14 --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher implements RequestMatcherInterface +{ + public function __construct( + private ExpressionLanguage $language, + private Expression|string $expression, + ) { + } + + public function matches(Request $request): bool + { + return $this->language->evaluate($this->expression, [ + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + ]); + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php new file mode 100644 index 000000000..2836759cd --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request URL host name matches a regular expression. + * + * @author Fabien Potencier + */ +class HostRequestMatcher implements RequestMatcherInterface +{ + public function __construct(private string $regexp) + { + } + + public function matches(Request $request): bool + { + return preg_match('{'.$this->regexp.'}i', $request->getHost()); + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php new file mode 100644 index 000000000..333612e2f --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the client IP of a Request. + * + * @author Fabien Potencier + */ +class IpsRequestMatcher implements RequestMatcherInterface +{ + private array $ips; + + /** + * @param string[]|string $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + * Strings can contain a comma-delimited list of IPs/ranges + */ + public function __construct(array|string $ips) + { + $this->ips = array_reduce((array) $ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); + } + + public function matches(Request $request): bool + { + if (!$this->ips) { + return true; + } + + return IpUtils::checkIp($request->getClientIp() ?? '', $this->ips); + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php new file mode 100644 index 000000000..875f992be --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request content is valid JSON. + * + * @author Fabien Potencier + */ +class IsJsonRequestMatcher implements RequestMatcherInterface +{ + public function matches(Request $request): bool + { + return json_validate($request->getContent()); + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php new file mode 100644 index 000000000..b37f6e3c8 --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the HTTP method of a Request. + * + * @author Fabien Potencier + */ +class MethodRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $methods = []; + + /** + * @param string[]|string $methods An HTTP method or an array of HTTP methods + * Strings can contain a comma-delimited list of methods + */ + public function __construct(array|string $methods) + { + $this->methods = array_reduce(array_map('strtoupper', (array) $methods), static fn (array $methods, string $method) => array_merge($methods, preg_split('/\s*,\s*/', $method)), []); + } + + public function matches(Request $request): bool + { + if (!$this->methods) { + return true; + } + + return \in_array($request->getMethod(), $this->methods, true); + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php new file mode 100644 index 000000000..c7c7a02c1 --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request URL path info matches a regular expression. + * + * @author Fabien Potencier + */ +class PathRequestMatcher implements RequestMatcherInterface +{ + public function __construct(private string $regexp) + { + } + + public function matches(Request $request): bool + { + return preg_match('{'.$this->regexp.'}', rawurldecode($request->getPathInfo())); + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php new file mode 100644 index 000000000..5a01ce959 --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the HTTP port of a Request. + * + * @author Fabien Potencier + */ +class PortRequestMatcher implements RequestMatcherInterface +{ + public function __construct(private int $port) + { + } + + public function matches(Request $request): bool + { + return $request->getPort() === $this->port; + } +} diff --git a/lib/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php new file mode 100644 index 000000000..9c9cd58b9 --- /dev/null +++ b/lib/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the HTTP scheme of a Request. + * + * @author Fabien Potencier + */ +class SchemeRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $schemes; + + /** + * @param string[]|string $schemes A scheme or a list of schemes + * Strings can contain a comma-delimited list of schemes + */ + public function __construct(array|string $schemes) + { + $this->schemes = array_reduce(array_map('strtolower', (array) $schemes), static fn (array $schemes, string $scheme) => array_merge($schemes, preg_split('/\s*,\s*/', $scheme)), []); + } + + public function matches(Request $request): bool + { + if (!$this->schemes) { + return true; + } + + return \in_array($request->getScheme(), $this->schemes, true); + } +} diff --git a/lib/symfony/http-foundation/RequestMatcherInterface.php b/lib/symfony/http-foundation/RequestMatcherInterface.php index c2e147858..6dcc3e0fc 100644 --- a/lib/symfony/http-foundation/RequestMatcherInterface.php +++ b/lib/symfony/http-foundation/RequestMatcherInterface.php @@ -20,8 +20,6 @@ interface RequestMatcherInterface { /** * Decides whether the rule(s) implemented by the strategy matches the supplied request. - * - * @return bool */ - public function matches(Request $request); + public function matches(Request $request): bool; } diff --git a/lib/symfony/http-foundation/RequestStack.php b/lib/symfony/http-foundation/RequestStack.php index 855b51816..5aa8ba793 100644 --- a/lib/symfony/http-foundation/RequestStack.php +++ b/lib/symfony/http-foundation/RequestStack.php @@ -24,13 +24,15 @@ class RequestStack /** * @var Request[] */ - private $requests = []; + private array $requests = []; /** * Pushes a Request on the stack. * * This method should generally not be called directly as the stack * management should be taken care of by the application itself. + * + * @return void */ public function push(Request $request) { @@ -44,10 +46,8 @@ class RequestStack * * This method should generally not be called directly as the stack * management should be taken care of by the application itself. - * - * @return Request|null */ - public function pop() + public function pop(): ?Request { if (!$this->requests) { return null; @@ -56,10 +56,7 @@ class RequestStack return array_pop($this->requests); } - /** - * @return Request|null - */ - public function getCurrentRequest() + public function getCurrentRequest(): ?Request { return end($this->requests) ?: null; } @@ -80,20 +77,6 @@ class RequestStack return $this->requests[0]; } - /** - * Gets the master request. - * - * @return Request|null - * - * @deprecated since symfony/http-foundation 5.3, use getMainRequest() instead - */ - public function getMasterRequest() - { - trigger_deprecation('symfony/http-foundation', '5.3', '"%s()" is deprecated, use "getMainRequest()" instead.', __METHOD__); - - return $this->getMainRequest(); - } - /** * Returns the parent request of the current. * @@ -102,10 +85,8 @@ class RequestStack * like ESI support. * * If current Request is the main request, it returns null. - * - * @return Request|null */ - public function getParentRequest() + public function getParentRequest(): ?Request { $pos = \count($this->requests) - 2; diff --git a/lib/symfony/http-foundation/Response.php b/lib/symfony/http-foundation/Response.php index d5c8cb45c..ef6ece002 100644 --- a/lib/symfony/http-foundation/Response.php +++ b/lib/symfony/http-foundation/Response.php @@ -98,6 +98,8 @@ class Response 'proxy_revalidate' => false, 'max_age' => true, 's_maxage' => true, + 'stale_if_error' => true, // RFC5861 + 'stale_while_revalidate' => true, // RFC5861 'immutable' => false, 'last_modified' => true, 'etag' => true, @@ -210,6 +212,13 @@ class Response ]; /** + * Tracks headers already sent in informational responses. + */ + private array $sentHeaders; + + /** + * @param int $status The HTTP status code (200 "OK" by default) + * * @throws \InvalidArgumentException When the HTTP status code is not valid */ public function __construct(?string $content = '', int $status = 200, array $headers = []) @@ -220,25 +229,6 @@ class Response $this->setProtocolVersion('1.0'); } - /** - * Factory method for chainability. - * - * Example: - * - * return Response::create($body, 200) - * ->setSharedMaxAge(300); - * - * @return static - * - * @deprecated since Symfony 5.1, use __construct() instead. - */ - public static function create(?string $content = '', int $status = 200, array $headers = []) - { - trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); - - return new static($content, $status, $headers); - } - /** * Returns the Response as an HTTP string. * @@ -246,11 +236,9 @@ class Response * one that will be sent to the client only if the prepare() method * has been called before. * - * @return string - * * @see prepare() */ - public function __toString() + public function __toString(): string { return sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". @@ -275,7 +263,7 @@ class Response * * @return $this */ - public function prepare(Request $request) + public function prepare(Request $request): static { $headers = $this->headers; @@ -298,7 +286,7 @@ class Response $charset = $this->charset ?: 'UTF-8'; if (!$headers->has('Content-Type')) { $headers->set('Content-Type', 'text/html; charset='.$charset); - } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + } elseif (0 === stripos($headers->get('Content-Type') ?? '', 'text/') && false === stripos($headers->get('Content-Type') ?? '', 'charset')) { // add the charset $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); } @@ -343,21 +331,54 @@ class Response /** * Sends HTTP headers. * + * @param positive-int|null $statusCode The status code to use, override the statusCode property if set and not null + * * @return $this */ - public function sendHeaders() + public function sendHeaders(/* int $statusCode = null */): static { // headers have already been sent by the developer if (headers_sent()) { return $this; } + $statusCode = \func_num_args() > 0 ? func_get_arg(0) : null; + $informationalResponse = $statusCode >= 100 && $statusCode < 200; + if ($informationalResponse && !\function_exists('headers_send')) { + // skip informational responses if not supported by the SAPI + return $this; + } + // headers foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { - $replace = 0 === strcasecmp($name, 'Content-Type'); - foreach ($values as $value) { + $newValues = $values; + $replace = false; + + // As recommended by RFC 8297, PHP automatically copies headers from previous 103 responses, we need to deal with that if headers changed + if (103 === $statusCode) { + $previousValues = $this->sentHeaders[$name] ?? null; + if ($previousValues === $values) { + // Header already sent in a previous response, it will be automatically copied in this response by PHP + continue; + } + + $replace = 0 === strcasecmp($name, 'Content-Type'); + + if (null !== $previousValues && array_diff($previousValues, $values)) { + header_remove($name); + $previousValues = null; + } + + $newValues = null === $previousValues ? $values : array_diff($values, $previousValues); + } + + foreach ($newValues as $value) { header($name.': '.$value, $replace, $this->statusCode); } + + if ($informationalResponse) { + $this->sentHeaders[$name] = $values; + } } // cookies @@ -365,8 +386,16 @@ class Response header('Set-Cookie: '.$cookie, false, $this->statusCode); } + if ($informationalResponse) { + headers_send($statusCode); + + return $this; + } + + $statusCode ??= $this->statusCode; + // status - header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + header(sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode); return $this; } @@ -376,7 +405,7 @@ class Response * * @return $this */ - public function sendContent() + public function sendContent(): static { echo $this->content; @@ -386,18 +415,25 @@ class Response /** * Sends HTTP headers and content. * + * @param bool $flush Whether output buffers should be flushed + * * @return $this */ - public function send() + public function send(/* bool $flush = true */): static { $this->sendHeaders(); $this->sendContent(); + $flush = 1 <= \func_num_args() ? func_get_arg(0) : true; + if (!$flush) { + return $this; + } + if (\function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } elseif (\function_exists('litespeed_finish_request')) { litespeed_finish_request(); - } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { static::closeOutputBuffers(0, true); flush(); } @@ -410,7 +446,7 @@ class Response * * @return $this */ - public function setContent(?string $content) + public function setContent(?string $content): static { $this->content = $content ?? ''; @@ -419,10 +455,8 @@ class Response /** * Gets the current response content. - * - * @return string|false */ - public function getContent() + public function getContent(): string|false { return $this->content; } @@ -434,7 +468,7 @@ class Response * * @final */ - public function setProtocolVersion(string $version): object + public function setProtocolVersion(string $version): static { $this->version = $version; @@ -463,7 +497,7 @@ class Response * * @final */ - public function setStatusCode(int $code, string $text = null): object + public function setStatusCode(int $code, string $text = null): static { $this->statusCode = $code; if ($this->isInvalid()) { @@ -476,12 +510,6 @@ class Response return $this; } - if (false === $text) { - $this->statusText = ''; - - return $this; - } - $this->statusText = $text; return $this; @@ -504,7 +532,7 @@ class Response * * @final */ - public function setCharset(string $charset): object + public function setCharset(string $charset): static { $this->charset = $charset; @@ -585,7 +613,7 @@ class Response * * @final */ - public function setPrivate(): object + public function setPrivate(): static { $this->headers->removeCacheControlDirective('public'); $this->headers->addCacheControlDirective('private'); @@ -602,7 +630,7 @@ class Response * * @final */ - public function setPublic(): object + public function setPublic(): static { $this->headers->addCacheControlDirective('public'); $this->headers->removeCacheControlDirective('private'); @@ -617,7 +645,7 @@ class Response * * @final */ - public function setImmutable(bool $immutable = true): object + public function setImmutable(bool $immutable = true): static { if ($immutable) { $this->headers->addCacheControlDirective('immutable'); @@ -660,7 +688,7 @@ class Response * * @final */ - public function getDate(): ?\DateTimeInterface + public function getDate(): ?\DateTimeImmutable { return $this->headers->getDate('Date'); } @@ -672,12 +700,9 @@ class Response * * @final */ - public function setDate(\DateTimeInterface $date): object + public function setDate(\DateTimeInterface $date): static { - if ($date instanceof \DateTime) { - $date = \DateTimeImmutable::createFromMutable($date); - } - + $date = \DateTimeImmutable::createFromInterface($date); $date = $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); @@ -703,7 +728,7 @@ class Response * * @return $this */ - public function expire() + public function expire(): static { if ($this->isFresh()) { $this->headers->set('Age', $this->getMaxAge()); @@ -718,13 +743,13 @@ class Response * * @final */ - public function getExpires(): ?\DateTimeInterface + public function getExpires(): ?\DateTimeImmutable { try { return $this->headers->getDate('Expires'); - } catch (\RuntimeException $e) { + } catch (\RuntimeException) { // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past - return \DateTime::createFromFormat('U', time() - 172800); + return \DateTimeImmutable::createFromFormat('U', time() - 172800); } } @@ -737,18 +762,18 @@ class Response * * @final */ - public function setExpires(\DateTimeInterface $date = null): object + public function setExpires(\DateTimeInterface $date = null): static { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if (null === $date) { $this->headers->remove('Expires'); return $this; } - if ($date instanceof \DateTime) { - $date = \DateTimeImmutable::createFromMutable($date); - } - + $date = \DateTimeImmutable::createFromInterface($date); $date = $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); @@ -774,8 +799,10 @@ class Response return (int) $this->headers->getCacheControlDirective('max-age'); } - if (null !== $this->getExpires()) { - return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U'); + if (null !== $expires = $this->getExpires()) { + $maxAge = (int) $expires->format('U') - (int) $this->getDate()->format('U'); + + return max($maxAge, 0); } return null; @@ -790,13 +817,45 @@ class Response * * @final */ - public function setMaxAge(int $value): object + public function setMaxAge(int $value): static { $this->headers->addCacheControlDirective('max-age', $value); return $this; } + /** + * Sets the number of seconds after which the response should no longer be returned by shared caches when backend is down. + * + * This method sets the Cache-Control stale-if-error directive. + * + * @return $this + * + * @final + */ + public function setStaleIfError(int $value): static + { + $this->headers->addCacheControlDirective('stale-if-error', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer return stale content by shared caches. + * + * This method sets the Cache-Control stale-while-revalidate directive. + * + * @return $this + * + * @final + */ + public function setStaleWhileRevalidate(int $value): static + { + $this->headers->addCacheControlDirective('stale-while-revalidate', $value); + + return $this; + } + /** * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. * @@ -806,7 +865,7 @@ class Response * * @final */ - public function setSharedMaxAge(int $value): object + public function setSharedMaxAge(int $value): static { $this->setPublic(); $this->headers->addCacheControlDirective('s-maxage', $value); @@ -819,7 +878,7 @@ class Response * * It returns null when no freshness information is present in the response. * - * When the responses TTL is <= 0, the response may not be served from cache without first + * When the response's TTL is 0, the response may not be served from cache without first * revalidating with the origin. * * @final @@ -828,7 +887,7 @@ class Response { $maxAge = $this->getMaxAge(); - return null !== $maxAge ? $maxAge - $this->getAge() : null; + return null !== $maxAge ? max($maxAge - $this->getAge(), 0) : null; } /** @@ -840,7 +899,7 @@ class Response * * @final */ - public function setTtl(int $seconds): object + public function setTtl(int $seconds): static { $this->setSharedMaxAge($this->getAge() + $seconds); @@ -856,7 +915,7 @@ class Response * * @final */ - public function setClientTtl(int $seconds): object + public function setClientTtl(int $seconds): static { $this->setMaxAge($this->getAge() + $seconds); @@ -870,7 +929,7 @@ class Response * * @final */ - public function getLastModified(): ?\DateTimeInterface + public function getLastModified(): ?\DateTimeImmutable { return $this->headers->getDate('Last-Modified'); } @@ -884,18 +943,18 @@ class Response * * @final */ - public function setLastModified(\DateTimeInterface $date = null): object + public function setLastModified(\DateTimeInterface $date = null): static { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if (null === $date) { $this->headers->remove('Last-Modified'); return $this; } - if ($date instanceof \DateTime) { - $date = \DateTimeImmutable::createFromMutable($date); - } - + $date = \DateTimeImmutable::createFromInterface($date); $date = $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); @@ -922,8 +981,11 @@ class Response * * @final */ - public function setEtag(string $etag = null, bool $weak = false): object + public function setEtag(string $etag = null, bool $weak = false): static { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if (null === $etag) { $this->headers->remove('Etag'); } else { @@ -948,7 +1010,7 @@ class Response * * @final */ - public function setCache(array $options): object + public function setCache(array $options): static { if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) { throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff))); @@ -970,6 +1032,14 @@ class Response $this->setSharedMaxAge($options['s_maxage']); } + if (isset($options['stale_while_revalidate'])) { + $this->setStaleWhileRevalidate($options['stale_while_revalidate']); + } + + if (isset($options['stale_if_error'])) { + $this->setStaleIfError($options['stale_if_error']); + } + foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) { if (!$hasValue && isset($options[$directive])) { if ($options[$directive]) { @@ -1011,7 +1081,7 @@ class Response * * @final */ - public function setNotModified(): object + public function setNotModified(): static { $this->setStatusCode(304); $this->setContent(null); @@ -1056,14 +1126,13 @@ class Response /** * Sets the Vary header. * - * @param string|array $headers - * @param bool $replace Whether to replace the actual value or not (true by default) + * @param bool $replace Whether to replace the actual value or not (true by default) * * @return $this * * @final */ - public function setVary($headers, bool $replace = true): object + public function setVary(string|array $headers, bool $replace = true): static { $this->headers->set('Vary', $headers, $replace); diff --git a/lib/symfony/http-foundation/ResponseHeaderBag.php b/lib/symfony/http-foundation/ResponseHeaderBag.php index 1df13fa21..10450ca5e 100644 --- a/lib/symfony/http-foundation/ResponseHeaderBag.php +++ b/lib/symfony/http-foundation/ResponseHeaderBag.php @@ -44,10 +44,8 @@ class ResponseHeaderBag extends HeaderBag /** * Returns the headers, with original capitalizations. - * - * @return array */ - public function allPreserveCase() + public function allPreserveCase(): array { $headers = []; foreach ($this->all() as $name => $value) { @@ -57,6 +55,9 @@ class ResponseHeaderBag extends HeaderBag return $headers; } + /** + * @return array + */ public function allPreserveCaseWithoutCookies() { $headers = $this->allPreserveCase(); @@ -68,7 +69,7 @@ class ResponseHeaderBag extends HeaderBag } /** - * {@inheritdoc} + * @return void */ public function replace(array $headers = []) { @@ -85,10 +86,7 @@ class ResponseHeaderBag extends HeaderBag } } - /** - * {@inheritdoc} - */ - public function all(string $key = null) + public function all(string $key = null): array { $headers = parent::all(); @@ -106,9 +104,9 @@ class ResponseHeaderBag extends HeaderBag } /** - * {@inheritdoc} + * @return void */ - public function set(string $key, $values, bool $replace = true) + public function set(string $key, string|array|null $values, bool $replace = true) { $uniqueKey = strtr($key, self::UPPER, self::LOWER); @@ -137,7 +135,7 @@ class ResponseHeaderBag extends HeaderBag } /** - * {@inheritdoc} + * @return void */ public function remove(string $key) { @@ -161,22 +159,19 @@ class ResponseHeaderBag extends HeaderBag } } - /** - * {@inheritdoc} - */ - public function hasCacheControlDirective(string $key) + public function hasCacheControlDirective(string $key): bool { return \array_key_exists($key, $this->computedCacheControl); } - /** - * {@inheritdoc} - */ - public function getCacheControlDirective(string $key) + public function getCacheControlDirective(string $key): bool|string|null { return $this->computedCacheControl[$key] ?? null; } + /** + * @return void + */ public function setCookie(Cookie $cookie) { $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; @@ -185,12 +180,12 @@ class ResponseHeaderBag extends HeaderBag /** * Removes a cookie from the array, but does not unset it in the browser. + * + * @return void */ public function removeCookie(string $name, ?string $path = '/', string $domain = null) { - if (null === $path) { - $path = '/'; - } + $path ??= '/'; unset($this->cookies[$domain][$path][$name]); @@ -214,7 +209,7 @@ class ResponseHeaderBag extends HeaderBag * * @throws \InvalidArgumentException When the $format is invalid */ - public function getCookies(string $format = self::COOKIES_FLAT) + public function getCookies(string $format = self::COOKIES_FLAT): array { if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); @@ -238,6 +233,8 @@ class ResponseHeaderBag extends HeaderBag /** * Clears a cookie in the browser. + * + * @return void */ public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null) { @@ -246,6 +243,8 @@ class ResponseHeaderBag extends HeaderBag /** * @see HeaderUtils::makeDisposition() + * + * @return string */ public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') { @@ -257,10 +256,8 @@ class ResponseHeaderBag extends HeaderBag * * This considers several other headers and calculates or modifies the * cache-control header to a sensible, conservative value. - * - * @return string */ - protected function computeCacheControlValue() + protected function computeCacheControlValue(): string { if (!$this->cacheControl) { if ($this->has('Last-Modified') || $this->has('Expires')) { diff --git a/lib/symfony/http-foundation/ServerBag.php b/lib/symfony/http-foundation/ServerBag.php index 25688d523..3e912cb80 100644 --- a/lib/symfony/http-foundation/ServerBag.php +++ b/lib/symfony/http-foundation/ServerBag.php @@ -22,10 +22,8 @@ class ServerBag extends ParameterBag { /** * Gets the HTTP headers. - * - * @return array */ - public function getHeaders() + public function getHeaders(): array { $headers = []; foreach ($this->parameters as $key => $value) { @@ -51,7 +49,7 @@ class ServerBag extends ParameterBag * RewriteCond %{HTTP:Authorization} .+ * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] * RewriteCond %{REQUEST_FILENAME} !-f - * RewriteRule ^(.*)$ app.php [QSA,L] + * RewriteRule ^(.*)$ index.php [QSA,L] */ $authorizationHeader = null; diff --git a/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php b/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php index f4f051c7a..ad5a6590a 100644 --- a/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php +++ b/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -18,8 +18,8 @@ namespace Symfony\Component\HttpFoundation\Session\Attribute; */ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable { - private $name = 'attributes'; - private $storageKey; + private string $name = 'attributes'; + private string $storageKey; protected $attributes = []; @@ -31,69 +31,57 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta $this->storageKey = $storageKey; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->name; } + /** + * @return void + */ public function setName(string $name) { $this->name = $name; } /** - * {@inheritdoc} + * @return void */ public function initialize(array &$attributes) { $this->attributes = &$attributes; } - /** - * {@inheritdoc} - */ - public function getStorageKey() + public function getStorageKey(): string { return $this->storageKey; } - /** - * {@inheritdoc} - */ - public function has(string $name) + public function has(string $name): bool { return \array_key_exists($name, $this->attributes); } - /** - * {@inheritdoc} - */ - public function get(string $name, $default = null) + public function get(string $name, mixed $default = null): mixed { return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; } /** - * {@inheritdoc} + * @return void */ - public function set(string $name, $value) + public function set(string $name, mixed $value) { $this->attributes[$name] = $value; } - /** - * {@inheritdoc} - */ - public function all() + public function all(): array { return $this->attributes; } /** - * {@inheritdoc} + * @return void */ public function replace(array $attributes) { @@ -103,10 +91,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta } } - /** - * {@inheritdoc} - */ - public function remove(string $name) + public function remove(string $name): mixed { $retval = null; if (\array_key_exists($name, $this->attributes)) { @@ -117,10 +102,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta return $retval; } - /** - * {@inheritdoc} - */ - public function clear() + public function clear(): mixed { $return = $this->attributes; $this->attributes = []; @@ -133,19 +115,15 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta * * @return \ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->attributes); } /** * Returns the number of attributes. - * - * @return int */ - #[\ReturnTypeWillChange] - public function count() + public function count(): int { return \count($this->attributes); } diff --git a/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php index cb5069681..e8cd0b5a4 100644 --- a/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php +++ b/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -22,34 +22,31 @@ interface AttributeBagInterface extends SessionBagInterface { /** * Checks if an attribute is defined. - * - * @return bool */ - public function has(string $name); + public function has(string $name): bool; /** * Returns an attribute. - * - * @param mixed $default The default value if not found - * - * @return mixed */ - public function get(string $name, $default = null); + public function get(string $name, mixed $default = null): mixed; /** * Sets an attribute. * - * @param mixed $value + * @return void */ - public function set(string $name, $value); + public function set(string $name, mixed $value); /** * Returns attributes. * * @return array */ - public function all(); + public function all(): array; + /** + * @return void + */ public function replace(array $attributes); /** @@ -57,5 +54,5 @@ interface AttributeBagInterface extends SessionBagInterface * * @return mixed The removed value or null when it does not exist */ - public function remove(string $name); + public function remove(string $name): mixed; } diff --git a/lib/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/lib/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php deleted file mode 100644 index 864b35fb7..000000000 --- a/lib/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php +++ /dev/null @@ -1,161 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\Session\Attribute; - -trigger_deprecation('symfony/http-foundation', '5.3', 'The "%s" class is deprecated.', NamespacedAttributeBag::class); - -/** - * This class provides structured storage of session attributes using - * a name spacing character in the key. - * - * @author Drak - * - * @deprecated since Symfony 5.3 - */ -class NamespacedAttributeBag extends AttributeBag -{ - private $namespaceCharacter; - - /** - * @param string $storageKey Session storage key - * @param string $namespaceCharacter Namespace character to use in keys - */ - public function __construct(string $storageKey = '_sf2_attributes', string $namespaceCharacter = '/') - { - $this->namespaceCharacter = $namespaceCharacter; - parent::__construct($storageKey); - } - - /** - * {@inheritdoc} - */ - public function has(string $name) - { - // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is - $attributes = $this->resolveAttributePath($name); - $name = $this->resolveKey($name); - - if (null === $attributes) { - return false; - } - - return \array_key_exists($name, $attributes); - } - - /** - * {@inheritdoc} - */ - public function get(string $name, $default = null) - { - // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is - $attributes = $this->resolveAttributePath($name); - $name = $this->resolveKey($name); - - if (null === $attributes) { - return $default; - } - - return \array_key_exists($name, $attributes) ? $attributes[$name] : $default; - } - - /** - * {@inheritdoc} - */ - public function set(string $name, $value) - { - $attributes = &$this->resolveAttributePath($name, true); - $name = $this->resolveKey($name); - $attributes[$name] = $value; - } - - /** - * {@inheritdoc} - */ - public function remove(string $name) - { - $retval = null; - $attributes = &$this->resolveAttributePath($name); - $name = $this->resolveKey($name); - if (null !== $attributes && \array_key_exists($name, $attributes)) { - $retval = $attributes[$name]; - unset($attributes[$name]); - } - - return $retval; - } - - /** - * Resolves a path in attributes property and returns it as a reference. - * - * This method allows structured namespacing of session attributes. - * - * @param string $name Key name - * @param bool $writeContext Write context, default false - * - * @return array|null - */ - protected function &resolveAttributePath(string $name, bool $writeContext = false) - { - $array = &$this->attributes; - $name = (str_starts_with($name, $this->namespaceCharacter)) ? substr($name, 1) : $name; - - // Check if there is anything to do, else return - if (!$name) { - return $array; - } - - $parts = explode($this->namespaceCharacter, $name); - if (\count($parts) < 2) { - if (!$writeContext) { - return $array; - } - - $array[$parts[0]] = []; - - return $array; - } - - unset($parts[\count($parts) - 1]); - - foreach ($parts as $part) { - if (null !== $array && !\array_key_exists($part, $array)) { - if (!$writeContext) { - $null = null; - - return $null; - } - - $array[$part] = []; - } - - $array = &$array[$part]; - } - - return $array; - } - - /** - * Resolves the key from the name. - * - * This is the last part in a dot separated string. - * - * @return string - */ - protected function resolveKey(string $name) - { - if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { - $name = substr($name, $pos + 1); - } - - return $name; - } -} diff --git a/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php index 8aab3a122..80bbeda0f 100644 --- a/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php +++ b/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -18,9 +18,9 @@ namespace Symfony\Component\HttpFoundation\Session\Flash; */ class AutoExpireFlashBag implements FlashBagInterface { - private $name = 'flashes'; - private $flashes = ['display' => [], 'new' => []]; - private $storageKey; + private string $name = 'flashes'; + private array $flashes = ['display' => [], 'new' => []]; + private string $storageKey; /** * @param string $storageKey The key used to store flashes in the session @@ -30,21 +30,21 @@ class AutoExpireFlashBag implements FlashBagInterface $this->storageKey = $storageKey; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->name; } + /** + * @return void + */ public function setName(string $name) { $this->name = $name; } /** - * {@inheritdoc} + * @return void */ public function initialize(array &$flashes) { @@ -58,33 +58,24 @@ class AutoExpireFlashBag implements FlashBagInterface } /** - * {@inheritdoc} + * @return void */ - public function add(string $type, $message) + public function add(string $type, mixed $message) { $this->flashes['new'][$type][] = $message; } - /** - * {@inheritdoc} - */ - public function peek(string $type, array $default = []) + public function peek(string $type, array $default = []): array { return $this->has($type) ? $this->flashes['display'][$type] : $default; } - /** - * {@inheritdoc} - */ - public function peekAll() + public function peekAll(): array { return \array_key_exists('display', $this->flashes) ? $this->flashes['display'] : []; } - /** - * {@inheritdoc} - */ - public function get(string $type, array $default = []) + public function get(string $type, array $default = []): array { $return = $default; @@ -100,10 +91,7 @@ class AutoExpireFlashBag implements FlashBagInterface return $return; } - /** - * {@inheritdoc} - */ - public function all() + public function all(): array { $return = $this->flashes['display']; $this->flashes['display'] = []; @@ -112,7 +100,7 @@ class AutoExpireFlashBag implements FlashBagInterface } /** - * {@inheritdoc} + * @return void */ public function setAll(array $messages) { @@ -120,41 +108,29 @@ class AutoExpireFlashBag implements FlashBagInterface } /** - * {@inheritdoc} + * @return void */ - public function set(string $type, $messages) + public function set(string $type, string|array $messages) { $this->flashes['new'][$type] = (array) $messages; } - /** - * {@inheritdoc} - */ - public function has(string $type) + public function has(string $type): bool { return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; } - /** - * {@inheritdoc} - */ - public function keys() + public function keys(): array { return array_keys($this->flashes['display']); } - /** - * {@inheritdoc} - */ - public function getStorageKey() + public function getStorageKey(): string { return $this->storageKey; } - /** - * {@inheritdoc} - */ - public function clear() + public function clear(): mixed { return $this->all(); } diff --git a/lib/symfony/http-foundation/Session/Flash/FlashBag.php b/lib/symfony/http-foundation/Session/Flash/FlashBag.php index 88df7508a..659d59d18 100644 --- a/lib/symfony/http-foundation/Session/Flash/FlashBag.php +++ b/lib/symfony/http-foundation/Session/Flash/FlashBag.php @@ -18,9 +18,9 @@ namespace Symfony\Component\HttpFoundation\Session\Flash; */ class FlashBag implements FlashBagInterface { - private $name = 'flashes'; - private $flashes = []; - private $storageKey; + private string $name = 'flashes'; + private array $flashes = []; + private string $storageKey; /** * @param string $storageKey The key used to store flashes in the session @@ -30,21 +30,21 @@ class FlashBag implements FlashBagInterface $this->storageKey = $storageKey; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->name; } + /** + * @return void + */ public function setName(string $name) { $this->name = $name; } /** - * {@inheritdoc} + * @return void */ public function initialize(array &$flashes) { @@ -52,33 +52,24 @@ class FlashBag implements FlashBagInterface } /** - * {@inheritdoc} + * @return void */ - public function add(string $type, $message) + public function add(string $type, mixed $message) { $this->flashes[$type][] = $message; } - /** - * {@inheritdoc} - */ - public function peek(string $type, array $default = []) + public function peek(string $type, array $default = []): array { return $this->has($type) ? $this->flashes[$type] : $default; } - /** - * {@inheritdoc} - */ - public function peekAll() + public function peekAll(): array { return $this->flashes; } - /** - * {@inheritdoc} - */ - public function get(string $type, array $default = []) + public function get(string $type, array $default = []): array { if (!$this->has($type)) { return $default; @@ -91,10 +82,7 @@ class FlashBag implements FlashBagInterface return $return; } - /** - * {@inheritdoc} - */ - public function all() + public function all(): array { $return = $this->peekAll(); $this->flashes = []; @@ -103,49 +91,37 @@ class FlashBag implements FlashBagInterface } /** - * {@inheritdoc} + * @return void */ - public function set(string $type, $messages) + public function set(string $type, string|array $messages) { $this->flashes[$type] = (array) $messages; } /** - * {@inheritdoc} + * @return void */ public function setAll(array $messages) { $this->flashes = $messages; } - /** - * {@inheritdoc} - */ - public function has(string $type) + public function has(string $type): bool { return \array_key_exists($type, $this->flashes) && $this->flashes[$type]; } - /** - * {@inheritdoc} - */ - public function keys() + public function keys(): array { return array_keys($this->flashes); } - /** - * {@inheritdoc} - */ - public function getStorageKey() + public function getStorageKey(): string { return $this->storageKey; } - /** - * {@inheritdoc} - */ - public function clear() + public function clear(): mixed { return $this->all(); } diff --git a/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php index 8713e71d0..bbcf7f8b7 100644 --- a/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php +++ b/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -23,66 +23,56 @@ interface FlashBagInterface extends SessionBagInterface /** * Adds a flash message for the given type. * - * @param mixed $message + * @return void */ - public function add(string $type, $message); + public function add(string $type, mixed $message); /** * Registers one or more messages for a given type. * - * @param string|array $messages + * @return void */ - public function set(string $type, $messages); + public function set(string $type, string|array $messages); /** * Gets flash messages for a given type. * * @param string $type Message category type * @param array $default Default value if $type does not exist - * - * @return array */ - public function peek(string $type, array $default = []); + public function peek(string $type, array $default = []): array; /** * Gets all flash messages. - * - * @return array */ - public function peekAll(); + public function peekAll(): array; /** * Gets and clears flash from the stack. * * @param array $default Default value if $type does not exist - * - * @return array */ - public function get(string $type, array $default = []); + public function get(string $type, array $default = []): array; /** * Gets and clears flashes from the stack. - * - * @return array */ - public function all(); + public function all(): array; /** * Sets all flash messages. + * + * @return void */ public function setAll(array $messages); /** * Has flash messages for a given type? - * - * @return bool */ - public function has(string $type); + public function has(string $type): bool; /** * Returns a list of all defined types. - * - * @return array */ - public function keys(); + public function keys(): array; } diff --git a/lib/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php b/lib/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php new file mode 100644 index 000000000..90151d38d --- /dev/null +++ b/lib/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; + +/** + * Interface for session with a flashbag. + */ +interface FlashBagAwareSessionInterface extends SessionInterface +{ + public function getFlashBag(): FlashBagInterface; +} diff --git a/lib/symfony/http-foundation/Session/Session.php b/lib/symfony/http-foundation/Session/Session.php index 022e3986f..b45be2f8c 100644 --- a/lib/symfony/http-foundation/Session/Session.php +++ b/lib/symfony/http-foundation/Session/Session.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; @@ -29,98 +30,80 @@ class_exists(SessionBagProxy::class); * * @implements \IteratorAggregate */ -class Session implements SessionInterface, \IteratorAggregate, \Countable +class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Countable { protected $storage; - private $flashName; - private $attributeName; - private $data = []; - private $usageIndex = 0; - private $usageReporter; + private string $flashName; + private string $attributeName; + private array $data = []; + private int $usageIndex = 0; + private ?\Closure $usageReporter; public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null) { $this->storage = $storage ?? new NativeSessionStorage(); - $this->usageReporter = $usageReporter; + $this->usageReporter = null === $usageReporter ? null : $usageReporter(...); - $attributes = $attributes ?? new AttributeBag(); + $attributes ??= new AttributeBag(); $this->attributeName = $attributes->getName(); $this->registerBag($attributes); - $flashes = $flashes ?? new FlashBag(); + $flashes ??= new FlashBag(); $this->flashName = $flashes->getName(); $this->registerBag($flashes); } - /** - * {@inheritdoc} - */ - public function start() + public function start(): bool { return $this->storage->start(); } - /** - * {@inheritdoc} - */ - public function has(string $name) + public function has(string $name): bool { return $this->getAttributeBag()->has($name); } - /** - * {@inheritdoc} - */ - public function get(string $name, $default = null) + public function get(string $name, mixed $default = null): mixed { return $this->getAttributeBag()->get($name, $default); } /** - * {@inheritdoc} + * @return void */ - public function set(string $name, $value) + public function set(string $name, mixed $value) { $this->getAttributeBag()->set($name, $value); } - /** - * {@inheritdoc} - */ - public function all() + public function all(): array { return $this->getAttributeBag()->all(); } /** - * {@inheritdoc} + * @return void */ public function replace(array $attributes) { $this->getAttributeBag()->replace($attributes); } - /** - * {@inheritdoc} - */ - public function remove(string $name) + public function remove(string $name): mixed { return $this->getAttributeBag()->remove($name); } /** - * {@inheritdoc} + * @return void */ public function clear() { $this->getAttributeBag()->clear(); } - /** - * {@inheritdoc} - */ - public function isStarted() + public function isStarted(): bool { return $this->storage->isStarted(); } @@ -130,19 +113,15 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable * * @return \ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->getAttributeBag()->all()); } /** * Returns the number of attributes. - * - * @return int */ - #[\ReturnTypeWillChange] - public function count() + public function count(): int { return \count($this->getAttributeBag()->all()); } @@ -172,42 +151,33 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable return true; } - /** - * {@inheritdoc} - */ - public function invalidate(int $lifetime = null) + public function invalidate(int $lifetime = null): bool { $this->storage->clear(); return $this->migrate(true, $lifetime); } - /** - * {@inheritdoc} - */ - public function migrate(bool $destroy = false, int $lifetime = null) + public function migrate(bool $destroy = false, int $lifetime = null): bool { return $this->storage->regenerate($destroy, $lifetime); } /** - * {@inheritdoc} + * @return void */ public function save() { $this->storage->save(); } - /** - * {@inheritdoc} - */ - public function getId() + public function getId(): string { return $this->storage->getId(); } /** - * {@inheritdoc} + * @return void */ public function setId(string $id) { @@ -216,26 +186,20 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable } } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->storage->getName(); } /** - * {@inheritdoc} + * @return void */ public function setName(string $name) { $this->storage->setName($name); } - /** - * {@inheritdoc} - */ - public function getMetadataBag() + public function getMetadataBag(): MetadataBag { ++$this->usageIndex; if ($this->usageReporter && 0 <= $this->usageIndex) { @@ -246,17 +210,14 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable } /** - * {@inheritdoc} + * @return void */ public function registerBag(SessionBagInterface $bag) { $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); } - /** - * {@inheritdoc} - */ - public function getBag(string $name) + public function getBag(string $name): SessionBagInterface { $bag = $this->storage->getBag($name); @@ -265,10 +226,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * Gets the flashbag interface. - * - * @return FlashBagInterface */ - public function getFlashBag() + public function getFlashBag(): FlashBagInterface { return $this->getBag($this->flashName); } diff --git a/lib/symfony/http-foundation/Session/SessionBagInterface.php b/lib/symfony/http-foundation/Session/SessionBagInterface.php index 8e37d06d6..e1c250554 100644 --- a/lib/symfony/http-foundation/Session/SessionBagInterface.php +++ b/lib/symfony/http-foundation/Session/SessionBagInterface.php @@ -20,27 +20,25 @@ interface SessionBagInterface { /** * Gets this bag's name. - * - * @return string */ - public function getName(); + public function getName(): string; /** * Initializes the Bag. + * + * @return void */ public function initialize(array &$array); /** * Gets the storage key for this bag. - * - * @return string */ - public function getStorageKey(); + public function getStorageKey(): string; /** * Clears out data from bag. * * @return mixed Whatever data was contained */ - public function clear(); + public function clear(): mixed; } diff --git a/lib/symfony/http-foundation/Session/SessionBagProxy.php b/lib/symfony/http-foundation/Session/SessionBagProxy.php index 90aa010c9..e759d94db 100644 --- a/lib/symfony/http-foundation/Session/SessionBagProxy.php +++ b/lib/symfony/http-foundation/Session/SessionBagProxy.php @@ -18,17 +18,17 @@ namespace Symfony\Component\HttpFoundation\Session; */ final class SessionBagProxy implements SessionBagInterface { - private $bag; - private $data; - private $usageIndex; - private $usageReporter; + private SessionBagInterface $bag; + private array $data; + private ?int $usageIndex; + private ?\Closure $usageReporter; public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter) { $this->bag = $bag; $this->data = &$data; $this->usageIndex = &$usageIndex; - $this->usageReporter = $usageReporter; + $this->usageReporter = null === $usageReporter ? null : $usageReporter(...); } public function getBag(): SessionBagInterface @@ -54,17 +54,11 @@ final class SessionBagProxy implements SessionBagInterface return empty($this->data[$this->bag->getStorageKey()]); } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->bag->getName(); } - /** - * {@inheritdoc} - */ public function initialize(array &$array): void { ++$this->usageIndex; @@ -77,18 +71,12 @@ final class SessionBagProxy implements SessionBagInterface $this->bag->initialize($array); } - /** - * {@inheritdoc} - */ public function getStorageKey(): string { return $this->bag->getStorageKey(); } - /** - * {@inheritdoc} - */ - public function clear() + public function clear(): mixed { return $this->bag->clear(); } diff --git a/lib/symfony/http-foundation/Session/SessionFactory.php b/lib/symfony/http-foundation/Session/SessionFactory.php index 04c4b06a0..cdb6af51e 100644 --- a/lib/symfony/http-foundation/Session/SessionFactory.php +++ b/lib/symfony/http-foundation/Session/SessionFactory.php @@ -22,15 +22,15 @@ class_exists(Session::class); */ class SessionFactory implements SessionFactoryInterface { - private $requestStack; - private $storageFactory; - private $usageReporter; + private RequestStack $requestStack; + private SessionStorageFactoryInterface $storageFactory; + private ?\Closure $usageReporter; public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null) { $this->requestStack = $requestStack; $this->storageFactory = $storageFactory; - $this->usageReporter = $usageReporter; + $this->usageReporter = null === $usageReporter ? null : $usageReporter(...); } public function createSession(): SessionInterface diff --git a/lib/symfony/http-foundation/Session/SessionInterface.php b/lib/symfony/http-foundation/Session/SessionInterface.php index b2f09fd0d..534883d2d 100644 --- a/lib/symfony/http-foundation/Session/SessionInterface.php +++ b/lib/symfony/http-foundation/Session/SessionInterface.php @@ -23,33 +23,31 @@ interface SessionInterface /** * Starts the session storage. * - * @return bool - * * @throws \RuntimeException if session fails to start */ - public function start(); + public function start(): bool; /** * Returns the session ID. - * - * @return string */ - public function getId(); + public function getId(): string; /** * Sets the session ID. + * + * @return void */ public function setId(string $id); /** * Returns the session name. - * - * @return string */ - public function getName(); + public function getName(): string; /** * Sets the session name. + * + * @return void */ public function setName(string $name); @@ -59,28 +57,24 @@ interface SessionInterface * Clears all session attributes and flashes and regenerates the * session and deletes the old session from persistence. * - * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value - * will leave the system settings unchanged, 0 sets the cookie - * to expire with browser session. Time is in seconds, and is - * not a Unix timestamp. - * - * @return bool + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. */ - public function invalidate(int $lifetime = null); + public function invalidate(int $lifetime = null): bool; /** * Migrates the current session to a new session id while maintaining all * session attributes. * - * @param bool $destroy Whether to delete the old session or leave it to garbage collection - * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value - * will leave the system settings unchanged, 0 sets the cookie - * to expire with browser session. Time is in seconds, and is - * not a Unix timestamp. - * - * @return bool + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. */ - public function migrate(bool $destroy = false, int $lifetime = null); + public function migrate(bool $destroy = false, int $lifetime = null): bool; /** * Force the session to be saved and closed. @@ -88,41 +82,37 @@ interface SessionInterface * This method is generally not required for real sessions as * the session will be automatically saved at the end of * code execution. + * + * @return void */ public function save(); /** * Checks if an attribute is defined. - * - * @return bool */ - public function has(string $name); + public function has(string $name): bool; /** * Returns an attribute. - * - * @param mixed $default The default value if not found - * - * @return mixed */ - public function get(string $name, $default = null); + public function get(string $name, mixed $default = null): mixed; /** * Sets an attribute. * - * @param mixed $value + * @return void */ - public function set(string $name, $value); + public function set(string $name, mixed $value); /** * Returns attributes. - * - * @return array */ - public function all(); + public function all(): array; /** * Sets attributes. + * + * @return void */ public function replace(array $attributes); @@ -131,36 +121,34 @@ interface SessionInterface * * @return mixed The removed value or null when it does not exist */ - public function remove(string $name); + public function remove(string $name): mixed; /** * Clears all attributes. + * + * @return void */ public function clear(); /** * Checks if the session was started. - * - * @return bool */ - public function isStarted(); + public function isStarted(): bool; /** * Registers a SessionBagInterface with the session. + * + * @return void */ public function registerBag(SessionBagInterface $bag); /** * Gets a bag instance by name. - * - * @return SessionBagInterface */ - public function getBag(string $name); + public function getBag(string $name): SessionBagInterface; /** * Gets session meta. - * - * @return MetadataBag */ - public function getMetadataBag(); + public function getMetadataBag(): MetadataBag; } diff --git a/lib/symfony/http-foundation/Session/SessionUtils.php b/lib/symfony/http-foundation/Session/SessionUtils.php index b5bce4a88..504c5848e 100644 --- a/lib/symfony/http-foundation/Session/SessionUtils.php +++ b/lib/symfony/http-foundation/Session/SessionUtils.php @@ -25,7 +25,7 @@ final class SessionUtils * Finds the session header amongst the headers that are to be sent, removes it, and returns * it so the caller can process it further. */ - public static function popSessionCookie(string $sessionName, string $sessionId): ?string + public static function popSessionCookie(string $sessionName, #[\SensitiveParameter] string $sessionId): ?string { $sessionCookie = null; $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName)); diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php index 35d7b4b81..288c24232 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -22,17 +22,13 @@ use Symfony\Component\HttpFoundation\Session\SessionUtils; */ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { - private $sessionName; - private $prefetchId; - private $prefetchData; - private $newSessionId; - private $igbinaryEmptyData; + private string $sessionName; + private string $prefetchId; + private string $prefetchData; + private ?string $newSessionId = null; + private string $igbinaryEmptyData; - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function open($savePath, $sessionName) + public function open(string $savePath, string $sessionName): bool { $this->sessionName = $sessionName; if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) { @@ -42,52 +38,26 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess return true; } - /** - * @return string - */ - abstract protected function doRead(string $sessionId); + abstract protected function doRead(#[\SensitiveParameter] string $sessionId): string; - /** - * @return bool - */ - abstract protected function doWrite(string $sessionId, string $data); + abstract protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool; - /** - * @return bool - */ - abstract protected function doDestroy(string $sessionId); + abstract protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool; - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function validateId($sessionId) + public function validateId(#[\SensitiveParameter] string $sessionId): bool { $this->prefetchData = $this->read($sessionId); $this->prefetchId = $sessionId; - if (\PHP_VERSION_ID < 70317 || (70400 <= \PHP_VERSION_ID && \PHP_VERSION_ID < 70405)) { - // work around https://bugs.php.net/79413 - foreach (debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { - if (!isset($frame['class']) && isset($frame['function']) && \in_array($frame['function'], ['session_regenerate_id', 'session_create_id'], true)) { - return '' === $this->prefetchData; - } - } - } - return '' !== $this->prefetchData; } - /** - * @return string - */ - #[\ReturnTypeWillChange] - public function read($sessionId) + public function read(#[\SensitiveParameter] string $sessionId): string { - if (null !== $this->prefetchId) { + if (isset($this->prefetchId)) { $prefetchId = $this->prefetchId; $prefetchData = $this->prefetchData; - $this->prefetchId = $this->prefetchData = null; + unset($this->prefetchId, $this->prefetchData); if ($prefetchId === $sessionId || '' === $prefetchData) { $this->newSessionId = '' === $prefetchData ? $sessionId : null; @@ -102,16 +72,10 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess return $data; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function write($sessionId, $data) + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool { - if (null === $this->igbinaryEmptyData) { - // see https://github.com/igbinary/igbinary/issues/146 - $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; - } + // see https://github.com/igbinary/igbinary/issues/146 + $this->igbinaryEmptyData ??= \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; if ('' === $data || $this->igbinaryEmptyData === $data) { return $this->destroy($sessionId); } @@ -120,14 +84,10 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess return $this->doWrite($sessionId, $data); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function destroy($sessionId) + public function destroy(#[\SensitiveParameter] string $sessionId): bool { - if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { - if (!$this->sessionName) { + if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL)) { + if (!isset($this->sessionName)) { throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); } $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId); @@ -140,13 +100,9 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess * started the session). */ if (null === $cookie || isset($_COOKIE[$this->sessionName])) { - if (\PHP_VERSION_ID < 70300) { - setcookie($this->sessionName, '', 0, \ini_get('session.cookie_path'), \ini_get('session.cookie_domain'), filter_var(\ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(\ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); - } else { - $params = session_get_cookie_params(); - unset($params['lifetime']); - setcookie($this->sessionName, '', $params); - } + $params = session_get_cookie_params(); + unset($params['lifetime']); + setcookie($this->sessionName, '', $params); } } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php b/lib/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php index bea3a323e..411a8d1f0 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php @@ -18,9 +18,6 @@ use Symfony\Component\Cache\Marshaller\MarshallerInterface; */ class IdentityMarshaller implements MarshallerInterface { - /** - * {@inheritdoc} - */ public function marshall(array $values, ?array &$failed): array { foreach ($values as $key => $value) { @@ -32,9 +29,6 @@ class IdentityMarshaller implements MarshallerInterface return $values; } - /** - * {@inheritdoc} - */ public function unmarshall(string $value): string { return $value; diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php index c321c8c93..1567f5433 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php @@ -18,8 +18,8 @@ use Symfony\Component\Cache\Marshaller\MarshallerInterface; */ class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { - private $handler; - private $marshaller; + private AbstractSessionHandler $handler; + private MarshallerInterface $marshaller; public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller) { @@ -27,56 +27,32 @@ class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpd $this->marshaller = $marshaller; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function open($savePath, $name) + public function open(string $savePath, string $name): bool { return $this->handler->open($savePath, $name); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { return $this->handler->close(); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function destroy($sessionId) + public function destroy(#[\SensitiveParameter] string $sessionId): bool { return $this->handler->destroy($sessionId); } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { return $this->handler->gc($maxlifetime); } - /** - * @return string - */ - #[\ReturnTypeWillChange] - public function read($sessionId) + public function read(#[\SensitiveParameter] string $sessionId): string { return $this->marshaller->unmarshall($this->handler->read($sessionId)); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function write($sessionId, $data) + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool { $failed = []; $marshalledData = $this->marshaller->marshall(['data' => $data], $failed); @@ -88,20 +64,12 @@ class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpd return $this->handler->write($sessionId, $marshalledData['data']); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function validateId($sessionId) + public function validateId(#[\SensitiveParameter] string $sessionId): bool { return $this->handler->validateId($sessionId); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler->updateTimestamp($sessionId, $data); } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php index e0ec4d2d9..91a023ddb 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -21,17 +21,17 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; */ class MemcachedSessionHandler extends AbstractSessionHandler { - private $memcached; + private \Memcached $memcached; /** - * @var int Time to live in seconds + * Time to live in seconds. */ - private $ttl; + private int|\Closure|null $ttl; /** - * @var string Key prefix for shared environments + * Key prefix for shared environments. */ - private $prefix; + private string $prefix; /** * Constructor. @@ -54,45 +54,31 @@ class MemcachedSessionHandler extends AbstractSessionHandler $this->prefix = $options['prefix'] ?? 'sf2s'; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { return $this->memcached->quit(); } - /** - * {@inheritdoc} - */ - protected function doRead(string $sessionId) + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return $this->memcached->get($this->prefix.$sessionId) ?: ''; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { $this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl()); return true; } - /** - * {@inheritdoc} - */ - protected function doWrite(string $sessionId, string $data) + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl()); } private function getCompatibleTtl(): int { - $ttl = (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')); + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); // If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time. // We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct. @@ -103,21 +89,14 @@ class MemcachedSessionHandler extends AbstractSessionHandler return $ttl; } - /** - * {@inheritdoc} - */ - protected function doDestroy(string $sessionId) + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { $result = $this->memcached->delete($this->prefix.$sessionId); return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode(); } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { // not required here because memcached will auto expire the records anyhow. return 0; @@ -125,10 +104,8 @@ class MemcachedSessionHandler extends AbstractSessionHandler /** * Return a Memcached instance. - * - * @return \Memcached */ - protected function getMemcached() + protected function getMemcached(): \Memcached { return $this->memcached; } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php index bf27ca6cc..8ed6a7b3f 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -22,15 +22,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; */ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { - /** - * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface - */ - private $currentHandler; - - /** - * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface - */ - private $writeOnlyHandler; + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $currentHandler; + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $writeOnlyHandler; public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) { @@ -45,11 +38,7 @@ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdat $this->writeOnlyHandler = $writeOnlyHandler; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { $result = $this->currentHandler->close(); $this->writeOnlyHandler->close(); @@ -57,11 +46,7 @@ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdat return $result; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function destroy($sessionId) + public function destroy(#[\SensitiveParameter] string $sessionId): bool { $result = $this->currentHandler->destroy($sessionId); $this->writeOnlyHandler->destroy($sessionId); @@ -69,11 +54,7 @@ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdat return $result; } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { $result = $this->currentHandler->gc($maxlifetime); $this->writeOnlyHandler->gc($maxlifetime); @@ -81,11 +62,7 @@ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdat return $result; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function open($savePath, $sessionName) + public function open(string $savePath, string $sessionName): bool { $result = $this->currentHandler->open($savePath, $sessionName); $this->writeOnlyHandler->open($savePath, $sessionName); @@ -93,21 +70,13 @@ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdat return $result; } - /** - * @return string - */ - #[\ReturnTypeWillChange] - public function read($sessionId) + public function read(#[\SensitiveParameter] string $sessionId): string { // No reading from new handler until switch-over return $this->currentHandler->read($sessionId); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function write($sessionId, $sessionData) + public function write(#[\SensitiveParameter] string $sessionId, string $sessionData): bool { $result = $this->currentHandler->write($sessionId, $sessionData); $this->writeOnlyHandler->write($sessionId, $sessionData); @@ -115,21 +84,13 @@ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdat return $result; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function validateId($sessionId) + public function validateId(#[\SensitiveParameter] string $sessionId): bool { // No reading from new handler until switch-over return $this->currentHandler->validateId($sessionId); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $sessionData) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $sessionData): bool { $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php index ef8f71942..d5586030f 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -14,29 +14,24 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use MongoDB\BSON\Binary; use MongoDB\BSON\UTCDateTime; use MongoDB\Client; -use MongoDB\Collection; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Query; /** - * Session handler using the mongodb/mongodb package and MongoDB driver extension. + * Session handler using the MongoDB driver extension. * * @author Markus Bachmann + * @author JĂ©rĂ´me Tamarelle * - * @see https://packagist.org/packages/mongodb/mongodb * @see https://php.net/mongodb */ class MongoDbSessionHandler extends AbstractSessionHandler { - private $mongo; - - /** - * @var Collection - */ - private $collection; - - /** - * @var array - */ - private $options; + private Manager $manager; + private string $namespace; + private array $options; + private int|\Closure|null $ttl; /** * Constructor. @@ -47,7 +42,8 @@ class MongoDbSessionHandler extends AbstractSessionHandler * * id_field: The field name for storing the session id [default: _id] * * data_field: The field name for storing the session data [default: data] * * time_field: The field name for storing the timestamp [default: time] - * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] + * * ttl: The time to live in seconds. * * It is strongly recommended to put an index on the `expiry_field` for * garbage-collection. Alternatively it's possible to automatically expire @@ -68,13 +64,18 @@ class MongoDbSessionHandler extends AbstractSessionHandler * * @throws \InvalidArgumentException When "database" or "collection" not provided */ - public function __construct(Client $mongo, array $options) + public function __construct(Client|Manager $mongo, array $options) { if (!isset($options['database']) || !isset($options['collection'])) { throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.'); } - $this->mongo = $mongo; + if ($mongo instanceof Client) { + $mongo = $mongo->getManager(); + } + + $this->manager = $mongo; + $this->namespace = $options['database'].'.'.$options['collection']; $this->options = array_merge([ 'id_field' => '_id', @@ -82,112 +83,104 @@ class MongoDbSessionHandler extends AbstractSessionHandler 'time_field' => 'time', 'expiry_field' => 'expires_at', ], $options); + $this->ttl = $this->options['ttl'] ?? null; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { return true; } - /** - * {@inheritdoc} - */ - protected function doDestroy(string $sessionId) + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { - $this->getCollection()->deleteOne([ - $this->options['id_field'] => $sessionId, - ]); + $write = new BulkWrite(); + $write->delete( + [$this->options['id_field'] => $sessionId], + ['limit' => 1] + ); + + $this->manager->executeBulkWrite($this->namespace, $write); return true; } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { - return $this->getCollection()->deleteMany([ - $this->options['expiry_field'] => ['$lt' => new UTCDateTime()], - ])->getDeletedCount(); + $write = new BulkWrite(); + $write->delete( + [$this->options['expiry_field'] => ['$lt' => $this->getUTCDateTime()]], + ); + $result = $this->manager->executeBulkWrite($this->namespace, $write); + + return $result->getDeletedCount() ?? false; } - /** - * {@inheritdoc} - */ - protected function doWrite(string $sessionId, string $data) + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { - $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + $expiry = $this->getUTCDateTime($ttl); $fields = [ - $this->options['time_field'] => new UTCDateTime(), + $this->options['time_field'] => $this->getUTCDateTime(), $this->options['expiry_field'] => $expiry, - $this->options['data_field'] => new Binary($data, Binary::TYPE_OLD_BINARY), + $this->options['data_field'] => new Binary($data, Binary::TYPE_GENERIC), ]; - $this->getCollection()->updateOne( + $write = new BulkWrite(); + $write->update( [$this->options['id_field'] => $sessionId], ['$set' => $fields], ['upsert' => true] ); + $this->manager->executeBulkWrite($this->namespace, $write); + return true; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { - $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + $expiry = $this->getUTCDateTime($ttl); - $this->getCollection()->updateOne( + $write = new BulkWrite(); + $write->update( [$this->options['id_field'] => $sessionId], ['$set' => [ - $this->options['time_field'] => new UTCDateTime(), + $this->options['time_field'] => $this->getUTCDateTime(), $this->options['expiry_field'] => $expiry, - ]] + ]], + ['multi' => false], ); + $this->manager->executeBulkWrite($this->namespace, $write); + return true; } - /** - * {@inheritdoc} - */ - protected function doRead(string $sessionId) + protected function doRead(#[\SensitiveParameter] string $sessionId): string { - $dbData = $this->getCollection()->findOne([ + $cursor = $this->manager->executeQuery($this->namespace, new Query([ $this->options['id_field'] => $sessionId, - $this->options['expiry_field'] => ['$gte' => new UTCDateTime()], - ]); + $this->options['expiry_field'] => ['$gte' => $this->getUTCDateTime()], + ], [ + 'projection' => [ + '_id' => false, + $this->options['data_field'] => true, + ], + 'limit' => 1, + ])); - if (null === $dbData) { - return ''; + foreach ($cursor as $document) { + return (string) $document->{$this->options['data_field']} ?? ''; } - return $dbData[$this->options['data_field']]->getData(); + // Not found + return ''; } - private function getCollection(): Collection + private function getUTCDateTime(int $additionalSeconds = 0): UTCDateTime { - if (null === $this->collection) { - $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); - } - - return $this->collection; - } - - /** - * @return Client - */ - protected function getMongo() - { - return $this->mongo; + return new UTCDateTime((time() + $additionalSeconds) * 1000); } } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php index 1ca4bfeb0..f6e73f9e6 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -19,9 +19,9 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; class NativeFileSessionHandler extends \SessionHandler { /** - * @param string $savePath Path of directory to save session files - * Default null will leave setting as defined by PHP. - * '/path', 'N;/path', or 'N;octal-mode;/path + * @param string|null $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path * * @see https://php.net/session.configuration#ini.session.save-path for further details. * @@ -30,11 +30,7 @@ class NativeFileSessionHandler extends \SessionHandler */ public function __construct(string $savePath = null) { - if (null === $savePath) { - $savePath = \ini_get('session.save_path'); - } - - $baseDir = $savePath; + $baseDir = $savePath ??= \ini_get('session.save_path'); if ($count = substr_count($savePath, ';')) { if ($count > 2) { @@ -49,7 +45,11 @@ class NativeFileSessionHandler extends \SessionHandler throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $baseDir)); } - ini_set('session.save_path', $savePath); - ini_set('session.save_handler', 'files'); + if ($savePath !== \ini_get('session.save_path')) { + ini_set('session.save_path', $savePath); + } + if ('files' !== \ini_get('session.save_handler')) { + ini_set('session.save_handler', 'files'); + } } } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php index 4331dbe50..a77185e2e 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -18,62 +18,37 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; */ class NullSessionHandler extends AbstractSessionHandler { - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { return true; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function validateId($sessionId) + public function validateId(#[\SensitiveParameter] string $sessionId): bool { return true; } - /** - * {@inheritdoc} - */ - protected function doRead(string $sessionId) + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return ''; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return true; } - /** - * {@inheritdoc} - */ - protected function doWrite(string $sessionId, string $data) + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { return true; } - /** - * {@inheritdoc} - */ - protected function doDestroy(string $sessionId) + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { return true; } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { return 0; } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php index 24c98940d..1e8f9c6c4 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Types\Types; + /** * Session handler using a PDO connection to read and write data. * @@ -65,105 +68,66 @@ class PdoSessionHandler extends AbstractSessionHandler */ public const LOCK_TRANSACTIONAL = 2; - private const MAX_LIFETIME = 315576000; - - /** - * @var \PDO|null PDO instance or null when not connected yet - */ - private $pdo; + private \PDO $pdo; /** * DSN string or null for session.save_path or false when lazy connection disabled. - * - * @var string|false|null */ - private $dsn = false; + private string|false|null $dsn = false; + + private string $driver; + private string $table = 'sessions'; + private string $idCol = 'sess_id'; + private string $dataCol = 'sess_data'; + private string $lifetimeCol = 'sess_lifetime'; + private string $timeCol = 'sess_time'; /** - * @var string|null + * Time to live in seconds. */ - private $driver; - - /** - * @var string - */ - private $table = 'sessions'; - - /** - * @var string - */ - private $idCol = 'sess_id'; - - /** - * @var string - */ - private $dataCol = 'sess_data'; - - /** - * @var string - */ - private $lifetimeCol = 'sess_lifetime'; - - /** - * @var string - */ - private $timeCol = 'sess_time'; + private int|\Closure|null $ttl; /** * Username when lazy-connect. - * - * @var string */ - private $username = ''; + private ?string $username = null; /** * Password when lazy-connect. - * - * @var string */ - private $password = ''; + private ?string $password = null; /** * Connection options when lazy-connect. - * - * @var array */ - private $connectionOptions = []; + private array $connectionOptions = []; /** * The strategy for locking, see constants. - * - * @var int */ - private $lockMode = self::LOCK_TRANSACTIONAL; + private int $lockMode = self::LOCK_TRANSACTIONAL; /** * It's an array to support multiple reads before closing which is manual, non-standard usage. * * @var \PDOStatement[] An array of statements to release advisory locks */ - private $unlockStatements = []; + private array $unlockStatements = []; /** * True when the current session exists but expired according to session.gc_maxlifetime. - * - * @var bool */ - private $sessionExpired = false; + private bool $sessionExpired = false; /** * Whether a transaction is active. - * - * @var bool */ - private $inTransaction = false; + private bool $inTransaction = false; /** * Whether gc() has been called. - * - * @var bool */ - private $gcCalled = false; + private bool $gcCalled = false; /** * You can either pass an existing database connection as PDO instance or @@ -181,12 +145,13 @@ class PdoSessionHandler extends AbstractSessionHandler * * db_password: The password when lazy-connect [default: ''] * * db_connection_options: An array of driver-specific connection options [default: []] * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * * ttl: The time to live in seconds. * * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null * * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION */ - public function __construct($pdoOrDsn = null, array $options = []) + public function __construct(#[\SensitiveParameter] \PDO|string $pdoOrDsn = null, #[\SensitiveParameter] array $options = []) { if ($pdoOrDsn instanceof \PDO) { if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { @@ -210,6 +175,57 @@ class PdoSessionHandler extends AbstractSessionHandler $this->password = $options['db_password'] ?? $this->password; $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; $this->lockMode = $options['lock_mode'] ?? $this->lockMode; + $this->ttl = $options['ttl'] ?? null; + } + + /** + * Adds the Table to the Schema if it doesn't exist. + */ + public function configureSchema(Schema $schema, \Closure $isSameDatabase = null): void + { + if ($schema->hasTable($this->table) || ($isSameDatabase && !$isSameDatabase($this->getConnection()->exec(...)))) { + return; + } + + $table = $schema->createTable($this->table); + switch ($this->driver) { + case 'mysql': + $table->addColumn($this->idCol, Types::BINARY)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addOption('collate', 'utf8mb4_bin'); + $table->addOption('engine', 'InnoDB'); + break; + case 'sqlite': + $table->addColumn($this->idCol, Types::TEXT)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'pgsql': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BINARY)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'oci': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'sqlsrv': + $table->addColumn($this->idCol, Types::TEXT)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + $table->setPrimaryKey([$this->idCol]); + $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx'); } /** @@ -220,6 +236,8 @@ class PdoSessionHandler extends AbstractSessionHandler * saved in a BLOB. One could also use a shorter inlined varbinary column * if one was sure the data fits into it. * + * @return void + * * @throws \PDOException When the table already exists * @throws \DomainException When an unsupported PDO driver is used */ @@ -228,34 +246,23 @@ class PdoSessionHandler extends AbstractSessionHandler // connect if we are not yet $this->getConnection(); - switch ($this->driver) { - case 'mysql': - // We use varbinary for the ID column because it prevents unwanted conversions: - // - character set conversions between server and client - // - trailing space removal - // - case-insensitivity - // - language processing like é == e - $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; - break; - case 'sqlite': - $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; - break; - case 'pgsql': - $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; - break; - case 'oci': - $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; - break; - case 'sqlsrv': - $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; - break; - default: - throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); - } + $sql = match ($this->driver) { + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + 'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB", + 'sqlite' => "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + 'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + 'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + 'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + default => throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)), + }; try { $this->pdo->exec($sql); - $this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)"); + $this->pdo->exec("CREATE INDEX {$this->lifetimeCol}_idx ON $this->table ($this->lifetimeCol)"); } catch (\PDOException $e) { $this->rollback(); @@ -267,34 +274,24 @@ class PdoSessionHandler extends AbstractSessionHandler * Returns true when the current session exists but expired according to session.gc_maxlifetime. * * Can be used to distinguish between a new session and one that expired due to inactivity. - * - * @return bool */ - public function isSessionExpired() + public function isSessionExpired(): bool { return $this->sessionExpired; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function open($savePath, $sessionName) + public function open(string $savePath, string $sessionName): bool { $this->sessionExpired = false; - if (null === $this->pdo) { + if (!isset($this->pdo)) { $this->connect($this->dsn ?: $savePath); } return parent::open($savePath, $sessionName); } - /** - * @return string - */ - #[\ReturnTypeWillChange] - public function read($sessionId) + public function read(#[\SensitiveParameter] string $sessionId): string { try { return parent::read($sessionId); @@ -305,11 +302,7 @@ class PdoSessionHandler extends AbstractSessionHandler } } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. // This way, pruning expired sessions does not block them from being started while the current session is used. @@ -318,10 +311,7 @@ class PdoSessionHandler extends AbstractSessionHandler return 0; } - /** - * {@inheritdoc} - */ - protected function doDestroy(string $sessionId) + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { // delete the record associated with this id $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; @@ -339,12 +329,9 @@ class PdoSessionHandler extends AbstractSessionHandler return true; } - /** - * {@inheritdoc} - */ - protected function doWrite(string $sessionId, string $data) + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { - $maxlifetime = (int) \ini_get('session.gc_maxlifetime'); + $maxlifetime = (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime')); try { // We use a single MERGE SQL query when supported by the database. @@ -385,20 +372,16 @@ class PdoSessionHandler extends AbstractSessionHandler return true; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { - $expiry = time() + (int) \ini_get('session.gc_maxlifetime'); + $expiry = time() + (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime')); try { $updateStmt = $this->pdo->prepare( "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" ); - $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); - $updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT); + $updateStmt->bindValue(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindValue(':expiry', $expiry, \PDO::PARAM_INT); $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); $updateStmt->execute(); } catch (\PDOException $e) { @@ -410,11 +393,7 @@ class PdoSessionHandler extends AbstractSessionHandler return true; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { $this->commit(); @@ -426,27 +405,14 @@ class PdoSessionHandler extends AbstractSessionHandler $this->gcCalled = false; // delete the session records that have expired - $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min"; + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time"; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); - $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); - $stmt->execute(); - // to be removed in 6.0 - if ('mysql' === $this->driver) { - $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time"; - } else { - $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol"; - } - - $stmt = $this->pdo->prepare($legacySql); - $stmt->bindValue(':time', time(), \PDO::PARAM_INT); - $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); $stmt->execute(); } if (false !== $this->dsn) { - $this->pdo = null; // only close lazy-connection - $this->driver = null; + unset($this->pdo, $this->driver); // only close lazy-connection } return true; @@ -455,7 +421,7 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Lazy-connects to the database. */ - private function connect(string $dsn): void + private function connect(#[\SensitiveParameter] string $dsn): void { $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); @@ -467,7 +433,7 @@ class PdoSessionHandler extends AbstractSessionHandler * * @todo implement missing support for oci DSN (which look totally different from other PDO ones) */ - private function buildDsnFromUrl(string $dsnOrUrl): string + private function buildDsnFromUrl(#[\SensitiveParameter] string $dsnOrUrl): string { // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); @@ -530,10 +496,10 @@ class PdoSessionHandler extends AbstractSessionHandler return $dsn; } } - // If "unix_socket" is not in the query, we continue with the same process as pgsql - // no break + // If "unix_socket" is not in the query, we continue with the same process as pgsql + // no break case 'pgsql': - $dsn ?? $dsn = 'pgsql:'; + $dsn ??= 'pgsql:'; if (isset($params['host']) && '' !== $params['host']) { $dsn .= 'host='.$params['host'].';'; @@ -649,10 +615,8 @@ class PdoSessionHandler extends AbstractSessionHandler * * We need to make sure we do not return session data that is already considered garbage according * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. - * - * @return string */ - protected function doRead(string $sessionId) + protected function doRead(#[\SensitiveParameter] string $sessionId): string { if (self::LOCK_ADVISORY === $this->lockMode) { $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); @@ -669,9 +633,6 @@ class PdoSessionHandler extends AbstractSessionHandler if ($sessionRows) { $expiry = (int) $sessionRows[0][1]; - if ($expiry <= self::MAX_LIFETIME) { - $expiry += $sessionRows[0][2]; - } if ($expiry < time()) { $this->sessionExpired = true; @@ -687,7 +648,7 @@ class PdoSessionHandler extends AbstractSessionHandler throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); } - if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOL) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { // In strict mode, session fixation is not possible: new sessions always start with a unique // random id, so that concurrency is not possible and this code path can be skipped. // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block @@ -726,7 +687,7 @@ class PdoSessionHandler extends AbstractSessionHandler * - for oci using DBMS_LOCK.REQUEST * - for sqlsrv using sp_getapplock with LockOwner = Session */ - private function doAdvisoryLock(string $sessionId): \PDOStatement + private function doAdvisoryLock(#[\SensitiveParameter] string $sessionId): \PDOStatement { switch ($this->driver) { case 'mysql': @@ -804,14 +765,13 @@ class PdoSessionHandler extends AbstractSessionHandler if (self::LOCK_TRANSACTIONAL === $this->lockMode) { $this->beginTransaction(); - // selecting the time column should be removed in 6.0 switch ($this->driver) { case 'mysql': case 'oci': case 'pgsql': - return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + return "SELECT $this->dataCol, $this->lifetimeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; case 'sqlsrv': - return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + return "SELECT $this->dataCol, $this->lifetimeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; case 'sqlite': // we already locked when starting transaction break; @@ -820,13 +780,13 @@ class PdoSessionHandler extends AbstractSessionHandler } } - return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id"; + return "SELECT $this->dataCol, $this->lifetimeCol FROM $this->table WHERE $this->idCol = :id"; } /** * Returns an insert statement supported by the database for writing session data. */ - private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + private function getInsertStatement(#[\SensitiveParameter] string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': @@ -853,7 +813,7 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Returns an update statement supported by the database for writing session data. */ - private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + private function getUpdateStatement(#[\SensitiveParameter] string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': @@ -880,7 +840,7 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. */ - private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement + private function getMergeStatement(#[\SensitiveParameter] string $sessionId, string $data, int $maxlifetime): ?\PDOStatement { switch (true) { case 'mysql' === $this->driver: @@ -929,12 +889,10 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Return a PDO instance. - * - * @return \PDO */ - protected function getConnection() + protected function getConnection(): \PDO { - if (null === $this->pdo) { + if (!isset($this->pdo)) { $this->connect($this->dsn ?: \ini_get('session.save_path')); } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php index 31954e677..b696eee4b 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php @@ -12,8 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use Predis\Response\ErrorInterface; -use Symfony\Component\Cache\Traits\RedisClusterProxy; -use Symfony\Component\Cache\Traits\RedisProxy; +use Relay\Relay; /** * Redis based session storage handler based on the Redis class @@ -23,78 +22,56 @@ use Symfony\Component\Cache\Traits\RedisProxy; */ class RedisSessionHandler extends AbstractSessionHandler { - private $redis; + /** + * Key prefix for shared environments. + */ + private string $prefix; /** - * @var string Key prefix for shared environments + * Time to live in seconds. */ - private $prefix; - - /** - * @var int Time to live in seconds - */ - private $ttl; + private int|\Closure|null $ttl; /** * List of available options: * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server * * ttl: The time to live in seconds. * - * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis - * * @throws \InvalidArgumentException When unsupported client or options are passed */ - public function __construct($redis, array $options = []) - { - if ( - !$redis instanceof \Redis && - !$redis instanceof \RedisArray && - !$redis instanceof \RedisCluster && - !$redis instanceof \Predis\ClientInterface && - !$redis instanceof RedisProxy && - !$redis instanceof RedisClusterProxy - ) { - throw new \InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis))); - } - + public function __construct( + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + array $options = [], + ) { if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); } - $this->redis = $redis; $this->prefix = $options['prefix'] ?? 'sf_s'; $this->ttl = $options['ttl'] ?? null; } - /** - * {@inheritdoc} - */ - protected function doRead(string $sessionId): string + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return $this->redis->get($this->prefix.$sessionId) ?: ''; } - /** - * {@inheritdoc} - */ - protected function doWrite(string $sessionId, string $data): bool + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { - $result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data); + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + $result = $this->redis->setEx($this->prefix.$sessionId, (int) $ttl, $data); return $result && !$result instanceof ErrorInterface; } - /** - * {@inheritdoc} - */ - protected function doDestroy(string $sessionId): bool + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { static $unlink = true; if ($unlink) { try { $unlink = false !== $this->redis->unlink($this->prefix.$sessionId); - } catch (\Throwable $e) { + } catch (\Throwable) { $unlink = false; } } @@ -106,32 +83,21 @@ class RedisSessionHandler extends AbstractSessionHandler return true; } - /** - * {@inheritdoc} - */ #[\ReturnTypeWillChange] public function close(): bool { return true; } - /** - * {@inheritdoc} - * - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { return 0; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { - return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + + return $this->redis->expire($this->prefix.$sessionId, (int) $ttl); } } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/lib/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php index f3f7b201d..ff5b70d81 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -11,36 +11,35 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; -use Symfony\Component\Cache\Traits\RedisClusterProxy; -use Symfony\Component\Cache\Traits\RedisProxy; /** * @author Nicolas Grekas */ class SessionHandlerFactory { - /** - * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN - */ - public static function createHandler($connection): AbstractSessionHandler + public static function createHandler(object|string $connection, array $options = []): AbstractSessionHandler { - if (!\is_string($connection) && !\is_object($connection)) { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a string or a connection object, "%s" given.', __METHOD__, get_debug_type($connection))); - } + if ($query = \is_string($connection) ? parse_url($connection) : false) { + parse_str($query['query'] ?? '', $query); - if ($options = \is_string($connection) ? parse_url($connection) : false) { - parse_str($options['query'] ?? '', $options); + if (($options['ttl'] ?? null) instanceof \Closure) { + $query['ttl'] = $options['ttl']; + } } + $options = ($query ?: []) + $options; switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: - case $connection instanceof RedisProxy: - case $connection instanceof RedisClusterProxy: return new RedisSessionHandler($connection); case $connection instanceof \Memcached: @@ -60,18 +59,27 @@ class SessionHandlerFactory case str_starts_with($connection, 'rediss:'): case str_starts_with($connection, 'memcached:'): if (!class_exists(AbstractAdapter::class)) { - throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); + throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".'); } $handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); - return new $handlerClass($connection, array_intersect_key($options ?: [], ['prefix' => 1, 'ttl' => 1])); + return new $handlerClass($connection, array_intersect_key($options, ['prefix' => 1, 'ttl' => 1])); case str_starts_with($connection, 'pdo_oci://'): if (!class_exists(DriverManager::class)) { - throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".'); } - $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); + $connection[3] = '-'; + $params = class_exists(DsnParser::class) ? (new DsnParser())->parse($connection) : ['url' => $connection]; + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $connection = DriverManager::getConnection($params, $config); + // The condition should be removed once support for DBAL <3.3 is dropped + $connection = method_exists($connection, 'getNativeConnection') ? $connection->getNativeConnection() : $connection->getWrappedConnection(); // no break; case str_starts_with($connection, 'mssql://'): @@ -83,7 +91,7 @@ class SessionHandlerFactory case str_starts_with($connection, 'sqlsrv://'): case str_starts_with($connection, 'sqlite://'): case str_starts_with($connection, 'sqlite3://'): - return new PdoSessionHandler($connection, $options ?: []); + return new PdoSessionHandler($connection, $options); } throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection)); diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php index f7c385f64..1f8668744 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -18,8 +18,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; */ class StrictSessionHandler extends AbstractSessionHandler { - private $handler; - private $doDestroy; + private \SessionHandlerInterface $handler; + private bool $doDestroy; public function __construct(\SessionHandlerInterface $handler) { @@ -40,47 +40,29 @@ class StrictSessionHandler extends AbstractSessionHandler return $this->handler instanceof \SessionHandler; } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function open($savePath, $sessionName) + public function open(string $savePath, string $sessionName): bool { parent::open($savePath, $sessionName); return $this->handler->open($savePath, $sessionName); } - /** - * {@inheritdoc} - */ - protected function doRead(string $sessionId) + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return $this->handler->read($sessionId); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->write($sessionId, $data); } - /** - * {@inheritdoc} - */ - protected function doWrite(string $sessionId, string $data) + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler->write($sessionId, $data); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function destroy($sessionId) + public function destroy(#[\SensitiveParameter] string $sessionId): bool { $this->doDestroy = true; $destroyed = parent::destroy($sessionId); @@ -88,30 +70,19 @@ class StrictSessionHandler extends AbstractSessionHandler return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed; } - /** - * {@inheritdoc} - */ - protected function doDestroy(string $sessionId) + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { $this->doDestroy = false; return $this->handler->destroy($sessionId); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { return $this->handler->close(); } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { return $this->handler->gc($maxlifetime); } diff --git a/lib/symfony/http-foundation/Session/Storage/MetadataBag.php b/lib/symfony/http-foundation/Session/Storage/MetadataBag.php index 595a9e23c..ebe4b748a 100644 --- a/lib/symfony/http-foundation/Session/Storage/MetadataBag.php +++ b/lib/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -26,15 +26,8 @@ class MetadataBag implements SessionBagInterface public const UPDATED = 'u'; public const LIFETIME = 'l'; - /** - * @var string - */ - private $name = '__metadata'; - - /** - * @var string - */ - private $storageKey; + private string $name = '__metadata'; + private string $storageKey; /** * @var array @@ -43,15 +36,10 @@ class MetadataBag implements SessionBagInterface /** * Unix timestamp. - * - * @var int */ - private $lastUsed; + private int $lastUsed; - /** - * @var int - */ - private $updateThreshold; + private int $updateThreshold; /** * @param string $storageKey The key used to store bag in the session @@ -64,7 +52,7 @@ class MetadataBag implements SessionBagInterface } /** - * {@inheritdoc} + * @return void */ public function initialize(array &$array) { @@ -84,10 +72,8 @@ class MetadataBag implements SessionBagInterface /** * Gets the lifetime that the session cookie was set with. - * - * @return int */ - public function getLifetime() + public function getLifetime(): int { return $this->meta[self::LIFETIME]; } @@ -95,20 +81,19 @@ class MetadataBag implements SessionBagInterface /** * Stamps a new session's metadata. * - * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value - * will leave the system settings unchanged, 0 sets the cookie - * to expire with browser session. Time is in seconds, and is - * not a Unix timestamp. + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return void */ public function stampNew(int $lifetime = null) { $this->stampCreated($lifetime); } - /** - * {@inheritdoc} - */ - public function getStorageKey() + public function getStorageKey(): string { return $this->storageKey; } @@ -118,7 +103,7 @@ class MetadataBag implements SessionBagInterface * * @return int Unix timestamp */ - public function getCreated() + public function getCreated(): int { return $this->meta[self::CREATED]; } @@ -128,30 +113,26 @@ class MetadataBag implements SessionBagInterface * * @return int Unix timestamp */ - public function getLastUsed() + public function getLastUsed(): int { return $this->lastUsed; } - /** - * {@inheritdoc} - */ - public function clear() + public function clear(): mixed { // nothing to do return null; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->name; } /** * Sets name. + * + * @return void */ public function setName(string $name) { diff --git a/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php index c5c2bb073..d30b56d69 100644 --- a/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -68,15 +68,15 @@ class MockArraySessionStorage implements SessionStorageInterface $this->setMetadataBag($metaBag); } + /** + * @return void + */ public function setSessionData(array $array) { $this->data = $array; } - /** - * {@inheritdoc} - */ - public function start() + public function start(): bool { if ($this->started) { return true; @@ -91,10 +91,7 @@ class MockArraySessionStorage implements SessionStorageInterface return true; } - /** - * {@inheritdoc} - */ - public function regenerate(bool $destroy = false, int $lifetime = null) + public function regenerate(bool $destroy = false, int $lifetime = null): bool { if (!$this->started) { $this->start(); @@ -106,16 +103,13 @@ class MockArraySessionStorage implements SessionStorageInterface return true; } - /** - * {@inheritdoc} - */ - public function getId() + public function getId(): string { return $this->id; } /** - * {@inheritdoc} + * @return void */ public function setId(string $id) { @@ -126,16 +120,13 @@ class MockArraySessionStorage implements SessionStorageInterface $this->id = $id; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->name; } /** - * {@inheritdoc} + * @return void */ public function setName(string $name) { @@ -143,7 +134,7 @@ class MockArraySessionStorage implements SessionStorageInterface } /** - * {@inheritdoc} + * @return void */ public function save() { @@ -156,7 +147,7 @@ class MockArraySessionStorage implements SessionStorageInterface } /** - * {@inheritdoc} + * @return void */ public function clear() { @@ -173,17 +164,14 @@ class MockArraySessionStorage implements SessionStorageInterface } /** - * {@inheritdoc} + * @return void */ public function registerBag(SessionBagInterface $bag) { $this->bags[$bag->getName()] = $bag; } - /** - * {@inheritdoc} - */ - public function getBag(string $name) + public function getBag(string $name): SessionBagInterface { if (!isset($this->bags[$name])) { throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); @@ -196,29 +184,26 @@ class MockArraySessionStorage implements SessionStorageInterface return $this->bags[$name]; } - /** - * {@inheritdoc} - */ - public function isStarted() + public function isStarted(): bool { return $this->started; } + /** + * @return void + */ public function setMetadataBag(MetadataBag $bag = null) { - if (null === $bag) { - $bag = new MetadataBag(); + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); } - - $this->metadataBag = $bag; + $this->metadataBag = $bag ?? new MetadataBag(); } /** * Gets the MetadataBag. - * - * @return MetadataBag */ - public function getMetadataBag() + public function getMetadataBag(): MetadataBag { return $this->metadataBag; } @@ -228,21 +213,22 @@ class MockArraySessionStorage implements SessionStorageInterface * * This doesn't need to be particularly cryptographically secure since this is just * a mock. - * - * @return string */ - protected function generateId() + protected function generateId(): string { return hash('sha256', uniqid('ss_mock_', true)); } + /** + * @return void + */ protected function loadSession() { $bags = array_merge($this->bags, [$this->metadataBag]); foreach ($bags as $bag) { $key = $bag->getStorageKey(); - $this->data[$key] = $this->data[$key] ?? []; + $this->data[$key] ??= []; $bag->initialize($this->data[$key]); } diff --git a/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php index 8e32a45e3..95f69f2e1 100644 --- a/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -25,16 +25,14 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; */ class MockFileSessionStorage extends MockArraySessionStorage { - private $savePath; + private string $savePath; /** * @param string|null $savePath Path of directory to save session files */ public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) { - if (null === $savePath) { - $savePath = sys_get_temp_dir(); - } + $savePath ??= sys_get_temp_dir(); if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $savePath)); @@ -45,10 +43,7 @@ class MockFileSessionStorage extends MockArraySessionStorage parent::__construct($name, $metaBag); } - /** - * {@inheritdoc} - */ - public function start() + public function start(): bool { if ($this->started) { return true; @@ -65,10 +60,7 @@ class MockFileSessionStorage extends MockArraySessionStorage return true; } - /** - * {@inheritdoc} - */ - public function regenerate(bool $destroy = false, int $lifetime = null) + public function regenerate(bool $destroy = false, int $lifetime = null): bool { if (!$this->started) { $this->start(); @@ -82,7 +74,7 @@ class MockFileSessionStorage extends MockArraySessionStorage } /** - * {@inheritdoc} + * @return void */ public function save() { diff --git a/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php index d0da1e169..8ecf943dc 100644 --- a/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php +++ b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php @@ -21,9 +21,9 @@ class_exists(MockFileSessionStorage::class); */ class MockFileSessionStorageFactory implements SessionStorageFactoryInterface { - private $savePath; - private $name; - private $metaBag; + private ?string $savePath; + private string $name; + private ?MetadataBag $metaBag; /** * @see MockFileSessionStorage constructor. diff --git a/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php index a50c8270f..7c6b6f929 100644 --- a/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; use Symfony\Component\HttpFoundation\Session\SessionBagInterface; -use Symfony\Component\HttpFoundation\Session\SessionUtils; use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; @@ -54,11 +53,6 @@ class NativeSessionStorage implements SessionStorageInterface */ protected $metadataBag; - /** - * @var string|null - */ - private $emulateSameSite; - /** * Depending on how you want the storage driver to behave you probably * want to override this constructor entirely. @@ -94,10 +88,8 @@ class NativeSessionStorage implements SessionStorageInterface * sid_bits_per_character, "5" * trans_sid_hosts, $_SERVER['HTTP_HOST'] * trans_sid_tags, "a=href,area=href,frame=src,form=" - * - * @param AbstractProxy|\SessionHandlerInterface|null $handler */ - public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) + public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null) { if (!\extension_loaded('session')) { throw new \LogicException('PHP extension "session" is required.'); @@ -120,18 +112,13 @@ class NativeSessionStorage implements SessionStorageInterface /** * Gets the save handler instance. - * - * @return AbstractProxy|\SessionHandlerInterface */ - public function getSaveHandler() + public function getSaveHandler(): AbstractProxy|\SessionHandlerInterface { return $this->saveHandler; } - /** - * {@inheritdoc} - */ - public function start() + public function start(): bool { if ($this->started) { return true; @@ -141,7 +128,7 @@ class NativeSessionStorage implements SessionStorageInterface throw new \RuntimeException('Failed to start the session: already started by PHP.'); } - if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { + if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL) && headers_sent($file, $line)) { throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); } @@ -186,54 +173,38 @@ class NativeSessionStorage implements SessionStorageInterface throw new \RuntimeException('Failed to start the session.'); } - if (null !== $this->emulateSameSite) { - $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); - if (null !== $originalCookie) { - header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); - } - } - $this->loadSession(); return true; } - /** - * {@inheritdoc} - */ - public function getId() + public function getId(): string { return $this->saveHandler->getId(); } /** - * {@inheritdoc} + * @return void */ public function setId(string $id) { $this->saveHandler->setId($id); } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->saveHandler->getName(); } /** - * {@inheritdoc} + * @return void */ public function setName(string $name) { $this->saveHandler->setName($name); } - /** - * {@inheritdoc} - */ - public function regenerate(bool $destroy = false, int $lifetime = null) + public function regenerate(bool $destroy = false, int $lifetime = null): bool { // Cannot regenerate the session ID for non-active sessions. if (\PHP_SESSION_ACTIVE !== session_status()) { @@ -254,20 +225,11 @@ class NativeSessionStorage implements SessionStorageInterface $this->metadataBag->stampNew(); } - $isRegenerated = session_regenerate_id($destroy); - - if (null !== $this->emulateSameSite) { - $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); - if (null !== $originalCookie) { - header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); - } - } - - return $isRegenerated; + return session_regenerate_id($destroy); } /** - * {@inheritdoc} + * @return void */ public function save() { @@ -287,7 +249,7 @@ class NativeSessionStorage implements SessionStorageInterface $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) { if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) { $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler; - $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler)); + $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', $handler::class); } return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false; @@ -309,7 +271,7 @@ class NativeSessionStorage implements SessionStorageInterface } /** - * {@inheritdoc} + * @return void */ public function clear() { @@ -326,7 +288,7 @@ class NativeSessionStorage implements SessionStorageInterface } /** - * {@inheritdoc} + * @return void */ public function registerBag(SessionBagInterface $bag) { @@ -337,10 +299,7 @@ class NativeSessionStorage implements SessionStorageInterface $this->bags[$bag->getName()] = $bag; } - /** - * {@inheritdoc} - */ - public function getBag(string $name) + public function getBag(string $name): SessionBagInterface { if (!isset($this->bags[$name])) { throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); @@ -355,29 +314,26 @@ class NativeSessionStorage implements SessionStorageInterface return $this->bags[$name]; } + /** + * @return void + */ public function setMetadataBag(MetadataBag $metaBag = null) { - if (null === $metaBag) { - $metaBag = new MetadataBag(); + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); } - - $this->metadataBag = $metaBag; + $this->metadataBag = $metaBag ?? new MetadataBag(); } /** * Gets the MetadataBag. - * - * @return MetadataBag */ - public function getMetadataBag() + public function getMetadataBag(): MetadataBag { return $this->metadataBag; } - /** - * {@inheritdoc} - */ - public function isStarted() + public function isStarted(): bool { return $this->started; } @@ -391,6 +347,8 @@ class NativeSessionStorage implements SessionStorageInterface * @param array $options Session ini directives [key => value] * * @see https://php.net/session.configuration + * + * @return void */ public function setOptions(array $options) { @@ -404,31 +362,16 @@ class NativeSessionStorage implements SessionStorageInterface 'gc_divisor', 'gc_maxlifetime', 'gc_probability', 'lazy_write', 'name', 'referer_check', 'serialize_handler', 'use_strict_mode', 'use_cookies', - 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', - 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', - 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags', + 'use_only_cookies', 'use_trans_sid', 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', ]); foreach ($options as $key => $value) { if (isset($validOptions[$key])) { - if (str_starts_with($key, 'upload_progress.')) { - trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. The settings prefixed with "session.upload_progress." can not be changed at runtime.', $key); - continue; - } - if ('url_rewriter.tags' === $key) { - trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. Use "trans_sid_tags" instead.', $key); - } - if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) { - // PHP < 7.3 does not support same_site cookies. We will emulate it in - // the start() method instead. - $this->emulateSameSite = $value; - continue; - } if ('cookie_secure' === $key && 'auto' === $value) { continue; } - ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value); + ini_set('session.'.$key, $value); } } } @@ -449,16 +392,14 @@ class NativeSessionStorage implements SessionStorageInterface * @see https://php.net/sessionhandlerinterface * @see https://php.net/sessionhandler * - * @param AbstractProxy|\SessionHandlerInterface|null $saveHandler + * @return void * * @throws \InvalidArgumentException */ - public function setSaveHandler($saveHandler = null) + public function setSaveHandler(AbstractProxy|\SessionHandlerInterface $saveHandler = null) { - if (!$saveHandler instanceof AbstractProxy && - !$saveHandler instanceof \SessionHandlerInterface && - null !== $saveHandler) { - throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.'); + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); } // Wrap $saveHandler in proxy and prevent double wrapping of proxy @@ -485,6 +426,8 @@ class NativeSessionStorage implements SessionStorageInterface * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). * PHP takes the return value from the read() handler, unserializes it * and populates $_SESSION with the result automatically. + * + * @return void */ protected function loadSession(array &$session = null) { diff --git a/lib/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php index a7d7411ff..08901284c 100644 --- a/lib/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php +++ b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; // Help opcache.preload discover always-needed symbols class_exists(NativeSessionStorage::class); @@ -21,15 +22,15 @@ class_exists(NativeSessionStorage::class); */ class NativeSessionStorageFactory implements SessionStorageFactoryInterface { - private $options; - private $handler; - private $metaBag; - private $secure; + private array $options; + private AbstractProxy|\SessionHandlerInterface|null $handler; + private ?MetadataBag $metaBag; + private bool $secure; /** * @see NativeSessionStorage constructor. */ - public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null, bool $secure = false) + public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null, bool $secure = false) { $this->options = $options; $this->handler = $handler; @@ -40,7 +41,7 @@ class NativeSessionStorageFactory implements SessionStorageFactoryInterface public function createStorage(?Request $request): SessionStorageInterface { $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag); - if ($this->secure && $request && $request->isSecure()) { + if ($this->secure && $request?->isSecure()) { $storage->setOptions(['cookie_secure' => true]); } diff --git a/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php index 72dbef134..28cb3c3d0 100644 --- a/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -20,10 +20,7 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; */ class PhpBridgeSessionStorage extends NativeSessionStorage { - /** - * @param AbstractProxy|\SessionHandlerInterface|null $handler - */ - public function __construct($handler = null, MetadataBag $metaBag = null) + public function __construct(AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null) { if (!\extension_loaded('session')) { throw new \LogicException('PHP extension "session" is required.'); @@ -33,10 +30,7 @@ class PhpBridgeSessionStorage extends NativeSessionStorage $this->setSaveHandler($handler); } - /** - * {@inheritdoc} - */ - public function start() + public function start(): bool { if ($this->started) { return true; @@ -48,7 +42,7 @@ class PhpBridgeSessionStorage extends NativeSessionStorage } /** - * {@inheritdoc} + * @return void */ public function clear() { diff --git a/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php index 173ef71de..5cc738024 100644 --- a/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php +++ b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; // Help opcache.preload discover always-needed symbols class_exists(PhpBridgeSessionStorage::class); @@ -21,14 +22,11 @@ class_exists(PhpBridgeSessionStorage::class); */ class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface { - private $handler; - private $metaBag; - private $secure; + private AbstractProxy|\SessionHandlerInterface|null $handler; + private ?MetadataBag $metaBag; + private bool $secure; - /** - * @see PhpBridgeSessionStorage constructor. - */ - public function __construct($handler = null, MetadataBag $metaBag = null, bool $secure = false) + public function __construct(AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null, bool $secure = false) { $this->handler = $handler; $this->metaBag = $metaBag; @@ -38,7 +36,7 @@ class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface public function createStorage(?Request $request): SessionStorageInterface { $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag); - if ($this->secure && $request && $request->isSecure()) { + if ($this->secure && $request?->isSecure()) { $storage->setOptions(['cookie_secure' => true]); } diff --git a/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php index edd04dff8..2fcd06b10 100644 --- a/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php +++ b/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -30,50 +30,40 @@ abstract class AbstractProxy /** * Gets the session.save_handler name. - * - * @return string|null */ - public function getSaveHandlerName() + public function getSaveHandlerName(): ?string { return $this->saveHandlerName; } /** * Is this proxy handler and instance of \SessionHandlerInterface. - * - * @return bool */ - public function isSessionHandlerInterface() + public function isSessionHandlerInterface(): bool { return $this instanceof \SessionHandlerInterface; } /** * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. - * - * @return bool */ - public function isWrapper() + public function isWrapper(): bool { return $this->wrapper; } /** * Has a session started? - * - * @return bool */ - public function isActive() + public function isActive(): bool { return \PHP_SESSION_ACTIVE === session_status(); } /** * Gets the session ID. - * - * @return string */ - public function getId() + public function getId(): string { return session_id(); } @@ -81,6 +71,8 @@ abstract class AbstractProxy /** * Sets the session ID. * + * @return void + * * @throws \LogicException */ public function setId(string $id) @@ -94,10 +86,8 @@ abstract class AbstractProxy /** * Gets the session name. - * - * @return string */ - public function getName() + public function getName(): string { return session_name(); } @@ -105,6 +95,8 @@ abstract class AbstractProxy /** * Sets the session name. * + * @return void + * * @throws \LogicException */ public function setName(string $name) diff --git a/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php index 0defa4a7a..7bf3f9ff1 100644 --- a/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -27,84 +27,49 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf $this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user'; } - /** - * @return \SessionHandlerInterface - */ - public function getHandler() + public function getHandler(): \SessionHandlerInterface { return $this->handler; } // \SessionHandlerInterface - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function open($savePath, $sessionName) + public function open(string $savePath, string $sessionName): bool { return $this->handler->open($savePath, $sessionName); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function close() + public function close(): bool { return $this->handler->close(); } - /** - * @return string|false - */ - #[\ReturnTypeWillChange] - public function read($sessionId) + public function read(#[\SensitiveParameter] string $sessionId): string|false { return $this->handler->read($sessionId); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function write($sessionId, $data) + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler->write($sessionId, $data); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function destroy($sessionId) + public function destroy(#[\SensitiveParameter] string $sessionId): bool { return $this->handler->destroy($sessionId); } - /** - * @return int|false - */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { return $this->handler->gc($maxlifetime); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function validateId($sessionId) + public function validateId(#[\SensitiveParameter] string $sessionId): bool { return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); } - /** - * @return bool - */ - #[\ReturnTypeWillChange] - public function updateTimestamp($sessionId, $data) + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); } diff --git a/lib/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php b/lib/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php deleted file mode 100644 index d17c60aeb..000000000 --- a/lib/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\Session\Storage; - -use Symfony\Component\HttpFoundation\Request; - -/** - * @author Jérémy Derussé - * - * @internal to be removed in Symfony 6 - */ -final class ServiceSessionFactory implements SessionStorageFactoryInterface -{ - private $storage; - - public function __construct(SessionStorageInterface $storage) - { - $this->storage = $storage; - } - - public function createStorage(?Request $request): SessionStorageInterface - { - if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { - $this->storage->setOptions(['cookie_secure' => true]); - } - - return $this->storage; - } -} diff --git a/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php index b7f66e7c7..ed2189e4e 100644 --- a/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php +++ b/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -24,40 +24,36 @@ interface SessionStorageInterface /** * Starts the session. * - * @return bool - * * @throws \RuntimeException if something goes wrong starting the session */ - public function start(); + public function start(): bool; /** * Checks if the session is started. - * - * @return bool */ - public function isStarted(); + public function isStarted(): bool; /** * Returns the session ID. - * - * @return string */ - public function getId(); + public function getId(): string; /** * Sets the session ID. + * + * @return void */ public function setId(string $id); /** * Returns the session name. - * - * @return string */ - public function getName(); + public function getName(): string; /** * Sets the session name. + * + * @return void */ public function setName(string $name); @@ -80,17 +76,15 @@ interface SessionStorageInterface * Otherwise session data could get lost again for concurrent requests with the * new ID. One result could be that you get logged out after just logging in. * - * @param bool $destroy Destroy session when regenerating? - * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value - * will leave the system settings unchanged, 0 sets the cookie - * to expire with browser session. Time is in seconds, and is - * not a Unix timestamp. - * - * @return bool + * @param bool $destroy Destroy session when regenerating? + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. * * @throws \RuntimeException If an error occurs while regenerating this storage */ - public function regenerate(bool $destroy = false, int $lifetime = null); + public function regenerate(bool $destroy = false, int $lifetime = null): bool; /** * Force the session to be saved and closed. @@ -100,6 +94,8 @@ interface SessionStorageInterface * a real PHP session would interfere with testing, in which case * it should actually persist the session data if required. * + * @return void + * * @throws \RuntimeException if the session is saved without being started, or if the session * is already closed */ @@ -107,25 +103,24 @@ interface SessionStorageInterface /** * Clear all session data in memory. + * + * @return void */ public function clear(); /** * Gets a SessionBagInterface by name. * - * @return SessionBagInterface - * * @throws \InvalidArgumentException If the bag does not exist */ - public function getBag(string $name); + public function getBag(string $name): SessionBagInterface; /** * Registers a SessionBagInterface for use. + * + * @return void */ public function registerBag(SessionBagInterface $bag); - /** - * @return MetadataBag - */ - public function getMetadataBag(); + public function getMetadataBag(): MetadataBag; } diff --git a/lib/symfony/http-foundation/StreamedJsonResponse.php b/lib/symfony/http-foundation/StreamedJsonResponse.php new file mode 100644 index 000000000..5b20ce910 --- /dev/null +++ b/lib/symfony/http-foundation/StreamedJsonResponse.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedJsonResponse represents a streamed HTTP response for JSON. + * + * A StreamedJsonResponse uses a structure and generics to create an + * efficient resource-saving JSON response. + * + * It is recommended to use flush() function after a specific number of items to directly stream the data. + * + * @see flush() + * + * @author Alexander Schranz + * + * Example usage: + * + * function loadArticles(): \Generator + * // some streamed loading + * yield ['title' => 'Article 1']; + * yield ['title' => 'Article 2']; + * yield ['title' => 'Article 3']; + * // recommended to use flush() after every specific number of items + * }), + * + * $response = new StreamedJsonResponse( + * // json structure with generators in which will be streamed + * [ + * '_embedded' => [ + * 'articles' => loadArticles(), // any generator which you want to stream as list of data + * ], + * ], + * ); + */ +class StreamedJsonResponse extends StreamedResponse +{ + private const PLACEHOLDER = '__symfony_json__'; + + /** + * @param mixed[] $data JSON Data containing PHP generators which will be streamed as list of data or a Generator + * @param int $status The HTTP status code (200 "OK" by default) + * @param array $headers An array of HTTP headers + * @param int $encodingOptions Flags for the json_encode() function + */ + public function __construct( + private readonly iterable $data, + int $status = 200, + array $headers = [], + private int $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS, + ) { + parent::__construct($this->stream(...), $status, $headers); + + if (!$this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + } + + private function stream(): void + { + $jsonEncodingOptions = \JSON_THROW_ON_ERROR | $this->encodingOptions; + $keyEncodingOptions = $jsonEncodingOptions & ~\JSON_NUMERIC_CHECK; + + $this->streamData($this->data, $jsonEncodingOptions, $keyEncodingOptions); + } + + private function streamData(mixed $data, int $jsonEncodingOptions, int $keyEncodingOptions): void + { + if (\is_array($data)) { + $this->streamArray($data, $jsonEncodingOptions, $keyEncodingOptions); + + return; + } + + if (is_iterable($data) && !$data instanceof \JsonSerializable) { + $this->streamIterable($data, $jsonEncodingOptions, $keyEncodingOptions); + + return; + } + + echo json_encode($data, $jsonEncodingOptions); + } + + private function streamArray(array $data, int $jsonEncodingOptions, int $keyEncodingOptions): void + { + $generators = []; + + array_walk_recursive($data, function (&$item, $key) use (&$generators) { + if (self::PLACEHOLDER === $key) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $key; + } + + // generators should be used but for better DX all kind of Traversable and objects are supported + if (\is_object($item)) { + $generators[] = $item; + $item = self::PLACEHOLDER; + } elseif (self::PLACEHOLDER === $item) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $item; + } + }); + + $jsonParts = explode('"'.self::PLACEHOLDER.'"', json_encode($data, $jsonEncodingOptions)); + + foreach ($generators as $index => $generator) { + // send first and between parts of the structure + echo $jsonParts[$index]; + + $this->streamData($generator, $jsonEncodingOptions, $keyEncodingOptions); + } + + // send last part of the structure + echo $jsonParts[array_key_last($jsonParts)]; + } + + private function streamIterable(iterable $iterable, int $jsonEncodingOptions, int $keyEncodingOptions): void + { + $isFirstItem = true; + $startTag = '['; + + foreach ($iterable as $key => $item) { + if ($isFirstItem) { + $isFirstItem = false; + // depending on the first elements key the generator is detected as a list or map + // we can not check for a whole list or map because that would hurt the performance + // of the streamed response which is the main goal of this response class + if (0 !== $key) { + $startTag = '{'; + } + + echo $startTag; + } else { + // if not first element of the generic, a separator is required between the elements + echo ','; + } + + if ('{' === $startTag) { + echo json_encode((string) $key, $keyEncodingOptions).':'; + } + + $this->streamData($item, $jsonEncodingOptions, $keyEncodingOptions); + } + + if ($isFirstItem) { // indicates that the generator was empty + echo '['; + } + + echo '[' === $startTag ? ']' : '}'; + } +} diff --git a/lib/symfony/http-foundation/StreamedResponse.php b/lib/symfony/http-foundation/StreamedResponse.php index 676cd6687..87be96a11 100644 --- a/lib/symfony/http-foundation/StreamedResponse.php +++ b/lib/symfony/http-foundation/StreamedResponse.php @@ -28,8 +28,11 @@ class StreamedResponse extends Response { protected $callback; protected $streamed; - private $headersSent; + private bool $headersSent; + /** + * @param int $status The HTTP status code (200 "OK" by default) + */ public function __construct(callable $callback = null, int $status = 200, array $headers = []) { parent::__construct(null, $status, $headers); @@ -41,60 +44,54 @@ class StreamedResponse extends Response $this->headersSent = false; } - /** - * Factory method for chainability. - * - * @param callable|null $callback A valid PHP callback or null to set it later - * - * @return static - * - * @deprecated since Symfony 5.1, use __construct() instead. - */ - public static function create($callback = null, int $status = 200, array $headers = []) - { - trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); - - return new static($callback, $status, $headers); - } - /** * Sets the PHP callback associated with this Response. * * @return $this */ - public function setCallback(callable $callback) + public function setCallback(callable $callback): static { - $this->callback = $callback; + $this->callback = $callback(...); return $this; } + public function getCallback(): ?\Closure + { + if (!isset($this->callback)) { + return null; + } + + return ($this->callback)(...); + } + /** - * {@inheritdoc} - * * This method only sends the headers once. * + * @param positive-int|null $statusCode The status code to use, override the statusCode property if set and not null + * * @return $this */ - public function sendHeaders() + public function sendHeaders(/* int $statusCode = null */): static { if ($this->headersSent) { return $this; } - $this->headersSent = true; + $statusCode = \func_num_args() > 0 ? func_get_arg(0) : null; + if ($statusCode < 100 || $statusCode >= 200) { + $this->headersSent = true; + } - return parent::sendHeaders(); + return parent::sendHeaders($statusCode); } /** - * {@inheritdoc} - * * This method only sends the content once. * * @return $this */ - public function sendContent() + public function sendContent(): static { if ($this->streamed) { return $this; @@ -102,8 +99,8 @@ class StreamedResponse extends Response $this->streamed = true; - if (null === $this->callback) { - throw new \LogicException('The Response callback must not be null.'); + if (!isset($this->callback)) { + throw new \LogicException('The Response callback must be set.'); } ($this->callback)(); @@ -112,13 +109,11 @@ class StreamedResponse extends Response } /** - * {@inheritdoc} + * @return $this * * @throws \LogicException when the content is not null - * - * @return $this */ - public function setContent(?string $content) + public function setContent(?string $content): static { if (null !== $content) { throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); @@ -129,10 +124,7 @@ class StreamedResponse extends Response return $this; } - /** - * {@inheritdoc} - */ - public function getContent() + public function getContent(): string|false { return false; } diff --git a/lib/symfony/http-foundation/UriSigner.php b/lib/symfony/http-foundation/UriSigner.php new file mode 100644 index 000000000..b04987724 --- /dev/null +++ b/lib/symfony/http-foundation/UriSigner.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * @author Fabien Potencier + */ +class UriSigner +{ + private string $secret; + private string $parameter; + + /** + * @param string $parameter Query string parameter to use + */ + public function __construct(#[\SensitiveParameter] string $secret, string $parameter = '_hash') + { + if (!$secret) { + throw new \InvalidArgumentException('A non-empty secret is required.'); + } + + $this->secret = $secret; + $this->parameter = $parameter; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding the query string parameter + * which value depends on the URI and the secret. + */ + public function sign(string $uri): string + { + $url = parse_url($uri); + $params = []; + + if (isset($url['query'])) { + parse_str($url['query'], $params); + } + + $uri = $this->buildUrl($url, $params); + $params[$this->parameter] = $this->computeHash($uri); + + return $this->buildUrl($url, $params); + } + + /** + * Checks that a URI contains the correct hash. + */ + public function check(string $uri): bool + { + $url = parse_url($uri); + $params = []; + + if (isset($url['query'])) { + parse_str($url['query'], $params); + } + + if (empty($params[$this->parameter])) { + return false; + } + + $hash = $params[$this->parameter]; + unset($params[$this->parameter]); + + return hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash); + } + + public function checkRequest(Request $request): bool + { + $qs = ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''; + + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + return $this->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$qs); + } + + private function computeHash(string $uri): string + { + return base64_encode(hash_hmac('sha256', $uri, $this->secret, true)); + } + + private function buildUrl(array $url, array $params = []): string + { + ksort($params, \SORT_STRING); + $url['query'] = http_build_query($params, '', '&'); + + $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; + $host = $url['host'] ?? ''; + $port = isset($url['port']) ? ':'.$url['port'] : ''; + $user = $url['user'] ?? ''; + $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = $url['path'] ?? ''; + $query = $url['query'] ? '?'.$url['query'] : ''; + $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; + + return $scheme.$user.$pass.$host.$port.$path.$query.$fragment; + } +} + +if (!class_exists(\Symfony\Component\HttpKernel\UriSigner::class, false)) { + class_alias(UriSigner::class, \Symfony\Component\HttpKernel\UriSigner::class); +} diff --git a/lib/symfony/http-foundation/UrlHelper.php b/lib/symfony/http-foundation/UrlHelper.php index c15f101cd..f971cf662 100644 --- a/lib/symfony/http-foundation/UrlHelper.php +++ b/lib/symfony/http-foundation/UrlHelper.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; /** * A helper service for manipulating URLs within and outside the request scope. @@ -20,18 +21,15 @@ use Symfony\Component\Routing\RequestContext; */ final class UrlHelper { - private $requestStack; - private $requestContext; - - public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) - { - $this->requestStack = $requestStack; - $this->requestContext = $requestContext; + public function __construct( + private RequestStack $requestStack, + private RequestContextAwareInterface|RequestContext|null $requestContext = null, + ) { } public function getAbsoluteUrl(string $path): string { - if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + if (str_contains($path, '://') || str_starts_with($path, '//')) { return $path; } @@ -60,7 +58,7 @@ final class UrlHelper public function getRelativePath(string $path): string { - if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + if (str_contains($path, '://') || str_starts_with($path, '//')) { return $path; } @@ -73,28 +71,36 @@ final class UrlHelper private function getAbsoluteUrlFromContext(string $path): string { - if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) { + if (null === $context = $this->requestContext) { return $path; } - $scheme = $this->requestContext->getScheme(); + if ($context instanceof RequestContextAwareInterface) { + $context = $context->getContext(); + } + + if ('' === $host = $context->getHost()) { + return $path; + } + + $scheme = $context->getScheme(); $port = ''; - if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) { - $port = ':'.$this->requestContext->getHttpPort(); - } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) { - $port = ':'.$this->requestContext->getHttpsPort(); + if ('http' === $scheme && 80 !== $context->getHttpPort()) { + $port = ':'.$context->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) { + $port = ':'.$context->getHttpsPort(); } if ('#' === $path[0]) { - $queryString = $this->requestContext->getQueryString(); - $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + $queryString = $context->getQueryString(); + $path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path; } elseif ('?' === $path[0]) { - $path = $this->requestContext->getPathInfo().$path; + $path = $context->getPathInfo().$path; } if ('/' !== $path[0]) { - $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + $path = rtrim($context->getBaseUrl(), '/').'/'.$path; } return $scheme.'://'.$host.$port.$path; diff --git a/lib/symfony/http-foundation/composer.json b/lib/symfony/http-foundation/composer.json index cb8d59ffe..be85696e5 100644 --- a/lib/symfony/http-foundation/composer.json +++ b/lib/symfony/http-foundation/composer.json @@ -16,22 +16,23 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-php83": "^1.27" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0" + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.3|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" }, - "suggest" : { - "symfony/mime": "To use the file extension guesser" + "conflict": { + "symfony/cache": "<6.3" }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, diff --git a/lib/symfony/http-kernel/Attribute/AsController.php b/lib/symfony/http-kernel/Attribute/AsController.php index ef3710451..0f2c91d45 100644 --- a/lib/symfony/http-kernel/Attribute/AsController.php +++ b/lib/symfony/http-kernel/Attribute/AsController.php @@ -12,9 +12,13 @@ namespace Symfony\Component\HttpKernel\Attribute; /** - * Service tag to autoconfigure controllers. + * Autoconfigures controllers as services by applying + * the `controller.service_arguments` tag to them. + * + * This enables injecting services as method arguments in addition + * to other conventional dependency injection strategies. */ -#[\Attribute(\Attribute::TARGET_CLASS)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_FUNCTION)] class AsController { public function __construct() diff --git a/lib/symfony/http-kernel/Attribute/ArgumentInterface.php b/lib/symfony/http-kernel/Attribute/AsTargetedValueResolver.php similarity index 56% rename from lib/symfony/http-kernel/Attribute/ArgumentInterface.php rename to lib/symfony/http-kernel/Attribute/AsTargetedValueResolver.php index 78769f1ac..c58f0e6dd 100644 --- a/lib/symfony/http-kernel/Attribute/ArgumentInterface.php +++ b/lib/symfony/http-kernel/Attribute/AsTargetedValueResolver.php @@ -11,13 +11,14 @@ namespace Symfony\Component\HttpKernel\Attribute; -trigger_deprecation('symfony/http-kernel', '5.3', 'The "%s" interface is deprecated.', ArgumentInterface::class); - /** - * Marker interface for controller argument attributes. - * - * @deprecated since Symfony 5.3 + * Service tag to autoconfigure targeted value resolvers. */ -interface ArgumentInterface +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsTargetedValueResolver { + public function __construct( + public readonly ?string $name = null, + ) { + } } diff --git a/lib/symfony/http-kernel/Attribute/Cache.php b/lib/symfony/http-kernel/Attribute/Cache.php new file mode 100644 index 000000000..19d13e922 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/Cache.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * Describes the default HTTP cache headers on controllers. + * Headers defined in the Cache attribute are ignored if they are already set + * by the controller. + * + * @see https://symfony.com/doc/current/http_cache.html#making-your-responses-http-cacheable + * + * @author Fabien Potencier + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +final class Cache +{ + public function __construct( + /** + * The expiration date as a valid date for the strtotime() function. + */ + public ?string $expires = null, + + /** + * The number of seconds that the response is considered fresh by a private + * cache like a web browser. + */ + public int|string|null $maxage = null, + + /** + * The number of seconds that the response is considered fresh by a public + * cache like a reverse proxy cache. + */ + public int|string|null $smaxage = null, + + /** + * If true, the contents will be stored in a public cache and served to all + * the next requests. + */ + public ?bool $public = null, + + /** + * If true, the response is not served stale by a cache in any circumstance + * without first revalidating with the origin. + */ + public bool $mustRevalidate = false, + + /** + * Set "Vary" header. + * + * Example: + * ['Accept-Encoding', 'User-Agent'] + * + * @see https://symfony.com/doc/current/http_cache/cache_vary.html + * + * @var string[] + */ + public array $vary = [], + + /** + * An expression to compute the Last-Modified HTTP header. + * + * The expression is evaluated by the ExpressionLanguage component, it + * receives all the request attributes and the resolved controller arguments. + * + * The result of the expression must be a DateTimeInterface. + */ + public ?string $lastModified = null, + + /** + * An expression to compute the ETag HTTP header. + * + * The expression is evaluated by the ExpressionLanguage component, it + * receives all the request attributes and the resolved controller arguments. + * + * The result must be a string that will be hashed. + */ + public ?string $etag = null, + + /** + * max-stale Cache-Control header + * It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...). + */ + public int|string|null $maxStale = null, + + /** + * stale-while-revalidate Cache-Control header + * It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...). + */ + public int|string|null $staleWhileRevalidate = null, + + /** + * stale-if-error Cache-Control header + * It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...). + */ + public int|string|null $staleIfError = null, + ) { + } +} diff --git a/lib/symfony/http-kernel/Attribute/MapDateTime.php b/lib/symfony/http-kernel/Attribute/MapDateTime.php new file mode 100644 index 000000000..bfe48a809 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/MapDateTime.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; + +/** + * Controller parameter tag to configure DateTime arguments. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapDateTime extends ValueResolver +{ + public function __construct( + public readonly ?string $format = null, + bool $disabled = false, + string $resolver = DateTimeValueResolver::class, + ) { + parent::__construct($resolver, $disabled); + } +} diff --git a/lib/symfony/http-kernel/Attribute/MapQueryParameter.php b/lib/symfony/http-kernel/Attribute/MapQueryParameter.php new file mode 100644 index 000000000..f83e331e4 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/MapQueryParameter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver; + +/** + * Can be used to pass a query parameter to a controller argument. + * + * @author Ruud Kamphuis + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +final class MapQueryParameter extends ValueResolver +{ + /** + * @see https://php.net/filter.filters.validate for filter, flags and options + * + * @param string|null $name The name of the query parameter. If null, the name of the argument in the controller will be used. + */ + public function __construct( + public ?string $name = null, + public ?int $filter = null, + public int $flags = 0, + public array $options = [], + string $resolver = QueryParameterValueResolver::class, + ) { + parent::__construct($resolver); + } +} diff --git a/lib/symfony/http-kernel/Attribute/MapQueryString.php b/lib/symfony/http-kernel/Attribute/MapQueryString.php new file mode 100644 index 000000000..83722266e --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/MapQueryString.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Controller parameter tag to map the query string of the request to typed object and validate it. + * + * @author Konstantin Myakshin + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapQueryString extends ValueResolver +{ + public ArgumentMetadata $metadata; + + public function __construct( + public readonly array $serializationContext = [], + public readonly string|GroupSequence|array|null $validationGroups = null, + string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_NOT_FOUND, + ) { + parent::__construct($resolver); + } +} diff --git a/lib/symfony/http-kernel/Attribute/MapRequestPayload.php b/lib/symfony/http-kernel/Attribute/MapRequestPayload.php new file mode 100644 index 000000000..cbac606e8 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/MapRequestPayload.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Controller parameter tag to map the request content to typed object and validate it. + * + * @author Konstantin Myakshin + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapRequestPayload extends ValueResolver +{ + public ArgumentMetadata $metadata; + + public function __construct( + public readonly array|string|null $acceptFormat = null, + public readonly array $serializationContext = [], + public readonly string|GroupSequence|array|null $validationGroups = null, + string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY, + ) { + parent::__construct($resolver); + } +} diff --git a/lib/symfony/http-kernel/Attribute/ValueResolver.php b/lib/symfony/http-kernel/Attribute/ValueResolver.php new file mode 100644 index 000000000..5875a2748 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/ValueResolver.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; + +#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)] +class ValueResolver +{ + /** + * @param class-string|string $resolver + */ + public function __construct( + public string $resolver, + public bool $disabled = false, + ) { + } +} diff --git a/lib/symfony/http-kernel/Attribute/WithHttpStatus.php b/lib/symfony/http-kernel/Attribute/WithHttpStatus.php new file mode 100644 index 000000000..718427aac --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/WithHttpStatus.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class WithHttpStatus +{ + /** + * @param array $headers + */ + public function __construct( + public readonly int $statusCode, + public readonly array $headers = [], + ) { + } +} diff --git a/lib/symfony/http-kernel/Attribute/WithLogLevel.php b/lib/symfony/http-kernel/Attribute/WithLogLevel.php new file mode 100644 index 000000000..762b07704 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/WithLogLevel.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Psr\Log\LogLevel; + +/** + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class WithLogLevel +{ + /** + * @param LogLevel::* $level + */ + public function __construct(public readonly string $level) + { + if (!\defined('Psr\Log\LogLevel::'.strtoupper($this->level))) { + throw new \InvalidArgumentException(sprintf('Invalid log level "%s".', $this->level)); + } + } +} diff --git a/lib/symfony/http-kernel/Bundle/AbstractBundle.php b/lib/symfony/http-kernel/Bundle/AbstractBundle.php new file mode 100644 index 000000000..d24a2bc78 --- /dev/null +++ b/lib/symfony/http-kernel/Bundle/AbstractBundle.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurableExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * A Bundle that provides configuration hooks. + * + * @author Yonel Ceruto + */ +abstract class AbstractBundle extends Bundle implements ConfigurableExtensionInterface +{ + protected string $extensionAlias = ''; + + public function configure(DefinitionConfigurator $definition): void + { + } + + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function getContainerExtension(): ?ExtensionInterface + { + if ('' === $this->extensionAlias) { + $this->extensionAlias = Container::underscore(preg_replace('/Bundle$/', '', $this->getName())); + } + + return $this->extension ??= new BundleExtension($this, $this->extensionAlias); + } + + public function getPath(): string + { + if (null === $this->path) { + $reflected = new \ReflectionObject($this); + // assume the modern directory structure by default + $this->path = \dirname($reflected->getFileName(), 2); + } + + return $this->path; + } +} diff --git a/lib/symfony/http-kernel/Bundle/Bundle.php b/lib/symfony/http-kernel/Bundle/Bundle.php index 54a1d10b9..09a19c480 100644 --- a/lib/symfony/http-kernel/Bundle/Bundle.php +++ b/lib/symfony/http-kernel/Bundle/Bundle.php @@ -13,8 +13,8 @@ namespace Symfony\Component\HttpKernel\Bundle; use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; /** @@ -24,32 +24,35 @@ use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; */ abstract class Bundle implements BundleInterface { - use ContainerAwareTrait; - protected $name; protected $extension; protected $path; - private $namespace; + private string $namespace; /** - * {@inheritdoc} + * @var ContainerInterface|null + */ + protected $container; + + /** + * @return void */ public function boot() { } /** - * {@inheritdoc} + * @return void */ public function shutdown() { } /** - * {@inheritdoc} - * * This method can be overridden to register compilation passes, * other extensions, ... + * + * @return void */ public function build(ContainerBuilder $container) { @@ -58,13 +61,11 @@ abstract class Bundle implements BundleInterface /** * Returns the bundle's container extension. * - * @return ExtensionInterface|null - * * @throws \LogicException */ - public function getContainerExtension() + public function getContainerExtension(): ?ExtensionInterface { - if (null === $this->extension) { + if (!isset($this->extension)) { $extension = $this->createContainerExtension(); if (null !== $extension) { @@ -89,24 +90,18 @@ abstract class Bundle implements BundleInterface return $this->extension ?: null; } - /** - * {@inheritdoc} - */ - public function getNamespace() + public function getNamespace(): string { - if (null === $this->namespace) { + if (!isset($this->namespace)) { $this->parseClassName(); } return $this->namespace; } - /** - * {@inheritdoc} - */ - public function getPath() + public function getPath(): string { - if (null === $this->path) { + if (!isset($this->path)) { $reflected = new \ReflectionObject($this); $this->path = \dirname($reflected->getFileName()); } @@ -119,23 +114,24 @@ abstract class Bundle implements BundleInterface */ final public function getName(): string { - if (null === $this->name) { + if (!isset($this->name)) { $this->parseClassName(); } return $this->name; } + /** + * @return void + */ public function registerCommands(Application $application) { } /** * Returns the bundle's container extension class. - * - * @return string */ - protected function getContainerExtensionClass() + protected function getContainerExtensionClass(): string { $basename = preg_replace('/Bundle$/', '', $this->getName()); @@ -144,20 +140,21 @@ abstract class Bundle implements BundleInterface /** * Creates the bundle's container extension. - * - * @return ExtensionInterface|null */ - protected function createContainerExtension() + protected function createContainerExtension(): ?ExtensionInterface { return class_exists($class = $this->getContainerExtensionClass()) ? new $class() : null; } - private function parseClassName() + private function parseClassName(): void { $pos = strrpos(static::class, '\\'); $this->namespace = false === $pos ? '' : substr(static::class, 0, $pos); - if (null === $this->name) { - $this->name = false === $pos ? static::class : substr(static::class, $pos + 1); - } + $this->name ??= false === $pos ? static::class : substr(static::class, $pos + 1); + } + + public function setContainer(?ContainerInterface $container): void + { + $this->container = $container; } } diff --git a/lib/symfony/http-kernel/Bundle/BundleExtension.php b/lib/symfony/http-kernel/Bundle/BundleExtension.php new file mode 100644 index 000000000..b80bc21f2 --- /dev/null +++ b/lib/symfony/http-kernel/Bundle/BundleExtension.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\Config\Definition\Configuration; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurableExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Extension\ExtensionTrait; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * @author Yonel Ceruto + * + * @internal + */ +class BundleExtension extends Extension implements PrependExtensionInterface +{ + use ExtensionTrait; + + public function __construct( + private ConfigurableExtensionInterface $subject, + private string $alias, + ) { + } + + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + { + return new Configuration($this->subject, $container, $this->getAlias()); + } + + public function getAlias(): string + { + return $this->alias; + } + + public function prepend(ContainerBuilder $container): void + { + $callback = function (ContainerConfigurator $configurator) use ($container) { + $this->subject->prependExtension($configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this->subject); + } + + public function load(array $configs, ContainerBuilder $container): void + { + $config = $this->processConfiguration($this->getConfiguration([], $container), $configs); + + $callback = function (ContainerConfigurator $configurator) use ($config, $container) { + $this->subject->loadExtension($config, $configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this->subject); + } +} diff --git a/lib/symfony/http-kernel/Bundle/BundleInterface.php b/lib/symfony/http-kernel/Bundle/BundleInterface.php index fdc13e0c8..400a9e0c9 100644 --- a/lib/symfony/http-kernel/Bundle/BundleInterface.php +++ b/lib/symfony/http-kernel/Bundle/BundleInterface.php @@ -11,8 +11,8 @@ namespace Symfony\Component\HttpKernel\Bundle; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; /** @@ -20,15 +20,19 @@ use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; * * @author Fabien Potencier */ -interface BundleInterface extends ContainerAwareInterface +interface BundleInterface { /** * Boots the Bundle. + * + * @return void */ public function boot(); /** * Shutdowns the Bundle. + * + * @return void */ public function shutdown(); @@ -36,36 +40,35 @@ interface BundleInterface extends ContainerAwareInterface * Builds the bundle. * * It is only ever called once when the cache is empty. + * + * @return void */ public function build(ContainerBuilder $container); /** * Returns the container extension that should be implicitly loaded. - * - * @return ExtensionInterface|null */ - public function getContainerExtension(); + public function getContainerExtension(): ?ExtensionInterface; /** * Returns the bundle name (the class short name). - * - * @return string */ - public function getName(); + public function getName(): string; /** * Gets the Bundle namespace. - * - * @return string */ - public function getNamespace(); + public function getNamespace(): string; /** * Gets the Bundle directory path. * * The path should always be returned as a Unix path (with /). - * - * @return string */ - public function getPath(); + public function getPath(): string; + + /** + * @return void + */ + public function setContainer(?ContainerInterface $container); } diff --git a/lib/symfony/http-kernel/CHANGELOG.md b/lib/symfony/http-kernel/CHANGELOG.md index d0dc2076c..c1743b1d1 100644 --- a/lib/symfony/http-kernel/CHANGELOG.md +++ b/lib/symfony/http-kernel/CHANGELOG.md @@ -1,6 +1,71 @@ CHANGELOG ========= +6.4 +--- + + * Support backed enums in #[MapQueryParameter] + * `BundleInterface` no longer extends `ContainerAwareInterface` + * Add optional `$className` parameter to `ControllerEvent::getAttributes()` + * Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass` + * Add argument `$validationFailedStatusCode` to `#[MapQueryString]` and `#[MapRequestPayload]` + * Add argument `$debug` to `Logger` + * Add class `DebugLoggerConfigurator` + * Add parameters `kernel.runtime_mode` and `kernel.runtime_mode.*`, all set from env var `APP_RUNTIME_MODE` + * Deprecate `Kernel::stripComments()` + * Support the `!` character at the beginning of a string as a negation operator in the url filter of the profiler + * Deprecate `UriSigner`, use `UriSigner` from the HttpFoundation component instead + * Deprecate `FileLinkFormatter`, use `FileLinkFormatter` from the ErrorHandler component instead + * Add argument `$buildDir` to `WarmableInterface` + * Add argument `$filter` to `Profiler::find()` and `FileProfilerStorage::find()` + * Add `ControllerResolver::allowControllers()` to define which callables are legit controllers when the `_check_controller_is_allowed` request attribute is set + +6.3 +--- + + * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead + * `FileProfilerStorage` removes profiles automatically after two days + * Add `#[WithHttpStatus]` for defining status codes for exceptions + * Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver` + * Add `#[WithLogLevel]` for defining log levels for exceptions + * Add `skip_response_headers` to the `HttpCache` options + * Introduce targeted value resolvers with `#[ValueResolver]` and `#[AsTargetedValueResolver]` + * Add `#[MapRequestPayload]` to map and validate request payload from `Request::getContent()` or `Request::$request->all()` to typed objects + * Add `#[MapQueryString]` to map and validate request query string from `Request::$query->all()` to typed objects + * Add `#[MapQueryParameter]` to map and validate individual query parameters to controller arguments + * Collect data from every event dispatcher + +6.2 +--- + + * Add constructor argument `bool $handleAllThrowable` to `HttpKernel` + * Add `ControllerEvent::getAttributes()` to handle attributes on controllers + * Add `#[Cache]` to describe the default HTTP cache headers on controllers + * Add `absolute_uri` option to surrogate fragment renderers + * Add `ValueResolverInterface` and deprecate `ArgumentValueResolverInterface` + * Add argument `$reflector` to `ArgumentResolverInterface` and `ArgumentMetadataFactoryInterface` + * Deprecate calling `ConfigDataCollector::setKernel()`, `RouterListener::setCurrentRequest()` without arguments + +6.1 +--- + + * Add `BackedEnumValueResolver` to resolve backed enum cases from request attributes in controller arguments + * Add `DateTimeValueResolver` to resolve request attributes into DateTime objects in controller arguments + * Deprecate StreamedResponseListener, it's not needed anymore + * Add `Profiler::isEnabled()` so collaborating collector services may elect to omit themselves + * Add the `UidValueResolver` argument value resolver + * Add `AbstractBundle` class for DI configuration/definition on a single file + * Update the path of a bundle placed in the `src/` directory to the parent directory when `AbstractBundle` is used + +6.0 +--- + + * Remove `ArgumentInterface` + * Remove `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead + * Remove support for returning a `ContainerBuilder` from `KernelInterface::registerContainerConfiguration()` + * Remove `KernelEvent::isMasterRequest()`, use `isMainRequest()` instead + * Remove support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead + 5.4 --- diff --git a/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php b/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php index 270f690e5..5ca426562 100644 --- a/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php +++ b/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php @@ -20,6 +20,8 @@ interface CacheClearerInterface { /** * Clears any caches necessary. + * + * @return void */ public function clear(string $cacheDir); } diff --git a/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php b/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php index a875d899d..0c541f21b 100644 --- a/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php +++ b/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php @@ -20,7 +20,7 @@ namespace Symfony\Component\HttpKernel\CacheClearer; */ class ChainCacheClearer implements CacheClearerInterface { - private $clearers; + private iterable $clearers; /** * @param iterable $clearers @@ -30,10 +30,7 @@ class ChainCacheClearer implements CacheClearerInterface $this->clearers = $clearers; } - /** - * {@inheritdoc} - */ - public function clear(string $cacheDir) + public function clear(string $cacheDir): void { foreach ($this->clearers as $clearer) { $clearer->clear($cacheDir); diff --git a/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php b/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php index a074060e4..3c99b74af 100644 --- a/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php +++ b/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php @@ -18,7 +18,7 @@ use Psr\Cache\CacheItemPoolInterface; */ class Psr6CacheClearer implements CacheClearerInterface { - private $pools = []; + private array $pools = []; /** * @param array $pools @@ -28,20 +28,15 @@ class Psr6CacheClearer implements CacheClearerInterface $this->pools = $pools; } - /** - * @return bool - */ - public function hasPool(string $name) + public function hasPool(string $name): bool { return isset($this->pools[$name]); } /** - * @return CacheItemPoolInterface - * * @throws \InvalidArgumentException If the cache pool with the given name does not exist */ - public function getPool(string $name) + public function getPool(string $name): CacheItemPoolInterface { if (!$this->hasPool($name)) { throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name)); @@ -51,11 +46,9 @@ class Psr6CacheClearer implements CacheClearerInterface } /** - * @return bool - * * @throws \InvalidArgumentException If the cache pool with the given name does not exist */ - public function clearPool(string $name) + public function clearPool(string $name): bool { if (!isset($this->pools[$name])) { throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name)); @@ -65,7 +58,7 @@ class Psr6CacheClearer implements CacheClearerInterface } /** - * {@inheritdoc} + * @return void */ public function clear(string $cacheDir) { diff --git a/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php b/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php index aef42d62f..f940ba4a7 100644 --- a/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php +++ b/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php @@ -18,6 +18,9 @@ namespace Symfony\Component\HttpKernel\CacheWarmer; */ abstract class CacheWarmer implements CacheWarmerInterface { + /** + * @return void + */ protected function writeCacheFile(string $file, $content) { $tmpFile = @tempnam(\dirname($file), basename($file)); diff --git a/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php b/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php index 67f9ed50b..a672956e0 100644 --- a/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php +++ b/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel\CacheWarmer; +use Symfony\Component\Console\Style\SymfonyStyle; + /** * Aggregates several cache warmers into a single one. * @@ -20,11 +22,11 @@ namespace Symfony\Component\HttpKernel\CacheWarmer; */ class CacheWarmerAggregate implements CacheWarmerInterface { - private $warmers; - private $debug; - private $deprecationLogsFilepath; - private $optionalsEnabled = false; - private $onlyOptionalsEnabled = false; + private iterable $warmers; + private bool $debug; + private ?string $deprecationLogsFilepath; + private bool $optionalsEnabled = false; + private bool $onlyOptionalsEnabled = false; /** * @param iterable $warmers @@ -36,21 +38,27 @@ class CacheWarmerAggregate implements CacheWarmerInterface $this->deprecationLogsFilepath = $deprecationLogsFilepath; } - public function enableOptionalWarmers() + public function enableOptionalWarmers(): void { $this->optionalsEnabled = true; } - public function enableOnlyOptionalWarmers() + public function enableOnlyOptionalWarmers(): void { $this->onlyOptionalsEnabled = $this->optionalsEnabled = true; } /** - * {@inheritdoc} + * @param string|null $buildDir */ - public function warmUp(string $cacheDir): array + public function warmUp(string $cacheDir, string|SymfonyStyle $buildDir = null, SymfonyStyle $io = null): array { + if ($buildDir instanceof SymfonyStyle) { + trigger_deprecation('symfony/http-kernel', '6.4', 'Passing a "%s" as second argument of "%s()" is deprecated, pass it as third argument instead, after the build directory.', SymfonyStyle::class, __METHOD__); + $io = $buildDir; + $buildDir = null; + } + if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { $collectedLogs = []; $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { @@ -96,7 +104,17 @@ class CacheWarmerAggregate implements CacheWarmerInterface continue; } - $preload[] = array_values((array) $warmer->warmUp($cacheDir)); + $start = microtime(true); + foreach ((array) $warmer->warmUp($cacheDir, $buildDir) as $item) { + if (is_dir($item) || (str_starts_with($item, \dirname($cacheDir)) && !is_file($item)) || ($buildDir && str_starts_with($item, \dirname($buildDir)) && !is_file($item))) { + throw new \LogicException(sprintf('"%s::warmUp()" should return a list of files or classes but "%s" is none of them.', $warmer::class, $item)); + } + $preload[] = $item; + } + + if ($io?->isDebug()) { + $io->info(sprintf('"%s" completed in %0.2fms.', $warmer::class, 1000 * (microtime(true) - $start))); + } } } finally { if ($collectDeprecations) { @@ -113,12 +131,9 @@ class CacheWarmerAggregate implements CacheWarmerInterface } } - return array_values(array_unique(array_merge([], ...$preload))); + return array_values(array_unique($preload)); } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return false; diff --git a/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php b/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php index 2f442cb53..cd051b1ad 100644 --- a/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php +++ b/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php @@ -21,7 +21,10 @@ interface WarmableInterface /** * Warms up the cache. * + * @param string $cacheDir Where warm-up artifacts should be stored + * @param string|null $buildDir Where read-only artifacts should go; null when called after compile-time + * * @return string[] A list of classes or files to preload on PHP 7.4+ */ - public function warmUp(string $cacheDir); + public function warmUp(string $cacheDir /* , string $buildDir = null */); } diff --git a/lib/symfony/http-kernel/Config/FileLocator.php b/lib/symfony/http-kernel/Config/FileLocator.php index 6eca98635..f81f91925 100644 --- a/lib/symfony/http-kernel/Config/FileLocator.php +++ b/lib/symfony/http-kernel/Config/FileLocator.php @@ -21,7 +21,7 @@ use Symfony\Component\HttpKernel\KernelInterface; */ class FileLocator extends BaseFileLocator { - private $kernel; + private KernelInterface $kernel; public function __construct(KernelInterface $kernel) { @@ -30,10 +30,7 @@ class FileLocator extends BaseFileLocator parent::__construct(); } - /** - * {@inheritdoc} - */ - public function locate(string $file, string $currentPath = null, bool $first = true) + public function locate(string $file, string $currentPath = null, bool $first = true): string|array { if (isset($file[0]) && '@' === $file[0]) { $resource = $this->kernel->locateResource($file); diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver.php index a54140b7e..3b0f89509 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver.php @@ -11,14 +11,19 @@ namespace Symfony\Component\HttpKernel\Controller; +use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\ValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; +use Symfony\Component\HttpKernel\Exception\ResolverNotFoundException; +use Symfony\Contracts\Service\ServiceProviderInterface; /** * Responsible for resolving the arguments passed to an action. @@ -27,56 +32,81 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInter */ final class ArgumentResolver implements ArgumentResolverInterface { - private $argumentMetadataFactory; - private $argumentValueResolvers; + private ArgumentMetadataFactoryInterface $argumentMetadataFactory; + private iterable $argumentValueResolvers; + private ?ContainerInterface $namedResolvers; /** - * @param iterable $argumentValueResolvers + * @param iterable $argumentValueResolvers */ - public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = []) + public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [], ContainerInterface $namedResolvers = null) { $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory(); $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); + $this->namedResolvers = $namedResolvers; } - /** - * {@inheritdoc} - */ - public function getArguments(Request $request, callable $controller): array + public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array { $arguments = []; - foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) { - foreach ($this->argumentValueResolvers as $resolver) { - if (!$resolver->supports($request, $metadata)) { + foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, $reflector) as $metadata) { + $argumentValueResolvers = $this->argumentValueResolvers; + $disabledResolvers = []; + + if ($this->namedResolvers && $attributes = $metadata->getAttributesOfType(ValueResolver::class, $metadata::IS_INSTANCEOF)) { + $resolverName = null; + foreach ($attributes as $attribute) { + if ($attribute->disabled) { + $disabledResolvers[$attribute->resolver] = true; + } elseif ($resolverName) { + throw new \LogicException(sprintf('You can only pin one resolver per argument, but argument "$%s" of "%s()" has more.', $metadata->getName(), $this->getPrettyName($controller))); + } else { + $resolverName = $attribute->resolver; + } + } + + if ($resolverName) { + if (!$this->namedResolvers->has($resolverName)) { + throw new ResolverNotFoundException($resolverName, $this->namedResolvers instanceof ServiceProviderInterface ? array_keys($this->namedResolvers->getProvidedServices()) : []); + } + + $argumentValueResolvers = [ + $this->namedResolvers->get($resolverName), + new DefaultValueResolver(), + ]; + } + } + + foreach ($argumentValueResolvers as $name => $resolver) { + if ((!$resolver instanceof ValueResolverInterface || $resolver instanceof TraceableValueResolver) && !$resolver->supports($request, $metadata)) { + continue; + } + if (isset($disabledResolvers[\is_int($name) ? $resolver::class : $name])) { continue; } - $resolved = $resolver->resolve($request, $metadata); - - $atLeastOne = false; - foreach ($resolved as $append) { - $atLeastOne = true; - $arguments[] = $append; + $count = 0; + foreach ($resolver->resolve($request, $metadata) as $argument) { + ++$count; + $arguments[] = $argument; } - if (!$atLeastOne) { + if (1 < $count && !$metadata->isVariadic()) { + throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at most one value for non-variadic arguments.', get_debug_type($resolver))); + } + + if ($count) { + // continue to the next controller argument + continue 2; + } + + if (!$resolver instanceof ValueResolverInterface) { throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver))); } - - // continue to the next controller argument - continue 2; } - $representative = $controller; - - if (\is_array($representative)) { - $representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]); - } elseif (\is_object($representative)) { - $representative = \get_class($representative); - } - - throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or there is a non-optional argument after this one.', $this->getPrettyName($controller), $metadata->getName())); } return $arguments; @@ -95,4 +125,21 @@ final class ArgumentResolver implements ArgumentResolverInterface new VariadicValueResolver(), ]; } + + private function getPrettyName($controller): string + { + if (\is_array($controller)) { + if (\is_object($controller[0])) { + $controller[0] = get_debug_type($controller[0]); + } + + return $controller[0].'::'.$controller[1]; + } + + if (\is_object($controller)) { + return get_debug_type($controller); + } + + return $controller; + } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php new file mode 100644 index 000000000..4f0ca76d3 --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Attempt to resolve backed enum cases from request attributes, for a route path parameter, + * leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type. + * + * @author Maxime Steinhausser + * + * @final since Symfony 6.2 + */ +class BackedEnumValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +{ + /** + * @deprecated since Symfony 6.2, use resolve() instead + */ + public function supports(Request $request, ArgumentMetadata $argument): bool + { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + + if (!is_subclass_of($argument->getType(), \BackedEnum::class)) { + return false; + } + + if ($argument->isVariadic()) { + // only target route path parameters, which cannot be variadic. + return false; + } + + // do not support if no value can be resolved at all + // letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used + // or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error. + return $request->attributes->has($argument->getName()); + } + + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + if (!is_subclass_of($argument->getType(), \BackedEnum::class)) { + return []; + } + + if ($argument->isVariadic()) { + // only target route path parameters, which cannot be variadic. + return []; + } + + // do not support if no value can be resolved at all + // letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used + // or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error. + if (!$request->attributes->has($argument->getName())) { + return []; + } + + $value = $request->attributes->get($argument->getName()); + + if (null === $value) { + return [null]; + } + + if ($value instanceof \BackedEnum) { + return [$value]; + } + + if (!\is_int($value) && !\is_string($value)) { + throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".', $argument->getType(), $argument->getName(), get_debug_type($value))); + } + + /** @var class-string<\BackedEnum> $enumType */ + $enumType = $argument->getType(); + + try { + return [$enumType::from($value)]; + } catch (\ValueError $e) { + throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: ', $argument->getType(), $argument->getName()).$e->getMessage(), $e); + } + } +} diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php new file mode 100644 index 000000000..0cfd42bad --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Clock\ClockInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\MapDateTime; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Convert DateTime instances from request attribute variable. + * + * @author Benjamin Eberlei + * @author Tim Goudriaan + */ +final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +{ + public function __construct( + private readonly ?ClockInterface $clock = null, + ) { + } + + /** + * @deprecated since Symfony 6.2, use resolve() instead + */ + public function supports(Request $request, ArgumentMetadata $argument): bool + { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + + return is_a($argument->getType(), \DateTimeInterface::class, true) && $request->attributes->has($argument->getName()); + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!is_a($argument->getType(), \DateTimeInterface::class, true) || !$request->attributes->has($argument->getName())) { + return []; + } + + $value = $request->attributes->get($argument->getName()); + $class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType(); + + if (!$value) { + if ($argument->isNullable()) { + return [null]; + } + if (!$this->clock) { + return [new $class()]; + } + $value = $this->clock->now(); + } + + if ($value instanceof \DateTimeInterface) { + return [$value instanceof $class ? $value : $class::createFromInterface($value)]; + } + + $format = null; + + if ($attributes = $argument->getAttributes(MapDateTime::class, ArgumentMetadata::IS_INSTANCEOF)) { + $attribute = $attributes[0]; + $format = $attribute->format; + } + + if (null !== $format) { + $date = $class::createFromFormat($format, $value, $this->clock?->now()->getTimeZone()); + + if (($class::getLastErrors() ?: ['warning_count' => 0])['warning_count']) { + $date = false; + } + } else { + if (false !== filter_var($value, \FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { + $value = '@'.$value; + } + try { + $date = new $class($value, $this->clock?->now()->getTimeZone()); + } catch (\Exception) { + $date = false; + } + } + + if (!$date) { + throw new NotFoundHttpException(sprintf('Invalid date given for parameter "%s".', $argument->getName())); + } + + return [$date]; + } +} diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php index 32a0e071d..eb9769c09 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php @@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,21 +21,28 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * * @author Iltar van der Berg */ -final class DefaultValueResolver implements ArgumentValueResolverInterface +final class DefaultValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()); } - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null; + if ($argument->hasDefaultValue()) { + return [$argument->getDefaultValue()]; + } + + if (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()) { + return [null]; + } + + return []; } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php index 48ea6e742..264036128 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php @@ -15,6 +15,7 @@ use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -22,9 +23,9 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * * @author Simeon Kolev */ -final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface +final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { - private $container; + private ContainerInterface $container; public function __construct(ContainerInterface $container) { @@ -32,10 +33,12 @@ final class NotTaggedControllerValueResolver implements ArgumentValueResolverInt } /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + $controller = $request->attributes->get('_controller'); if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { @@ -55,13 +58,14 @@ final class NotTaggedControllerValueResolver implements ArgumentValueResolverInt return false === $this->container->has($controller); } - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - if (\is_array($controller = $request->attributes->get('_controller'))) { + $controller = $request->attributes->get('_controller'); + + if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return []; } if ('\\' === $controller[0]) { @@ -74,6 +78,10 @@ final class NotTaggedControllerValueResolver implements ArgumentValueResolverInt : $controller.'::__invoke'; } + if ($this->container->has($controller)) { + return []; + } + $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); $message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what); diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php new file mode 100644 index 000000000..b186a39c5 --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Resolve arguments of type: array, string, int, float, bool, \BackedEnum from query parameters. + * + * @author Ruud Kamphuis + * @author Nicolas Grekas + * @author Mateusz Anders + */ +final class QueryParameterValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!$attribute = $argument->getAttributesOfType(MapQueryParameter::class)[0] ?? null) { + return []; + } + + $name = $attribute->name ?? $argument->getName(); + if (!$request->query->has($name)) { + if ($argument->isNullable() || $argument->hasDefaultValue()) { + return []; + } + + throw new NotFoundHttpException(sprintf('Missing query parameter "%s".', $name)); + } + + $value = $request->query->all()[$name]; + $type = $argument->getType(); + + if (null === $attribute->filter && 'array' === $type) { + if (!$argument->isVariadic()) { + return [(array) $value]; + } + + $filtered = array_values(array_filter((array) $value, \is_array(...))); + + if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name)); + } + + return $filtered; + } + + $options = [ + 'flags' => $attribute->flags | \FILTER_NULL_ON_FAILURE, + 'options' => $attribute->options, + ]; + + if ('array' === $type || $argument->isVariadic()) { + $value = (array) $value; + $options['flags'] |= \FILTER_REQUIRE_ARRAY; + } else { + $options['flags'] |= \FILTER_REQUIRE_SCALAR; + } + + $enumType = null; + $filter = match ($type) { + 'array' => \FILTER_DEFAULT, + 'string' => \FILTER_DEFAULT, + 'int' => \FILTER_VALIDATE_INT, + 'float' => \FILTER_VALIDATE_FLOAT, + 'bool' => \FILTER_VALIDATE_BOOL, + default => match ($enumType = is_subclass_of($type, \BackedEnum::class) ? (new \ReflectionEnum($type))->getBackingType()->getName() : null) { + 'int' => \FILTER_VALIDATE_INT, + 'string' => \FILTER_DEFAULT, + default => throw new \LogicException(sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')), + } + }; + + $value = filter_var($value, $attribute->filter ?? $filter, $options); + + if (null !== $enumType && null !== $value) { + $enumFrom = static function ($value) use ($type) { + if (!\is_string($value) && !\is_int($value)) { + return null; + } + + try { + return $type::from($value); + } catch (\ValueError) { + return null; + } + }; + + $value = \is_array($value) ? array_map($enumFrom, $value) : $enumFrom($value); + } + + if (null === $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name)); + } + + if (!\is_array($value)) { + return [$value]; + } + + $filtered = array_filter($value, static fn ($v) => null !== $v); + + if ($argument->isVariadic()) { + $filtered = array_values($filtered); + } + + if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name)); + } + + return $argument->isVariadic() ? $filtered : [$filtered]; + } +} diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php index c62d327b6..370e41445 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,21 +21,20 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * * @author Iltar van der Berg */ -final class RequestAttributeValueResolver implements ArgumentValueResolverInterface +final class RequestAttributeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return !$argument->isVariadic() && $request->attributes->has($argument->getName()); } - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $request->attributes->get($argument->getName()); + return !$argument->isVariadic() && $request->attributes->has($argument->getName()) ? [$request->attributes->get($argument->getName())] : []; } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php new file mode 100644 index 000000000..f7f603076 --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Exception\UnsupportedFormatException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Exception\ValidationFailedException; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Konstantin Myakshin + * + * @final + */ +class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscriberInterface +{ + /** + * @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT + * @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS + */ + private const CONTEXT_DENORMALIZE = [ + 'disable_type_enforcement' => true, + 'collect_denormalization_errors' => true, + ]; + + /** + * @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS + */ + private const CONTEXT_DESERIALIZE = [ + 'collect_denormalization_errors' => true, + ]; + + public function __construct( + private readonly SerializerInterface&DenormalizerInterface $serializer, + private readonly ?ValidatorInterface $validator = null, + private readonly ?TranslatorInterface $translator = null, + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + $attribute = $argument->getAttributesOfType(MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF)[0] + ?? $argument->getAttributesOfType(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0] + ?? null; + + if (!$attribute) { + return []; + } + + if ($argument->isVariadic()) { + throw new \LogicException(sprintf('Mapping variadic argument "$%s" is not supported.', $argument->getName())); + } + + $attribute->metadata = $argument; + + return [$attribute]; + } + + public function onKernelControllerArguments(ControllerArgumentsEvent $event): void + { + $arguments = $event->getArguments(); + + foreach ($arguments as $i => $argument) { + if ($argument instanceof MapQueryString) { + $payloadMapper = 'mapQueryString'; + $validationFailedCode = $argument->validationFailedStatusCode; + } elseif ($argument instanceof MapRequestPayload) { + $payloadMapper = 'mapRequestPayload'; + $validationFailedCode = $argument->validationFailedStatusCode; + } else { + continue; + } + $request = $event->getRequest(); + + if (!$type = $argument->metadata->getType()) { + throw new \LogicException(sprintf('Could not resolve the "$%s" controller argument: argument should be typed.', $argument->metadata->getName())); + } + + if ($this->validator) { + $violations = new ConstraintViolationList(); + try { + $payload = $this->$payloadMapper($request, $type, $argument); + } catch (PartialDenormalizationException $e) { + $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p); + foreach ($e->getErrors() as $error) { + $parameters = ['{{ type }}' => implode('|', $error->getExpectedTypes())]; + if ($error->canUseMessageForUser()) { + $parameters['hint'] = $error->getMessage(); + } + $template = 'This value should be of type {{ type }}.'; + $message = $trans($template, $parameters, 'validators'); + $violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null)); + } + $payload = $e->getData(); + } + + if (null !== $payload) { + $violations->addAll($this->validator->validate($payload, null, $argument->validationGroups ?? null)); + } + + if (\count($violations)) { + throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException($payload, $violations)); + } + } else { + try { + $payload = $this->$payloadMapper($request, $type, $argument); + } catch (PartialDenormalizationException $e) { + throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e); + } + } + + if (null === $payload) { + $payload = match (true) { + $argument->metadata->hasDefaultValue() => $argument->metadata->getDefaultValue(), + $argument->metadata->isNullable() => null, + default => throw new HttpException($validationFailedCode) + }; + } + + $arguments[$i] = $payload; + } + + $event->setArguments($arguments); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments', + ]; + } + + private function mapQueryString(Request $request, string $type, MapQueryString $attribute): ?object + { + if (!$data = $request->query->all()) { + return null; + } + + return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE); + } + + private function mapRequestPayload(Request $request, string $type, MapRequestPayload $attribute): ?object + { + if (null === $format = $request->getContentTypeFormat()) { + throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, 'Unsupported format.'); + } + + if ($attribute->acceptFormat && !\in_array($format, (array) $attribute->acceptFormat, true)) { + throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, sprintf('Unsupported format, expects "%s", but "%s" given.', implode('", "', (array) $attribute->acceptFormat), $format)); + } + + if ($data = $request->request->all()) { + return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE); + } + + if ('' === $data = $request->getContent()) { + return null; + } + + if ('form' === $format) { + throw new HttpException(Response::HTTP_BAD_REQUEST, 'Request payload contains invalid "form" data.'); + } + + try { + return $this->serializer->deserialize($data, $type, $format, self::CONTEXT_DESERIALIZE + $attribute->serializationContext); + } catch (UnsupportedFormatException $e) { + throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, sprintf('Unsupported format: "%s".', $format), $e); + } catch (NotEncodableValueException $e) { + throw new HttpException(Response::HTTP_BAD_REQUEST, sprintf('Request payload contains invalid "%s" data.', $format), $e); + } + } +} diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php index 75cbd97ed..6347f7019 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,21 +21,20 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * * @author Iltar van der Berg */ -final class RequestValueResolver implements ArgumentValueResolverInterface +final class RequestValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); } - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $request; + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class) ? [$request] : []; } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php index 4ffb8c99e..96e0337d6 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -15,6 +15,7 @@ use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -22,9 +23,9 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * * @author Nicolas Grekas */ -final class ServiceValueResolver implements ArgumentValueResolverInterface +final class ServiceValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { - private $container; + private ContainerInterface $container; public function __construct(ContainerInterface $container) { @@ -32,10 +33,12 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface } /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + $controller = $request->attributes->get('_controller'); if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { @@ -55,26 +58,30 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface return $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); } - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - if (\is_array($controller = $request->attributes->get('_controller'))) { + $controller = $request->attributes->get('_controller'); + + if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return []; } if ('\\' === $controller[0]) { $controller = ltrim($controller, '\\'); } - if (!$this->container->has($controller)) { - $i = strrpos($controller, ':'); + if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) { $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); } + if (!$this->container->has($controller) || !$this->container->get($controller)->has($argument->getName())) { + return []; + } + try { - yield $this->container->get($controller)->get($argument->getName()); + return [$this->container->get($controller)->get($argument->getName())]; } catch (RuntimeException $e) { $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage()); @@ -84,7 +91,6 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface } $r = new \ReflectionProperty($e, 'message'); - $r->setAccessible(true); $r->setValue($e, $message); throw $e; diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php index a1e6b4315..c8e7575d5 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php @@ -14,6 +14,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -21,13 +22,15 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * * @author Iltar van der Berg */ -final class SessionValueResolver implements ArgumentValueResolverInterface +final class SessionValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + if (!$request->hasSession()) { return false; } @@ -40,11 +43,17 @@ final class SessionValueResolver implements ArgumentValueResolverInterface return $request->getSession() instanceof $type; } - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $request->getSession(); + if (!$request->hasSession()) { + return []; + } + + $type = $argument->getType(); + if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { + return []; + } + + return $request->getSession() instanceof $type ? [$request->getSession()] : []; } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php index bde3c90c1..0cb4703b2 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php @@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Stopwatch\Stopwatch; @@ -21,23 +22,27 @@ use Symfony\Component\Stopwatch\Stopwatch; * * @author Iltar van der Berg */ -final class TraceableValueResolver implements ArgumentValueResolverInterface +final class TraceableValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { - private $inner; - private $stopwatch; + private ArgumentValueResolverInterface|ValueResolverInterface $inner; + private Stopwatch $stopwatch; - public function __construct(ArgumentValueResolverInterface $inner, Stopwatch $stopwatch) + public function __construct(ArgumentValueResolverInterface|ValueResolverInterface $inner, Stopwatch $stopwatch) { $this->inner = $inner; $this->stopwatch = $stopwatch; } /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { - $method = \get_class($this->inner).'::'.__FUNCTION__; + if ($this->inner instanceof ValueResolverInterface) { + return true; + } + + $method = $this->inner::class.'::'.__FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); $return = $this->inner->supports($request, $argument); @@ -47,12 +52,9 @@ final class TraceableValueResolver implements ArgumentValueResolverInterface return $return; } - /** - * {@inheritdoc} - */ public function resolve(Request $request, ArgumentMetadata $argument): iterable { - $method = \get_class($this->inner).'::'.__FUNCTION__; + $method = $this->inner::class.'::'.__FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); yield from $this->inner->resolve($request, $argument); diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php new file mode 100644 index 000000000..7a12e21ea --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Uid\AbstractUid; + +final class UidValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +{ + /** + * @deprecated since Symfony 6.2, use resolve() instead + */ + public function supports(Request $request, ArgumentMetadata $argument): bool + { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + + return !$argument->isVariadic() + && \is_string($request->attributes->get($argument->getName())) + && null !== $argument->getType() + && is_subclass_of($argument->getType(), AbstractUid::class, true); + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if ($argument->isVariadic() + || !\is_string($value = $request->attributes->get($argument->getName())) + || null === ($uidClass = $argument->getType()) + || !is_subclass_of($uidClass, AbstractUid::class, true) + ) { + return []; + } + + try { + return [$uidClass::fromString($value)]; + } catch (\InvalidArgumentException $e) { + throw new NotFoundHttpException(sprintf('The uid for the "%s" parameter is invalid.', $argument->getName()), $e); + } + } +} diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php index a8f7e0f44..4f6cba729 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php @@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,27 +21,30 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * * @author Iltar van der Berg */ -final class VariadicValueResolver implements ArgumentValueResolverInterface +final class VariadicValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return $argument->isVariadic() && $request->attributes->has($argument->getName()); } - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { + if (!$argument->isVariadic() || !$request->attributes->has($argument->getName())) { + return []; + } + $values = $request->attributes->get($argument->getName()); if (!\is_array($values)) { throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), get_debug_type($values))); } - yield from $values; + return $values; } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php b/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php index 30e4783e8..33d3ce298 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php @@ -24,9 +24,9 @@ interface ArgumentResolverInterface /** * Returns the arguments to pass to the controller. * - * @return array + * @param \ReflectionFunctionAbstract|null $reflector * * @throws \RuntimeException When no value could be provided for a required argument */ - public function getArguments(Request $request, callable $controller); + public function getArguments(Request $request, callable $controller/* , \ReflectionFunctionAbstract $reflector = null */): array; } diff --git a/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php b/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php index 1317707b1..9c3b1a016 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php +++ b/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php @@ -18,20 +18,18 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; * Responsible for resolving the value of an argument based on its metadata. * * @author Iltar van der Berg + * + * @deprecated since Symfony 6.2, implement ValueResolverInterface instead */ interface ArgumentValueResolverInterface { /** * Whether this resolver can resolve the value for the given ArgumentMetadata. - * - * @return bool */ - public function supports(Request $request, ArgumentMetadata $argument); + public function supports(Request $request, ArgumentMetadata $argument): bool; /** * Returns the possible value(s). - * - * @return iterable */ - public function resolve(Request $request, ArgumentMetadata $argument); + public function resolve(Request $request, ArgumentMetadata $argument): iterable; } diff --git a/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php b/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php index 3b9468465..1c9254e73 100644 --- a/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php +++ b/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php @@ -32,20 +32,7 @@ class ContainerControllerResolver extends ControllerResolver parent::__construct($logger); } - protected function createController(string $controller) - { - if (1 === substr_count($controller, ':')) { - $controller = str_replace(':', '::', $controller); - trigger_deprecation('symfony/http-kernel', '5.1', 'Referencing controllers with a single colon is deprecated. Use "%s" instead.', $controller); - } - - return parent::createController($controller); - } - - /** - * {@inheritdoc} - */ - protected function instantiateController(string $class) + protected function instantiateController(string $class): object { $class = ltrim($class, '\\'); @@ -67,7 +54,7 @@ class ContainerControllerResolver extends ControllerResolver throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class.', $class), 0, $e); } - private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous) + private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous): void { if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) { throw new \InvalidArgumentException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous); diff --git a/lib/symfony/http-kernel/Controller/ControllerResolver.php b/lib/symfony/http-kernel/Controller/ControllerResolver.php index 8abbadd48..d39508949 100644 --- a/lib/symfony/http-kernel/Controller/ControllerResolver.php +++ b/lib/symfony/http-kernel/Controller/ControllerResolver.php @@ -12,7 +12,9 @@ namespace Symfony\Component\HttpKernel\Controller; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\AsController; /** * This implementation uses the '_controller' request attribute to determine @@ -23,7 +25,9 @@ use Symfony\Component\HttpFoundation\Request; */ class ControllerResolver implements ControllerResolverInterface { - private $logger; + private ?LoggerInterface $logger; + private array $allowedControllerTypes = []; + private array $allowedControllerAttributes = [AsController::class => AsController::class]; public function __construct(LoggerInterface $logger = null) { @@ -31,14 +35,27 @@ class ControllerResolver implements ControllerResolverInterface } /** - * {@inheritdoc} + * @param array $types + * @param array $attributes */ - public function getController(Request $request) + public function allowControllers(array $types = [], array $attributes = []): void + { + foreach ($types as $type) { + $this->allowedControllerTypes[$type] = $type; + } + + foreach ($attributes as $attribute) { + $this->allowedControllerAttributes[$attribute] = $attribute; + } + } + + /** + * @throws BadRequestException when the request has attribute "_check_controller_is_allowed" set to true and the controller is not allowed + */ + public function getController(Request $request): callable|false { if (!$controller = $request->attributes->get('_controller')) { - if (null !== $this->logger) { - $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.'); - } + $this->logger?->warning('Unable to look for the controller as the "_controller" parameter is missing.'); return false; } @@ -48,15 +65,8 @@ class ControllerResolver implements ControllerResolverInterface try { $controller[0] = $this->instantiateController($controller[0]); } catch (\Error|\LogicException $e) { - try { - // We cannot just check is_callable but have to use reflection because a non-static method - // can still be called statically in PHP but we don't want that. This is deprecated in PHP 7, so we - // could simplify this with PHP 8. - if ((new \ReflectionMethod($controller[0], $controller[1]))->isStatic()) { - return $controller; - } - } catch (\ReflectionException $reflectionException) { - throw $e; + if (\is_callable($controller)) { + return $this->checkController($request, $controller); } throw $e; @@ -67,7 +77,7 @@ class ControllerResolver implements ControllerResolverInterface throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); } - return $controller; + return $this->checkController($request, $controller); } if (\is_object($controller)) { @@ -75,11 +85,11 @@ class ControllerResolver implements ControllerResolverInterface throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); } - return $controller; + return $this->checkController($request, $controller); } if (\function_exists($controller)) { - return $controller; + return $this->checkController($request, $controller); } try { @@ -92,17 +102,15 @@ class ControllerResolver implements ControllerResolverInterface throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($callable)); } - return $callable; + return $this->checkController($request, $callable); } /** * Returns a callable for the given controller. * - * @return callable - * * @throws \InvalidArgumentException When the controller cannot be created */ - protected function createController(string $controller) + protected function createController(string $controller): callable { if (!str_contains($controller, '::')) { $controller = $this->instantiateController($controller); @@ -123,7 +131,7 @@ class ControllerResolver implements ControllerResolverInterface if ((new \ReflectionMethod($class, $method))->isStatic()) { return $class.'::'.$method; } - } catch (\ReflectionException $reflectionException) { + } catch (\ReflectionException) { throw $e; } @@ -139,15 +147,13 @@ class ControllerResolver implements ControllerResolverInterface /** * Returns an instantiated controller. - * - * @return object */ - protected function instantiateController(string $class) + protected function instantiateController(string $class): object { return new $class(); } - private function getControllerError($callable): string + private function getControllerError(mixed $callable): string { if (\is_string($callable)) { if (str_contains($callable, '::')) { @@ -213,8 +219,61 @@ class ControllerResolver implements ControllerResolverInterface { $methods = get_class_methods($classOrObject); - return array_filter($methods, function (string $method) { - return 0 !== strncmp($method, '__', 2); - }); + return array_filter($methods, fn (string $method) => 0 !== strncmp($method, '__', 2)); + } + + private function checkController(Request $request, callable $controller): callable + { + if (!$request->attributes->get('_check_controller_is_allowed', false)) { + return $controller; + } + + $r = null; + + if (\is_array($controller)) { + [$class, $name] = $controller; + $name = (\is_string($class) ? $class : $class::class).'::'.$name; + } elseif (\is_object($controller) && !$controller instanceof \Closure) { + $class = $controller; + $name = $class::class.'::__invoke'; + } else { + $r = new \ReflectionFunction($controller); + $name = $r->name; + + if (str_contains($name, '{closure}')) { + $name = $class = \Closure::class; + } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { + $class = $class->name; + $name = $class.'::'.$name; + } + } + + if ($class) { + foreach ($this->allowedControllerTypes as $type) { + if (is_a($class, $type, true)) { + return $controller; + } + } + } + + $r ??= new \ReflectionClass($class); + + foreach ($r->getAttributes() as $attribute) { + if (isset($this->allowedControllerAttributes[$attribute->getName()])) { + return $controller; + } + } + + if (str_contains($name, '@anonymous')) { + $name = 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], $name); + } + + if (-1 === $request->attributes->get('_check_controller_is_allowed')) { + trigger_deprecation('symfony/http-kernel', '6.4', 'Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class); + + return $controller; + } + + throw new BadRequestException(sprintf('Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class)); } } diff --git a/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php b/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php index 8b70a88f6..6cfe6c35c 100644 --- a/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php +++ b/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php @@ -37,5 +37,5 @@ interface ControllerResolverInterface * * @throws \LogicException If a controller was found based on the request but it is not callable */ - public function getController(Request $request); + public function getController(Request $request): callable|false; } diff --git a/lib/symfony/http-kernel/Controller/ErrorController.php b/lib/symfony/http-kernel/Controller/ErrorController.php index b6c440103..9dd211686 100644 --- a/lib/symfony/http-kernel/Controller/ErrorController.php +++ b/lib/symfony/http-kernel/Controller/ErrorController.php @@ -25,11 +25,11 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ class ErrorController { - private $kernel; - private $controller; - private $errorRenderer; + private HttpKernelInterface $kernel; + private string|object|array|null $controller; + private ErrorRendererInterface $errorRenderer; - public function __construct(HttpKernelInterface $kernel, $controller, ErrorRendererInterface $errorRenderer) + public function __construct(HttpKernelInterface $kernel, string|object|array|null $controller, ErrorRendererInterface $errorRenderer) { $this->kernel = $kernel; $this->controller = $controller; diff --git a/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php b/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php index e22cf082c..27cc8fb1a 100644 --- a/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php +++ b/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php @@ -19,8 +19,8 @@ use Symfony\Component\Stopwatch\Stopwatch; */ class TraceableArgumentResolver implements ArgumentResolverInterface { - private $resolver; - private $stopwatch; + private ArgumentResolverInterface $resolver; + private Stopwatch $stopwatch; public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch) { @@ -29,16 +29,17 @@ class TraceableArgumentResolver implements ArgumentResolverInterface } /** - * {@inheritdoc} + * @param \ReflectionFunctionAbstract|null $reflector */ - public function getArguments(Request $request, callable $controller) + public function getArguments(Request $request, callable $controller/* , \ReflectionFunctionAbstract $reflector = null */): array { + $reflector = 2 < \func_num_args() ? func_get_arg(2) : null; $e = $this->stopwatch->start('controller.get_arguments'); - $ret = $this->resolver->getArguments($request, $controller); - - $e->stop(); - - return $ret; + try { + return $this->resolver->getArguments($request, $controller, $reflector); + } finally { + $e->stop(); + } } } diff --git a/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php b/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php index bf6b6aa1f..60f29ab5d 100644 --- a/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php +++ b/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php @@ -19,8 +19,8 @@ use Symfony\Component\Stopwatch\Stopwatch; */ class TraceableControllerResolver implements ControllerResolverInterface { - private $resolver; - private $stopwatch; + private ControllerResolverInterface $resolver; + private Stopwatch $stopwatch; public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) { @@ -28,17 +28,14 @@ class TraceableControllerResolver implements ControllerResolverInterface $this->stopwatch = $stopwatch; } - /** - * {@inheritdoc} - */ - public function getController(Request $request) + public function getController(Request $request): callable|false { $e = $this->stopwatch->start('controller.get_callable'); - $ret = $this->resolver->getController($request); - - $e->stop(); - - return $ret; + try { + return $this->resolver->getController($request); + } finally { + $e->stop(); + } } } diff --git a/lib/symfony/http-kernel/Controller/ValueResolverInterface.php b/lib/symfony/http-kernel/Controller/ValueResolverInterface.php new file mode 100644 index 000000000..a861705cb --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ValueResolverInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Responsible for resolving the value of an argument based on its metadata. + * + * @author Nicolas Grekas + */ +interface ValueResolverInterface +{ + /** + * Returns the possible value(s). + */ + public function resolve(Request $request, ArgumentMetadata $argument): iterable; +} diff --git a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php index 1a9ebc0c3..a352090ea 100644 --- a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php +++ b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php @@ -11,8 +11,6 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; -use Symfony\Component\HttpKernel\Attribute\ArgumentInterface; - /** * Responsible for storing metadata of an argument. * @@ -22,18 +20,18 @@ class ArgumentMetadata { public const IS_INSTANCEOF = 2; - private $name; - private $type; - private $isVariadic; - private $hasDefaultValue; - private $defaultValue; - private $isNullable; - private $attributes; + private string $name; + private ?string $type; + private bool $isVariadic; + private bool $hasDefaultValue; + private mixed $defaultValue; + private bool $isNullable; + private array $attributes; /** * @param object[] $attributes */ - public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, $defaultValue, bool $isNullable = false, $attributes = []) + public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, mixed $defaultValue, bool $isNullable = false, array $attributes = []) { $this->name = $name; $this->type = $type; @@ -41,21 +39,13 @@ class ArgumentMetadata $this->hasDefaultValue = $hasDefaultValue; $this->defaultValue = $defaultValue; $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); - - if (null === $attributes || $attributes instanceof ArgumentInterface) { - trigger_deprecation('symfony/http-kernel', '5.3', 'The "%s" constructor expects an array of PHP attributes as last argument, %s given.', __CLASS__, get_debug_type($attributes)); - $attributes = $attributes ? [$attributes] : []; - } - $this->attributes = $attributes; } /** * Returns the name as given in PHP, $foo would yield "foo". - * - * @return string */ - public function getName() + public function getName(): string { return $this->name; } @@ -64,20 +54,16 @@ class ArgumentMetadata * Returns the type of the argument. * * The type is the PHP class in 5.5+ and additionally the basic type in PHP 7.0+. - * - * @return string|null */ - public function getType() + public function getType(): ?string { return $this->type; } /** * Returns whether the argument is defined as "...$variadic". - * - * @return bool */ - public function isVariadic() + public function isVariadic(): bool { return $this->isVariadic; } @@ -86,20 +72,16 @@ class ArgumentMetadata * Returns whether the argument has a default value. * * Implies whether an argument is optional. - * - * @return bool */ - public function hasDefaultValue() + public function hasDefaultValue(): bool { return $this->hasDefaultValue; } /** * Returns whether the argument accepts null values. - * - * @return bool */ - public function isNullable() + public function isNullable(): bool { return $this->isNullable; } @@ -108,10 +90,8 @@ class ArgumentMetadata * Returns the default value of the argument. * * @throws \LogicException if no default value is present; {@see self::hasDefaultValue()} - * - * @return mixed */ - public function getDefaultValue() + public function getDefaultValue(): mixed { if (!$this->hasDefaultValue) { throw new \LogicException(sprintf('Argument $%s does not have a default value. Use "%s::hasDefaultValue()" to avoid this exception.', $this->name, __CLASS__)); @@ -121,21 +101,10 @@ class ArgumentMetadata } /** - * Returns the attribute (if any) that was set on the argument. - */ - public function getAttribute(): ?ArgumentInterface - { - trigger_deprecation('symfony/http-kernel', '5.3', 'Method "%s()" is deprecated, use "getAttributes()" instead.', __METHOD__); - - if (!$this->attributes) { - return null; - } - - return $this->attributes[0] instanceof ArgumentInterface ? $this->attributes[0] : null; - } - - /** - * @return object[] + * @param class-string $name + * @param self::IS_INSTANCEOF|0 $flags + * + * @return array */ public function getAttributes(string $name = null, int $flags = 0): array { @@ -143,6 +112,19 @@ class ArgumentMetadata return $this->attributes; } + return $this->getAttributesOfType($name, $flags); + } + + /** + * @template T of object + * + * @param class-string $name + * @param self::IS_INSTANCEOF|0 $flags + * + * @return array + */ + public function getAttributesOfType(string $name, int $flags = 0): array + { $attributes = []; if ($flags & self::IS_INSTANCEOF) { foreach ($this->attributes as $attribute) { @@ -152,7 +134,7 @@ class ArgumentMetadata } } else { foreach ($this->attributes as $attribute) { - if (\get_class($attribute) === $name) { + if ($attribute::class === $name) { $attributes[] = $attribute; } } diff --git a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php index 85bb805f3..cb7f0a78c 100644 --- a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php +++ b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -18,37 +18,20 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; */ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface { - /** - * {@inheritdoc} - */ - public function createArgumentMetadata($controller): array + public function createArgumentMetadata(string|object|array $controller, \ReflectionFunctionAbstract $reflector = null): array { $arguments = []; + $reflector ??= new \ReflectionFunction($controller(...)); - if (\is_array($controller)) { - $reflection = new \ReflectionMethod($controller[0], $controller[1]); - $class = $reflection->class; - } elseif (\is_object($controller) && !$controller instanceof \Closure) { - $reflection = new \ReflectionMethod($controller, '__invoke'); - $class = $reflection->class; - } else { - $reflection = new \ReflectionFunction($controller); - if ($class = str_contains($reflection->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $reflection->getClosureCalledClass() : $reflection->getClosureScopeClass())) { - $class = $class->name; - } - } - - foreach ($reflection->getParameters() as $param) { + foreach ($reflector->getParameters() as $param) { $attributes = []; - if (\PHP_VERSION_ID >= 80000) { - foreach ($param->getAttributes() as $reflectionAttribute) { - if (class_exists($reflectionAttribute->getName())) { - $attributes[] = $reflectionAttribute->newInstance(); - } + foreach ($param->getAttributes() as $reflectionAttribute) { + if (class_exists($reflectionAttribute->getName())) { + $attributes[] = $reflectionAttribute->newInstance(); } } - $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $class), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes); + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes); } return $arguments; @@ -57,22 +40,17 @@ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface /** * Returns an associated type to the given parameter if available. */ - private function getType(\ReflectionParameter $parameter, ?string $class): ?string + private function getType(\ReflectionParameter $parameter): ?string { if (!$type = $parameter->getType()) { return null; } $name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; - if (null !== $class) { - switch (strtolower($name)) { - case 'self': - return $class; - case 'parent': - return get_parent_class($class) ?: null; - } - } - - return $name; + return match (strtolower($name)) { + 'self' => $parameter->getDeclaringClass()?->name, + 'parent' => get_parent_class($parameter->getDeclaringClass()?->name ?? '') ?: null, + default => $name, + }; } } diff --git a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php index a34befc22..954f901ef 100644 --- a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php +++ b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php @@ -19,9 +19,9 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; interface ArgumentMetadataFactoryInterface { /** - * @param string|object|array $controller The controller to resolve the arguments for + * @param \ReflectionFunctionAbstract|null $reflector * * @return ArgumentMetadata[] */ - public function createArgumentMetadata($controller); + public function createArgumentMetadata(string|object|array $controller/* , \ReflectionFunctionAbstract $reflector = null */): array; } diff --git a/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php b/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php index fda6a4eaa..016ef2ece 100644 --- a/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php @@ -21,12 +21,12 @@ use Symfony\Component\HttpFoundation\Response; */ class AjaxDataCollector extends DataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { // all collecting is done client side } - public function reset() + public function reset(): void { // all collecting is done client side } diff --git a/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php b/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php index 9819507aa..8a75227d4 100644 --- a/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\Data; /** * @author Fabien Potencier @@ -24,26 +25,24 @@ use Symfony\Component\VarDumper\Caster\ClassStub; */ class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface { - /** - * @var KernelInterface - */ - private $kernel; + private KernelInterface $kernel; /** * Sets the Kernel associated with this Request. */ - public function setKernel(KernelInterface $kernel = null) + public function setKernel(KernelInterface $kernel = null): void { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/http-kernel', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } + $this->kernel = $kernel; } - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { - $eom = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); - $eol = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); + $eom = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); + $eol = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); $this->data = [ 'token' => $response->headers->get('X-Debug-Token'), @@ -60,15 +59,15 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte 'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', 'php_timezone' => date_default_timezone_get(), 'xdebug_enabled' => \extension_loaded('xdebug'), - 'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN), - 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN), + 'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL), + 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL), 'bundles' => [], 'sapi_name' => \PHP_SAPI, ]; if (isset($this->kernel)) { foreach ($this->kernel->getBundles() as $name => $bundle) { - $this->data['bundles'][$name] = new ClassStub(\get_class($bundle)); + $this->data['bundles'][$name] = new ClassStub($bundle::class); } } @@ -78,15 +77,7 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte } } - /** - * {@inheritdoc} - */ - public function reset() - { - $this->data = []; - } - - public function lateCollect() + public function lateCollect(): void { $this->data = $this->cloneVar($this->data); } @@ -108,9 +99,8 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte } /** - * Returns the state of the current Symfony release. - * - * @return string One of: unknown, dev, stable, eom, eol + * Returns the state of the current Symfony release + * as one of: unknown, dev, stable, eom, eol. */ public function getSymfonyState(): string { @@ -126,9 +116,6 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte return $this->data['symfony_minor_version']; } - /** - * Returns if the current Symfony version is a Long-Term Support one. - */ public function isSymfonyLts(): bool { return $this->data['symfony_lts']; @@ -168,9 +155,6 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte return $this->data['php_version_extra'] ?? null; } - /** - * @return int The PHP architecture as number of bits (e.g. 32 or 64) - */ public function getPhpArchitecture(): int { return $this->data['php_architecture']; @@ -199,19 +183,27 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte * * @return bool|string true if debug is enabled, false otherwise or a string if no kernel was set */ - public function isDebug() + public function isDebug(): bool|string { return $this->data['debug']; } /** - * Returns true if the XDebug is enabled. + * Returns true if the Xdebug is enabled. */ - public function hasXDebug(): bool + public function hasXdebug(): bool { return $this->data['xdebug_enabled']; } + /** + * Returns true if the function xdebug_info is available. + */ + public function hasXdebugInfo(): bool + { + return \function_exists('xdebug_info'); + } + /** * Returns true if APCu is enabled. */ @@ -228,7 +220,7 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte return $this->data['zend_opcache_enabled']; } - public function getBundles() + public function getBundles(): array|Data { return $this->data['bundles']; } @@ -241,24 +233,16 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte return $this->data['sapi_name']; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'config'; } - /** - * Tries to retrieve information about the current Symfony version. - * - * @return string One of: dev, stable, eom, eol - */ private function determineSymfonyState(): string { - $now = new \DateTime(); - $eom = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); - $eol = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE)->modify('last day of this month'); + $now = new \DateTimeImmutable(); + $eom = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); + $eol = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE)->modify('last day of this month'); if ($now > $eol) { $versionState = 'eol'; diff --git a/lib/symfony/http-kernel/DataCollector/DataCollector.php b/lib/symfony/http-kernel/DataCollector/DataCollector.php index ccaf66da0..d8b795d42 100644 --- a/lib/symfony/http-kernel/DataCollector/DataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/DataCollector.php @@ -33,27 +33,20 @@ abstract class DataCollector implements DataCollectorInterface */ protected $data = []; - /** - * @var ClonerInterface - */ - private $cloner; + private ClonerInterface $cloner; /** * Converts the variable into a serializable Data instance. * * This array can be displayed in the template using * the VarDumper component. - * - * @param mixed $var - * - * @return Data */ - protected function cloneVar($var) + protected function cloneVar(mixed $var): Data { if ($var instanceof Data) { return $var; } - if (null === $this->cloner) { + if (!isset($this->cloner)) { $this->cloner = new VarCloner(); $this->cloner->setMaxItems(-1); $this->cloner->addCasters($this->getCasters()); @@ -84,14 +77,14 @@ abstract class DataCollector implements DataCollectorInterface return $casters; } - /** - * @return array - */ - public function __sleep() + public function __sleep(): array { return ['data']; } + /** + * @return void + */ public function __wakeup() { } @@ -99,14 +92,22 @@ abstract class DataCollector implements DataCollectorInterface /** * @internal to prevent implementing \Serializable */ - final protected function serialize() + final protected function serialize(): void { } /** * @internal to prevent implementing \Serializable */ - final protected function unserialize($data) + final protected function unserialize(string $data): void { } + + /** + * @return void + */ + public function reset() + { + $this->data = []; + } } diff --git a/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php b/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php index 1cb865fd6..8df94ccb8 100644 --- a/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php +++ b/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php @@ -24,6 +24,8 @@ interface DataCollectorInterface extends ResetInterface { /** * Collects data for the given Request and Response. + * + * @return void */ public function collect(Request $request, Response $response, \Throwable $exception = null); diff --git a/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php b/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php index 08026e562..ce02b545b 100644 --- a/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php @@ -11,10 +11,10 @@ namespace Symfony\Component\HttpKernel\DataCollector; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\VarCloner; @@ -31,23 +31,20 @@ use Symfony\Component\VarDumper\Server\Connection; */ class DumpDataCollector extends DataCollector implements DataDumperInterface { - private $stopwatch; - private $fileLinkFormat; - private $dataCount = 0; - private $isCollected = true; - private $clonesCount = 0; - private $clonesIndex = 0; - private $rootRefs; - private $charset; - private $requestStack; - private $dumper; - private $sourceContextProvider; + private ?Stopwatch $stopwatch = null; + private string|FileLinkFormatter|false $fileLinkFormat; + private int $dataCount = 0; + private bool $isCollected = true; + private int $clonesCount = 0; + private int $clonesIndex = 0; + private array $rootRefs; + private string $charset; + private ?RequestStack $requestStack; + private DataDumperInterface|Connection|null $dumper; + private mixed $sourceContextProvider; + private bool $webMode; - /** - * @param string|FileLinkFormatter|null $fileLinkFormat - * @param DataDumperInterface|Connection|null $dumper - */ - public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, $dumper = null) + public function __construct(Stopwatch $stopwatch = null, string|FileLinkFormatter $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, DataDumperInterface|Connection $dumper = null, bool $webMode = null) { $fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $this->stopwatch = $stopwatch; @@ -55,6 +52,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface $this->charset = $charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'; $this->requestStack = $requestStack; $this->dumper = $dumper; + $this->webMode = $webMode ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true); // All clones share these properties by reference: $this->rootRefs = [ @@ -72,36 +70,37 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface $this->clonesIndex = ++$this->clonesCount; } - public function dump(Data $data) + public function dump(Data $data): ?string { - if ($this->stopwatch) { - $this->stopwatch->start('dump'); - } + $this->stopwatch?->start('dump'); ['name' => $name, 'file' => $file, 'line' => $line, 'file_excerpt' => $fileExcerpt] = $this->sourceContextProvider->getContext(); - if ($this->dumper instanceof Connection) { - if (!$this->dumper->write($data)) { - $this->isCollected = false; - } - } elseif ($this->dumper) { - $this->doDump($this->dumper, $data, $name, $file, $line); - } else { + if (!$this->dumper || $this->dumper instanceof Connection && !$this->dumper->write($data)) { $this->isCollected = false; } + $context = $data->getContext(); + $label = $context['label'] ?? ''; + unset($context['label']); + $data = $data->withContext($context); + + if ($this->dumper && !$this->dumper instanceof Connection) { + $this->doDump($this->dumper, $data, $name, $file, $line, $label); + } + if (!$this->dataCount) { $this->data = []; } - $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); + $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt', 'label'); ++$this->dataCount; - if ($this->stopwatch) { - $this->stopwatch->stop('dump'); - } + $this->stopwatch?->stop('dump'); + + return null; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if (!$this->dataCount) { $this->data = []; @@ -116,32 +115,28 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface if (!$this->requestStack || !$response->headers->has('X-Debug-Token') || $response->isRedirection() - || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html')) + || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type') ?? '', 'html')) || 'html' !== $request->getRequestFormat() || false === strripos($response->getContent(), '') ) { - if ($response->headers->has('Content-Type') && str_contains($response->headers->get('Content-Type'), 'html')) { + if ($response->headers->has('Content-Type') && str_contains($response->headers->get('Content-Type') ?? '', 'html')) { $dumper = new HtmlDumper('php://output', $this->charset); $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { $dumper = new CliDumper('php://output', $this->charset); - if (method_exists($dumper, 'setDisplayOptions')) { - $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); - } + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } foreach ($this->data as $dump) { - $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']); + $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line'], $dump['label'] ?? ''); } } } - public function reset() + public function reset(): void { - if ($this->stopwatch) { - $this->stopwatch->reset(); - } - $this->data = []; + $this->stopwatch?->reset(); + parent::reset(); $this->dataCount = 0; $this->isCollected = true; $this->clonesCount = 0; @@ -172,7 +167,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface /** * @internal */ - public function __wakeup() + public function __wakeup(): void { parent::__wakeup(); @@ -238,19 +233,17 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface --$i; } - if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && stripos($h[$i], 'html')) { + if ($this->webMode) { $dumper = new HtmlDumper('php://output', $this->charset); $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { $dumper = new CliDumper('php://output', $this->charset); - if (method_exists($dumper, 'setDisplayOptions')) { - $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); - } + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } foreach ($this->data as $i => $dump) { $this->data[$i] = null; - $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']); + $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line'], $dump['label'] ?? ''); } $this->data = []; @@ -258,10 +251,12 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface } } - private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line) + private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line, string $label): void { if ($dumper instanceof CliDumper) { - $contextDumper = function ($name, $file, $line, $fmt) { + $contextDumper = function ($name, $file, $line, $fmt, $label) { + $this->line = '' !== $label ? $this->style('meta', $label).' in ' : ''; + if ($this instanceof HtmlDumper) { if ($file) { $s = $this->style('meta', '%s'); @@ -275,17 +270,17 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface } else { $name = $this->style('meta', $name); } - $this->line = $name.' on line '.$this->style('meta', $line).':'; + $this->line .= $name.' on line '.$this->style('meta', $line).':'; } else { - $this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; + $this->line .= $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; } $this->dumpLine(0); }; $contextDumper = $contextDumper->bindTo($dumper, $dumper); - $contextDumper($name, $file, $line, $this->fileLinkFormat); + $contextDumper($name, $file, $line, $this->fileLinkFormat, $label); } else { $cloner = new VarCloner(); - $dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); + $dumper->dump($cloner->cloneVar(('' !== $label ? $label.' in ' : '').$name.' on line '.$line.':')); } $dumper->dump($data); } diff --git a/lib/symfony/http-kernel/DataCollector/EventDataCollector.php b/lib/symfony/http-kernel/DataCollector/EventDataCollector.php index a81355336..a6524ea04 100644 --- a/lib/symfony/http-kernel/DataCollector/EventDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/EventDataCollector.php @@ -22,89 +22,97 @@ use Symfony\Contracts\Service\ResetInterface; /** * @author Fabien Potencier * + * @see TraceableEventDispatcher + * * @final */ class EventDataCollector extends DataCollector implements LateDataCollectorInterface { - protected $dispatcher; - private $requestStack; - private $currentRequest; - - public function __construct(EventDispatcherInterface $dispatcher = null, RequestStack $requestStack = null) - { - $this->dispatcher = $dispatcher; - $this->requestStack = $requestStack; - } + /** @var iterable */ + private iterable $dispatchers; + private ?Request $currentRequest = null; /** - * {@inheritdoc} + * @param iterable|EventDispatcherInterface|null $dispatchers */ - public function collect(Request $request, Response $response, \Throwable $exception = null) - { - $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; - $this->data = [ - 'called_listeners' => [], - 'not_called_listeners' => [], - 'orphaned_events' => [], - ]; + public function __construct( + iterable|EventDispatcherInterface $dispatchers = null, + private ?RequestStack $requestStack = null, + private string $defaultDispatcher = 'event_dispatcher', + ) { + if ($dispatchers instanceof EventDispatcherInterface) { + $dispatchers = [$this->defaultDispatcher => $dispatchers]; + } + $this->dispatchers = $dispatchers ?? []; } - public function reset() + public function collect(Request $request, Response $response, \Throwable $exception = null): void { + $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; $this->data = []; + } - if ($this->dispatcher instanceof ResetInterface) { - $this->dispatcher->reset(); + public function reset(): void + { + parent::reset(); + + foreach ($this->dispatchers as $dispatcher) { + if ($dispatcher instanceof ResetInterface) { + $dispatcher->reset(); + } } } - public function lateCollect() + public function lateCollect(): void { - if ($this->dispatcher instanceof TraceableEventDispatcher) { - $this->setCalledListeners($this->dispatcher->getCalledListeners($this->currentRequest)); - $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners($this->currentRequest)); - $this->setOrphanedEvents($this->dispatcher->getOrphanedEvents($this->currentRequest)); + foreach ($this->dispatchers as $name => $dispatcher) { + if (!$dispatcher instanceof TraceableEventDispatcher) { + continue; + } + + $this->setCalledListeners($dispatcher->getCalledListeners($this->currentRequest), $name); + $this->setNotCalledListeners($dispatcher->getNotCalledListeners($this->currentRequest), $name); + $this->setOrphanedEvents($dispatcher->getOrphanedEvents($this->currentRequest), $name); } $this->data = $this->cloneVar($this->data); } - /** - * @param array $listeners An array of called listeners - * - * @see TraceableEventDispatcher - */ - public function setCalledListeners(array $listeners) + public function getData(): array|Data { - $this->data['called_listeners'] = $listeners; - } - - /** - * @see TraceableEventDispatcher - * - * @return array|Data - */ - public function getCalledListeners() - { - return $this->data['called_listeners']; + return $this->data; } /** * @see TraceableEventDispatcher */ - public function setNotCalledListeners(array $listeners) + public function setCalledListeners(array $listeners, string $dispatcher = null): void { - $this->data['not_called_listeners'] = $listeners; + $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] = $listeners; } /** * @see TraceableEventDispatcher - * - * @return array|Data */ - public function getNotCalledListeners() + public function getCalledListeners(string $dispatcher = null): array|Data { - return $this->data['not_called_listeners']; + return $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] ?? []; + } + + /** + * @see TraceableEventDispatcher + */ + public function setNotCalledListeners(array $listeners, string $dispatcher = null): void + { + $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] = $listeners; + } + + /** + * @see TraceableEventDispatcher + */ + public function getNotCalledListeners(string $dispatcher = null): array|Data + { + return $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] ?? []; } /** @@ -112,24 +120,19 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter * * @see TraceableEventDispatcher */ - public function setOrphanedEvents(array $events) + public function setOrphanedEvents(array $events, string $dispatcher = null): void { - $this->data['orphaned_events'] = $events; + $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] = $events; } /** * @see TraceableEventDispatcher - * - * @return array|Data */ - public function getOrphanedEvents() + public function getOrphanedEvents(string $dispatcher = null): array|Data { - return $this->data['orphaned_events']; + return $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] ?? []; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'events'; diff --git a/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php b/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php index 14bbbb364..16a29adc1 100644 --- a/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php @@ -22,35 +22,21 @@ use Symfony\Component\HttpFoundation\Response; */ class ExceptionDataCollector extends DataCollector { - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if (null !== $exception) { $this->data = [ - 'exception' => FlattenException::createFromThrowable($exception), + 'exception' => FlattenException::createWithDataRepresentation($exception), ]; } } - /** - * {@inheritdoc} - */ - public function reset() - { - $this->data = []; - } - public function hasException(): bool { return isset($this->data['exception']); } - /** - * @return \Exception|FlattenException - */ - public function getException() + public function getException(): \Exception|FlattenException { return $this->data['exception']; } @@ -75,9 +61,6 @@ class ExceptionDataCollector extends DataCollector return $this->data['exception']->getTrace(); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'exception'; diff --git a/lib/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php b/lib/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php index 012332de4..efa1a4f73 100644 --- a/lib/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php +++ b/lib/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php @@ -20,6 +20,8 @@ interface LateDataCollectorInterface { /** * Collects data as late as possible. + * + * @return void */ public function lateCollect(); } diff --git a/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php index 2bbd2a039..eb2b9c85c 100644 --- a/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -15,7 +15,9 @@ use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; /** * @author Fabien Potencier @@ -24,47 +26,27 @@ use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; */ class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface { - private $logger; - private $containerPathPrefix; - private $currentRequest; - private $requestStack; - private $processedLogs; + private ?DebugLoggerInterface $logger; + private ?string $containerPathPrefix; + private ?Request $currentRequest = null; + private ?RequestStack $requestStack; + private ?array $processedLogs = null; public function __construct(object $logger = null, string $containerPathPrefix = null, RequestStack $requestStack = null) { - if (null !== $logger && $logger instanceof DebugLoggerInterface) { - $this->logger = $logger; - } - + $this->logger = DebugLoggerConfigurator::getDebugLogger($logger); $this->containerPathPrefix = $containerPathPrefix; $this->requestStack = $requestStack; } - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; } - /** - * {@inheritdoc} - */ - public function reset() + public function lateCollect(): void { - if ($this->logger instanceof DebugLoggerInterface) { - $this->logger->clear(); - } - $this->data = []; - } - - /** - * {@inheritdoc} - */ - public function lateCollect() - { - if (null !== $this->logger) { + if ($this->logger) { $containerDeprecationLogs = $this->getContainerDeprecationLogs(); $this->data = $this->computeErrorsCount($containerDeprecationLogs); // get compiler logs later (only when they are needed) to improve performance @@ -76,12 +58,12 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte $this->currentRequest = null; } - public function getLogs() + public function getLogs(): Data|array { return $this->data['logs'] ?? []; } - public function getProcessedLogs() + public function getProcessedLogs(): array { if (null !== $this->processedLogs) { return $this->processedLogs; @@ -119,14 +101,12 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte } // sort logs from oldest to newest - usort($logs, static function ($logA, $logB) { - return $logA['timestamp'] <=> $logB['timestamp']; - }); + usort($logs, static fn ($logA, $logB) => $logA['timestamp'] <=> $logB['timestamp']); return $this->processedLogs = $logs; } - public function getFilters() + public function getFilters(): array { $filters = [ 'channel' => [], @@ -157,39 +137,36 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte return $filters; } - public function getPriorities() + public function getPriorities(): Data|array { return $this->data['priorities'] ?? []; } - public function countErrors() + public function countErrors(): int { return $this->data['error_count'] ?? 0; } - public function countDeprecations() + public function countDeprecations(): int { return $this->data['deprecation_count'] ?? 0; } - public function countWarnings() + public function countWarnings(): int { return $this->data['warning_count'] ?? 0; } - public function countScreams() + public function countScreams(): int { return $this->data['scream_count'] ?? 0; } - public function getCompilerLogs() + public function getCompilerLogs(): Data { return $this->cloneVar($this->getContainerCompilerLogs($this->data['compiler_logs_filepath'] ?? null)); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'logger'; @@ -224,7 +201,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte private function getContainerCompilerLogs(string $compilerLogsFilepath = null): array { - if (!is_file($compilerLogsFilepath)) { + if (!$compilerLogsFilepath || !is_file($compilerLogsFilepath)) { return []; } @@ -241,7 +218,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte return $logs; } - private function sanitizeLogs(array $logs) + private function sanitizeLogs(array $logs): array { $sanitizedLogs = []; $silencedLogs = []; @@ -273,7 +250,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte continue; } - $errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); + $errorId = hash('xxh128', "{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); if (isset($sanitizedLogs[$errorId])) { ++$sanitizedLogs[$errorId]['errorCount']; diff --git a/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php b/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php index 53a1f9e44..8b8894367 100644 --- a/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php @@ -26,18 +26,12 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte $this->reset(); } - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { $this->updateMemoryUsage(); } - /** - * {@inheritdoc} - */ - public function reset() + public function reset(): void { $this->data = [ 'memory' => 0, @@ -45,10 +39,7 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte ]; } - /** - * {@inheritdoc} - */ - public function lateCollect() + public function lateCollect(): void { $this->updateMemoryUsage(); } @@ -58,31 +49,22 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte return $this->data['memory']; } - /** - * @return int|float - */ - public function getMemoryLimit() + public function getMemoryLimit(): int|float { return $this->data['memory_limit']; } - public function updateMemoryUsage() + public function updateMemoryUsage(): void { $this->data['memory'] = memory_get_peak_usage(true); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'memory'; } - /** - * @return int|float - */ - private function convertToBytes(string $memoryLimit) + private function convertToBytes(string $memoryLimit): int|float { if ('-1' === $memoryLimit) { return -1; @@ -100,11 +82,11 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte switch (substr($memoryLimit, -1)) { case 't': $max *= 1024; - // no break + // no break case 'g': $max *= 1024; - // no break + // no break case 'm': $max *= 1024; - // no break + // no break case 'k': $max *= 1024; } diff --git a/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php b/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php index 5717000f2..eae5f24b7 100644 --- a/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -34,9 +34,9 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter /** * @var \SplObjectStorage */ - private $controllers; - private $sessionUsages = []; - private $requestStack; + private \SplObjectStorage $controllers; + private array $sessionUsages = []; + private ?RequestStack $requestStack; public function __construct(RequestStack $requestStack = null) { @@ -44,10 +44,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter $this->requestStack = $requestStack; } - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { // attributes are serialized and as they can be anything, they need to be converted to strings. $attributes = []; @@ -110,7 +107,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter 'session_metadata' => $sessionMetadata, 'session_attributes' => $sessionAttributes, 'session_usages' => array_values($this->sessionUsages), - 'stateless_check' => $this->requestStack && ($mainRequest = $this->requestStack->getMainRequest()) && $mainRequest->attributes->get('_stateless', false), + 'stateless_check' => $this->requestStack?->getMainRequest()?->attributes->get('_stateless') ?? false, 'flashes' => $flashes, 'path_info' => $request->getPathInfo(), 'controller' => 'n/a', @@ -139,7 +136,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter continue; } if ('request_headers' === $key || 'response_headers' === $key) { - $this->data[$key] = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); + $this->data[$key] = array_map(fn ($v) => isset($v[0]) && !isset($v[1]) ? $v[0] : $v, $value); } } @@ -176,108 +173,144 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter } } - public function lateCollect() + public function lateCollect(): void { $this->data = $this->cloneVar($this->data); } - public function reset() + public function reset(): void { - $this->data = []; + parent::reset(); $this->controllers = new \SplObjectStorage(); $this->sessionUsages = []; } - public function getMethod() + public function getMethod(): string { return $this->data['method']; } - public function getPathInfo() + public function getPathInfo(): string { return $this->data['path_info']; } + /** + * @return ParameterBag + */ public function getRequestRequest() { return new ParameterBag($this->data['request_request']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestQuery() { return new ParameterBag($this->data['request_query']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestFiles() { return new ParameterBag($this->data['request_files']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestHeaders() { return new ParameterBag($this->data['request_headers']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestServer(bool $raw = false) { return new ParameterBag($this->data['request_server']->getValue($raw)); } + /** + * @return ParameterBag + */ public function getRequestCookies(bool $raw = false) { return new ParameterBag($this->data['request_cookies']->getValue($raw)); } + /** + * @return ParameterBag + */ public function getRequestAttributes() { return new ParameterBag($this->data['request_attributes']->getValue()); } + /** + * @return ParameterBag + */ public function getResponseHeaders() { return new ParameterBag($this->data['response_headers']->getValue()); } + /** + * @return ParameterBag + */ public function getResponseCookies() { return new ParameterBag($this->data['response_cookies']->getValue()); } - public function getSessionMetadata() + public function getSessionMetadata(): array { return $this->data['session_metadata']->getValue(); } - public function getSessionAttributes() + public function getSessionAttributes(): array { return $this->data['session_attributes']->getValue(); } - public function getStatelessCheck() + public function getStatelessCheck(): bool { return $this->data['stateless_check']; } - public function getSessionUsages() + public function getSessionUsages(): Data|array { return $this->data['session_usages']; } - public function getFlashes() + public function getFlashes(): array { return $this->data['flashes']->getValue(); } + /** + * @return string|resource + */ public function getContent() { return $this->data['content']; } + /** + * @return bool + */ public function isJsonRequest() { return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']); } + /** + * @return string|null + */ public function getPrettyJson() { $decoded = json_decode($this->getContent()); @@ -285,31 +318,34 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return \JSON_ERROR_NONE === json_last_error() ? json_encode($decoded, \JSON_PRETTY_PRINT) : null; } - public function getContentType() + public function getContentType(): string { return $this->data['content_type']; } - public function getStatusText() + public function getStatusText(): string { return $this->data['status_text']; } - public function getStatusCode() + public function getStatusCode(): int { return $this->data['status_code']; } - public function getFormat() + public function getFormat(): string { return $this->data['format']; } - public function getLocale() + public function getLocale(): string { return $this->data['locale']; } + /** + * @return ParameterBag + */ public function getDotenvVars() { return new ParameterBag($this->data['dotenv_vars']->getValue()); @@ -325,7 +361,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return $this->data['route']; } - public function getIdentifier() + public function getIdentifier(): string { return $this->data['identifier']; } @@ -346,7 +382,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter * @return array|string|Data The controller as a string or array of data * with keys 'class', 'method', 'file' and 'line' */ - public function getController() + public function getController(): array|string|Data { return $this->data['controller']; } @@ -357,22 +393,22 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter * @return array|Data|false A legacy array of data from the previous redirection response * or false otherwise */ - public function getRedirect() + public function getRedirect(): array|Data|false { return $this->data['redirect'] ?? false; } - public function getForwardToken() + public function getForwardToken(): ?string { return $this->data['forward_token'] ?? null; } - public function onKernelController(ControllerEvent $event) + public function onKernelController(ControllerEvent $event): void { $this->controllers[$event->getRequest()] = $event->getController(); } - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; @@ -391,9 +427,6 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter ]; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'request'; @@ -431,11 +464,9 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter } /** - * @param string|object|array|null $controller The controller to parse - * * @return array|string An array of controller data or a simple string */ - private function parseController($controller) + private function parseController(array|object|string|null $controller): array|string { if (\is_string($controller) && str_contains($controller, '::')) { $controller = explode('::', $controller); @@ -451,7 +482,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter 'file' => $r->getFileName(), 'line' => $r->getStartLine(), ]; - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { if (\is_callable($controller)) { // using __call or __callStatic return [ diff --git a/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php b/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php index 372ede037..444138da7 100644 --- a/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php @@ -32,11 +32,9 @@ class RouterDataCollector extends DataCollector } /** - * {@inheritdoc} - * * @final */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if ($response instanceof RedirectResponse) { $this->data['redirect'] = true; @@ -50,6 +48,9 @@ class RouterDataCollector extends DataCollector unset($this->controllers[$request]); } + /** + * @return void + */ public function reset() { $this->controllers = new \SplObjectStorage(); @@ -61,13 +62,18 @@ class RouterDataCollector extends DataCollector ]; } - protected function guessRoute(Request $request, $controller) + /** + * @return string + */ + protected function guessRoute(Request $request, string|object|array $controller) { return 'n/a'; } /** * Remembers the controller associated to each request. + * + * @return void */ public function onKernelController(ControllerEvent $event) { @@ -77,31 +83,22 @@ class RouterDataCollector extends DataCollector /** * @return bool Whether this request will result in a redirect */ - public function getRedirect() + public function getRedirect(): bool { return $this->data['redirect']; } - /** - * @return string|null - */ - public function getTargetUrl() + public function getTargetUrl(): ?string { return $this->data['url']; } - /** - * @return string|null - */ - public function getTargetRoute() + public function getTargetRoute(): ?string { return $this->data['route']; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'router'; } diff --git a/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php b/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php index 43799060f..a8b7ead94 100644 --- a/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php @@ -24,19 +24,17 @@ use Symfony\Component\Stopwatch\StopwatchEvent; */ class TimeDataCollector extends DataCollector implements LateDataCollectorInterface { - private $kernel; - private $stopwatch; + private ?KernelInterface $kernel; + private ?Stopwatch $stopwatch; public function __construct(KernelInterface $kernel = null, Stopwatch $stopwatch = null) { $this->kernel = $kernel; $this->stopwatch = $stopwatch; + $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; } - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if (null !== $this->kernel) { $startTime = $this->kernel->getStartTime(); @@ -52,22 +50,14 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf ]; } - /** - * {@inheritdoc} - */ - public function reset() + public function reset(): void { - $this->data = []; + $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; - if (null !== $this->stopwatch) { - $this->stopwatch->reset(); - } + $this->stopwatch?->reset(); } - /** - * {@inheritdoc} - */ - public function lateCollect() + public function lateCollect(): void { if (null !== $this->stopwatch && isset($this->data['token'])) { $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); @@ -78,7 +68,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf /** * @param StopwatchEvent[] $events The request events */ - public function setEvents(array $events) + public function setEvents(array $events): void { foreach ($events as $event) { $event->ensureStopped(); @@ -133,9 +123,6 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf return $this->data['stopwatch_installed']; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'time'; diff --git a/lib/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php b/lib/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php new file mode 100644 index 000000000..49f188c22 --- /dev/null +++ b/lib/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Psr\Log\LoggerInterface; +use Symfony\Component\ErrorHandler\ErrorHandler; + +/** + * Configures the error handler. + * + * @final + * + * @internal + */ +class ErrorHandlerConfigurator +{ + private ?LoggerInterface $logger; + private ?LoggerInterface $deprecationLogger; + private array|int|null $levels; + private ?int $throwAt; + private bool $scream; + private bool $scope; + + /** + * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param bool $scope Enables/disables scoping mode + */ + public function __construct(LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, LoggerInterface $deprecationLogger = null) + { + $this->logger = $logger; + $this->levels = $levels ?? \E_ALL; + $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); + $this->scream = $scream; + $this->scope = $scope; + $this->deprecationLogger = $deprecationLogger; + } + + /** + * Configures the error handler. + */ + public function configure(ErrorHandler $handler): void + { + if ($this->logger || $this->deprecationLogger) { + $this->setDefaultLoggers($handler); + if (\is_array($this->levels)) { + $levels = 0; + foreach ($this->levels as $type => $log) { + $levels |= $type; + } + } else { + $levels = $this->levels; + } + + if ($this->scream) { + $handler->screamAt($levels); + } + if ($this->scope) { + $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); + } else { + $handler->scopeAt(0, true); + } + $this->logger = $this->deprecationLogger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); + } + } + + private function setDefaultLoggers(ErrorHandler $handler): void + { + if (\is_array($this->levels)) { + $levelsDeprecatedOnly = []; + $levelsWithoutDeprecated = []; + foreach ($this->levels as $type => $log) { + if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) { + $levelsDeprecatedOnly[$type] = $log; + } else { + $levelsWithoutDeprecated[$type] = $log; + } + } + } else { + $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED); + $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED; + } + + $defaultLoggerLevels = $this->levels; + if ($this->deprecationLogger && $levelsDeprecatedOnly) { + $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly); + $defaultLoggerLevels = $levelsWithoutDeprecated; + } + + if ($this->logger && $defaultLoggerLevels) { + $handler->setDefaultLogger($this->logger, $defaultLoggerLevels); + } + } +} diff --git a/lib/symfony/http-kernel/Debug/FileLinkFormatter.php b/lib/symfony/http-kernel/Debug/FileLinkFormatter.php index 9ac688cc5..23ced22fc 100644 --- a/lib/symfony/http-kernel/Debug/FileLinkFormatter.php +++ b/lib/symfony/http-kernel/Debug/FileLinkFormatter.php @@ -11,106 +11,21 @@ namespace Symfony\Component\HttpKernel\Debug; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter as ErrorHandlerFileLinkFormatter; -/** - * Formats debug file links. - * - * @author Jérémy Romey - * - * @final - */ -class FileLinkFormatter -{ - private const FORMATS = [ - 'textmate' => 'txmt://open?url=file://%f&line=%l', - 'macvim' => 'mvim://open?url=file://%f&line=%l', - 'emacs' => 'emacs://open?url=file://%f&line=%l', - 'sublime' => 'subl://open?url=file://%f&line=%l', - 'phpstorm' => 'phpstorm://open?file=%f&line=%l', - 'atom' => 'atom://core/open/file?filename=%f&line=%l', - 'vscode' => 'vscode://file/%f:%l', - ]; +trigger_deprecation('symfony/http-kernel', '6.4', 'The "%s" class is deprecated, use "%s" instead.', FileLinkFormatter::class, ErrorHandlerFileLinkFormatter::class); - private $fileLinkFormat; - private $requestStack; - private $baseDir; - private $urlFormat; +class_exists(ErrorHandlerFileLinkFormatter::class); +if (!class_exists(FileLinkFormatter::class, false)) { + class_alias(ErrorHandlerFileLinkFormatter::class, FileLinkFormatter::class); +} + +if (false) { /** - * @param string|array|null $fileLinkFormat - * @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand + * @deprecated since Symfony 6.4, use FileLinkFormatter from the ErrorHandler component instead */ - public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, $urlFormat = null) + class FileLinkFormatter { - if (!\is_array($fileLinkFormat) && $fileLinkFormat = (self::FORMATS[$fileLinkFormat] ?? $fileLinkFormat) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) { - $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); - $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); - } - - $this->fileLinkFormat = $fileLinkFormat; - $this->requestStack = $requestStack; - $this->baseDir = $baseDir; - $this->urlFormat = $urlFormat; - } - - public function format(string $file, int $line) - { - if ($fmt = $this->getFileLinkFormat()) { - for ($i = 1; isset($fmt[$i]); ++$i) { - if (str_starts_with($file, $k = $fmt[$i++])) { - $file = substr_replace($file, $fmt[$i], 0, \strlen($k)); - break; - } - } - - return strtr($fmt[0], ['%f' => $file, '%l' => $line]); - } - - return false; - } - - /** - * @internal - */ - public function __sleep(): array - { - $this->fileLinkFormat = $this->getFileLinkFormat(); - - return ['fileLinkFormat']; - } - - /** - * @internal - */ - public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString): ?string - { - try { - return $router->generate($routeName).$queryString; - } catch (\Throwable $e) { - return null; - } - } - - private function getFileLinkFormat() - { - if ($this->fileLinkFormat) { - return $this->fileLinkFormat; - } - - if ($this->requestStack && $this->baseDir && $this->urlFormat) { - $request = $this->requestStack->getMainRequest(); - - if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) { - return [ - $request->getSchemeAndHttpHost().$this->urlFormat, - $this->baseDir.\DIRECTORY_SEPARATOR, '', - ]; - } - } - - return null; } } diff --git a/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php b/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php index fd1cf9e58..d31ce7581 100644 --- a/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php +++ b/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php @@ -23,10 +23,7 @@ use Symfony\Component\HttpKernel\KernelEvents; */ class TraceableEventDispatcher extends BaseTraceableEventDispatcher { - /** - * {@inheritdoc} - */ - protected function beforeDispatch(string $eventName, object $event) + protected function beforeDispatch(string $eventName, object $event): void { switch ($eventName) { case KernelEvents::REQUEST: @@ -52,16 +49,13 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher // which must be caught. try { $this->stopwatch->openSection($sectionId); - } catch (\LogicException $e) { + } catch (\LogicException) { } break; } } - /** - * {@inheritdoc} - */ - protected function afterDispatch(string $eventName, object $event) + protected function afterDispatch(string $eventName, object $event): void { switch ($eventName) { case KernelEvents::CONTROLLER_ARGUMENTS: @@ -83,7 +77,7 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher } try { $this->stopwatch->stopSection($sectionId); - } catch (\LogicException $e) { + } catch (\LogicException) { } break; } diff --git a/lib/symfony/http-kernel/Debug/VirtualRequestStack.php b/lib/symfony/http-kernel/Debug/VirtualRequestStack.php new file mode 100644 index 000000000..ded9aae17 --- /dev/null +++ b/lib/symfony/http-kernel/Debug/VirtualRequestStack.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * A stack able to deal with virtual requests. + * + * @internal + * + * @author Jules Pietri + */ +final class VirtualRequestStack extends RequestStack +{ + public function __construct( + private readonly RequestStack $decorated, + ) { + } + + public function push(Request $request): void + { + if ($request->attributes->has('_virtual_type')) { + if ($this->decorated->getCurrentRequest()) { + throw new \LogicException('Cannot mix virtual and HTTP requests.'); + } + + parent::push($request); + + return; + } + + $this->decorated->push($request); + } + + public function pop(): ?Request + { + return $this->decorated->pop() ?? parent::pop(); + } + + public function getCurrentRequest(): ?Request + { + return $this->decorated->getCurrentRequest() ?? parent::getCurrentRequest(); + } + + public function getMainRequest(): ?Request + { + return $this->decorated->getMainRequest() ?? parent::getMainRequest(); + } + + public function getParentRequest(): ?Request + { + return $this->decorated->getParentRequest() ?? parent::getParentRequest(); + } +} diff --git a/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php index 4bb60b41f..1924b1ddb 100644 --- a/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php +++ b/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; use Composer\Autoload\ClassLoader; -use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\ErrorHandler\DebugClassLoader; @@ -25,7 +24,7 @@ use Symfony\Component\HttpKernel\Kernel; */ class AddAnnotatedClassesToCachePass implements CompilerPassInterface { - private $kernel; + private Kernel $kernel; public function __construct(Kernel $kernel) { @@ -33,7 +32,7 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface } /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -93,7 +92,7 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface continue; } - if ($function[0] instanceof DebugClassLoader || $function[0] instanceof LegacyDebugClassLoader) { + if ($function[0] instanceof DebugClassLoader) { $function = $function[0]->getClassLoader(); } @@ -117,7 +116,7 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface $regex = strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']); // If this class does not end by a slash, anchor the end - if ('\\' !== substr($regex, -1)) { + if (!str_ends_with($regex, '\\')) { $regex .= '$'; } diff --git a/lib/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php b/lib/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php index 072c35f1c..12d468cf0 100644 --- a/lib/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php +++ b/lib/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php @@ -27,16 +27,15 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ abstract class ConfigurableExtension extends Extension { - /** - * {@inheritdoc} - */ - final public function load(array $configs, ContainerBuilder $container) + final public function load(array $configs, ContainerBuilder $container): void { $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); } /** * Configures the passed container according to the merged configuration. + * + * @return void */ abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); } diff --git a/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php index d925ed6b0..d3b157418 100644 --- a/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -12,6 +12,8 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -28,41 +30,44 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; - private $argumentResolverService; - private $argumentValueResolverTag; - private $traceableResolverStopwatch; - - public function __construct(string $argumentResolverService = 'argument_resolver', string $argumentValueResolverTag = 'controller.argument_value_resolver', string $traceableResolverStopwatch = 'debug.stopwatch') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->argumentResolverService = $argumentResolverService; - $this->argumentValueResolverTag = $argumentValueResolverTag; - $this->traceableResolverStopwatch = $traceableResolverStopwatch; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->argumentResolverService)) { + if (!$container->hasDefinition('argument_resolver')) { return; } - $resolvers = $this->findAndSortTaggedServices($this->argumentValueResolverTag, $container); + $definitions = $container->getDefinitions(); + $namedResolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.targeted_value_resolver', 'name', needsIndexes: true), $container); + $resolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.argument_value_resolver', 'name', needsIndexes: true), $container); - if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has($this->traceableResolverStopwatch)) { - foreach ($resolvers as $resolverReference) { - $id = (string) $resolverReference; - $container->register("debug.$id", TraceableValueResolver::class) - ->setDecoratedService($id) - ->setArguments([new Reference("debug.$id.inner"), new Reference($this->traceableResolverStopwatch)]); + foreach ($resolvers as $name => $resolver) { + if ($definitions[(string) $resolver]->hasTag('controller.targeted_value_resolver')) { + unset($resolvers[$name]); + } else { + $namedResolvers[$name] ??= clone $resolver; + } + } + + if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has('debug.stopwatch')) { + foreach ($resolvers as $name => $resolver) { + $resolvers[$name] = new Reference('.debug.value_resolver.'.$resolver); + $container->register('.debug.value_resolver.'.$resolver, TraceableValueResolver::class) + ->setArguments([$resolver, new Reference('debug.stopwatch')]); + } + foreach ($namedResolvers as $name => $resolver) { + $namedResolvers[$name] = new Reference('.debug.value_resolver.'.$resolver); + $container->register('.debug.value_resolver.'.$resolver, TraceableValueResolver::class) + ->setArguments([$resolver, new Reference('debug.stopwatch')]); } } $container - ->getDefinition($this->argumentResolverService) - ->replaceArgument(1, new IteratorArgument($resolvers)) + ->getDefinition('argument_resolver') + ->replaceArgument(1, new IteratorArgument(array_values($resolvers))) + ->setArgument(2, new ServiceLocatorArgument($namedResolvers)) ; } } diff --git a/lib/symfony/http-kernel/DependencyInjection/Extension.php b/lib/symfony/http-kernel/DependencyInjection/Extension.php index 4090fd822..d72efa172 100644 --- a/lib/symfony/http-kernel/DependencyInjection/Extension.php +++ b/lib/symfony/http-kernel/DependencyInjection/Extension.php @@ -20,14 +20,12 @@ use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; */ abstract class Extension extends BaseExtension { - private $annotatedClasses = []; + private array $annotatedClasses = []; /** * Gets the annotated classes to cache. - * - * @return array */ - public function getAnnotatedClassesToCompile() + public function getAnnotatedClassesToCompile(): array { return $this->annotatedClasses; } @@ -36,6 +34,8 @@ abstract class Extension extends BaseExtension * Adds annotated classes to the class cache. * * @param array $annotatedClasses An array of class patterns + * + * @return void */ public function addAnnotatedClassesToCompile(array $annotatedClasses) { diff --git a/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php index f26baeca9..f41d58b81 100644 --- a/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php @@ -25,28 +25,18 @@ use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; */ class FragmentRendererPass implements CompilerPassInterface { - private $handlerService; - private $rendererTag; - - public function __construct(string $handlerService = 'fragment.handler', string $rendererTag = 'kernel.fragment_renderer') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->handlerService = $handlerService; - $this->rendererTag = $rendererTag; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->handlerService)) { + if (!$container->hasDefinition('fragment.handler')) { return; } - $definition = $container->getDefinition($this->handlerService); + $definition = $container->getDefinition('fragment.handler'); $renderers = []; - foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $id => $tags) { + foreach ($container->findTaggedServiceIds('kernel.fragment_renderer', true) as $id => $tags) { $def = $container->getDefinition($id); $class = $container->getParameterBag()->resolveValue($def->getClass()); diff --git a/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php index f25328704..944b5d0bc 100644 --- a/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php +++ b/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php @@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\Fragment\FragmentHandler; /** @@ -22,12 +23,12 @@ use Symfony\Component\HttpKernel\Fragment\FragmentHandler; */ class LazyLoadingFragmentHandler extends FragmentHandler { - private $container; + private ContainerInterface $container; /** * @var array */ - private $initialized = []; + private array $initialized = []; public function __construct(ContainerInterface $container, RequestStack $requestStack, bool $debug = false) { @@ -36,10 +37,7 @@ class LazyLoadingFragmentHandler extends FragmentHandler parent::__construct($requestStack, [], $debug); } - /** - * {@inheritdoc} - */ - public function render($uri, string $renderer = 'inline', array $options = []) + public function render(string|ControllerReference $uri, string $renderer = 'inline', array $options = []): ?string { if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) { $this->addRenderer($this->container->get($renderer)); diff --git a/lib/symfony/http-kernel/DependencyInjection/LoggerPass.php b/lib/symfony/http-kernel/DependencyInjection/LoggerPass.php index b6df1f6e6..6270875be 100644 --- a/lib/symfony/http-kernel/DependencyInjection/LoggerPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/LoggerPass.php @@ -14,6 +14,8 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Log\Logger; /** @@ -24,18 +26,23 @@ use Symfony\Component\HttpKernel\Log\Logger; class LoggerPass implements CompilerPassInterface { /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { - $container->setAlias(LoggerInterface::class, 'logger') - ->setPublic(false); + $container->setAlias(LoggerInterface::class, 'logger'); if ($container->has('logger')) { return; } + if ($debug = $container->getParameter('kernel.debug')) { + $debug = $container->hasParameter('kernel.runtime_mode.web') + ? $container->getParameter('kernel.runtime_mode.web') + : !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true); + } + $container->register('logger', Logger::class) - ->setPublic(false); + ->setArguments([null, null, null, new Reference(RequestStack::class), $debug]); } } diff --git a/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php b/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php index 5f0f0d8de..d65dbbab0 100644 --- a/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass { - private $extensions; + private array $extensions; /** * @param string[] $extensions @@ -31,7 +31,7 @@ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPas $this->extensions = $extensions; } - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { foreach ($this->extensions as $extension) { if (!\count($container->getExtensionConfig($extension))) { diff --git a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index 3dbaff564..d43c6a3ae 100644 --- a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -11,21 +11,21 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\VarExporter\ProxyHelper; /** * Creates the service-locators required by ServiceValueResolver. @@ -34,26 +34,12 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface; */ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface { - private $resolverServiceId; - private $controllerTag; - private $controllerLocator; - private $notTaggedControllerResolverServiceId; - - public function __construct(string $resolverServiceId = 'argument_resolver.service', string $controllerTag = 'controller.service_arguments', string $controllerLocator = 'argument_resolver.controller_locator', string $notTaggedControllerResolverServiceId = 'argument_resolver.not_tagged_controller') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->resolverServiceId = $resolverServiceId; - $this->controllerTag = $controllerTag; - $this->controllerLocator = $controllerLocator; - $this->notTaggedControllerResolverServiceId = $notTaggedControllerResolverServiceId; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { - if (false === $container->hasDefinition($this->resolverServiceId) && false === $container->hasDefinition($this->notTaggedControllerResolverServiceId)) { + if (!$container->hasDefinition('argument_resolver.service') && !$container->hasDefinition('argument_resolver.not_tagged_controller')) { return; } @@ -67,7 +53,9 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface } } - foreach ($container->findTaggedServiceIds($this->controllerTag, true) as $id => $tags) { + $emptyAutowireAttributes = class_exists(Autowire::class) ? null : []; + + foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) { $def = $container->getDefinition($id); $def->setPublic(true); $class = $def->getClass(); @@ -85,13 +73,12 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } - $isContainerAware = $r->implementsInterface(ContainerAwareInterface::class) || is_subclass_of($class, AbstractController::class); // get regular public methods $methods = []; $arguments = []; foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) { - if ('setContainer' === $r->name && $isContainerAware) { + if ('setContainer' === $r->name) { continue; } if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) { @@ -107,11 +94,11 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface } foreach (['action', 'argument', 'id'] as $k) { if (!isset($attributes[$k][0])) { - throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "%s" %s for service "%s".', $k, $this->controllerTag, json_encode($attributes, \JSON_UNESCAPED_UNICODE), $id)); + throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "controller.service_arguments" %s for service "%s".', $k, json_encode($attributes, \JSON_UNESCAPED_UNICODE), $id)); } } if (!isset($methods[$action = strtolower($attributes['action'])])) { - throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "%s" for service "%s": no public "%s()" method found on class "%s".', $this->controllerTag, $id, $attributes['action'], $class)); + throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "controller.service_arguments" for service "%s": no public "%s()" method found on class "%s".', $id, $attributes['action'], $class)); } [$r, $parameters] = $methods[$action]; $found = false; @@ -127,7 +114,7 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface } if (!$found) { - throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $this->controllerTag, $id, $r->name, $attributes['argument'], $class)); + throw new InvalidArgumentException(sprintf('Invalid "controller.service_arguments" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $id, $r->name, $attributes['argument'], $class)); } } @@ -138,15 +125,16 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface $args = []; foreach ($parameters as $p) { /** @var \ReflectionParameter $p */ - $type = ltrim($target = (string) ProxyHelper::getTypeHint($r, $p), '\\'); + $type = preg_replace('/(^|[(|&])\\\\/', '\1', $target = ltrim(ProxyHelper::exportType($p) ?? '', '?')); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + $autowireAttributes = $autowire ? $emptyAutowireAttributes : []; if (isset($arguments[$r->name][$p->name])) { $target = $arguments[$r->name][$p->name]; if ('?' !== $target[0]) { $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; } elseif ('' === $target = (string) substr($target, 1)) { - throw new InvalidArgumentException(sprintf('A "%s" tag must have non-empty "id" attributes for service "%s".', $this->controllerTag, $id)); + throw new InvalidArgumentException(sprintf('A "controller.service_arguments" tag must have non-empty "id" attributes for service "%s".', $id)); } elseif ($p->allowsNull() && !$p->isOptional()) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } @@ -156,17 +144,10 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface [$bindingValue, $bindingId, , $bindingType, $bindingFile] = $binding->getValues(); $binding->setValues([$bindingValue, $bindingId, true, $bindingType, $bindingFile]); - if (!$bindingValue instanceof Reference) { - $args[$p->name] = new Reference('.value.'.$container->hash($bindingValue)); - $container->register((string) $args[$p->name], 'mixed') - ->setFactory('current') - ->addArgument([$bindingValue]); - } else { - $args[$p->name] = $bindingValue; - } + $args[$p->name] = $bindingValue; continue; - } elseif (!$type || !$autowire || '\\' !== $target[0]) { + } elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) { continue; } elseif (is_subclass_of($type, \UnitEnum::class)) { // do not attempt to register enum typed arguments if not already present in bindings @@ -179,6 +160,26 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface continue; } + if ($autowireAttributes) { + $attribute = $autowireAttributes[0]->newInstance(); + $value = $parameterBag->resolveValue($attribute->value); + + if ($attribute instanceof AutowireCallable) { + $value = $attribute->buildDefinition($value, $type, $p); + } + + if ($value instanceof Reference) { + $args[$p->name] = $type ? new TypedReference($value, $type, $invalidBehavior, $p->name) : new Reference($value, $invalidBehavior); + } else { + $args[$p->name] = new Reference('.value.'.$container->hash($value)); + $container->register((string) $args[$p->name], 'mixed') + ->setFactory('current') + ->addArgument([$value]); + } + + continue; + } + if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) { $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); @@ -192,7 +193,7 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface $args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE); } else { - $target = ltrim($target, '\\'); + $target = preg_replace('/(^|[(|&])\\\\/', '\1', $target); $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior); } } @@ -209,16 +210,16 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface $controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers); - if ($container->hasDefinition($this->resolverServiceId)) { - $container->getDefinition($this->resolverServiceId) + if ($container->hasDefinition('argument_resolver.service')) { + $container->getDefinition('argument_resolver.service') ->replaceArgument(0, $controllerLocatorRef); } - if ($container->hasDefinition($this->notTaggedControllerResolverServiceId)) { - $container->getDefinition($this->notTaggedControllerResolverServiceId) + if ($container->hasDefinition('argument_resolver.not_tagged_controller')) { + $container->getDefinition('argument_resolver.not_tagged_controller') ->replaceArgument(0, $controllerLocatorRef); } - $container->setAlias($this->controllerLocator, (string) $controllerLocatorRef); + $container->setAlias('argument_resolver.controller_locator', (string) $controllerLocatorRef); } } diff --git a/lib/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php b/lib/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php index f0b801b8d..2a01365bd 100644 --- a/lib/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php @@ -23,39 +23,29 @@ use Symfony\Component\DependencyInjection\Reference; */ class RegisterLocaleAwareServicesPass implements CompilerPassInterface { - private $listenerServiceId; - private $localeAwareTag; - - public function __construct(string $listenerServiceId = 'locale_aware_listener', string $localeAwareTag = 'kernel.locale_aware') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->listenerServiceId = $listenerServiceId; - $this->localeAwareTag = $localeAwareTag; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->listenerServiceId)) { + if (!$container->hasDefinition('locale_aware_listener')) { return; } $services = []; - foreach ($container->findTaggedServiceIds($this->localeAwareTag) as $id => $tags) { + foreach ($container->findTaggedServiceIds('kernel.locale_aware') as $id => $tags) { $services[] = new Reference($id); } if (!$services) { - $container->removeDefinition($this->listenerServiceId); + $container->removeDefinition('locale_aware_listener'); return; } $container - ->getDefinition($this->listenerServiceId) + ->getDefinition('locale_aware_listener') ->setArgument(0, new IteratorArgument($services)) ; } diff --git a/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php index 2d077a0cb..7a21fe0e5 100644 --- a/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -21,20 +21,12 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface { - private $controllerLocator; - - public function __construct(string $controllerLocator = 'argument_resolver.controller_locator') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->controllerLocator = $controllerLocator; - } - + /** + * @return void + */ public function process(ContainerBuilder $container) { - $controllerLocator = $container->findDefinition($this->controllerLocator); + $controllerLocator = $container->findDefinition('argument_resolver.controller_locator'); $controllers = $controllerLocator->getArgument(0); foreach ($controllers as $controller => $argumentRef) { diff --git a/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php b/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php index 2e4cd6927..da9f8d632 100644 --- a/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php +++ b/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php @@ -23,19 +23,8 @@ use Symfony\Component\DependencyInjection\Reference; */ class ResettableServicePass implements CompilerPassInterface { - private $tagName; - - public function __construct(string $tagName = 'kernel.reset') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->tagName = $tagName; - } - /** - * {@inheritdoc} + * @return void */ public function process(ContainerBuilder $container) { @@ -45,12 +34,12 @@ class ResettableServicePass implements CompilerPassInterface $services = $methods = []; - foreach ($container->findTaggedServiceIds($this->tagName, true) as $id => $tags) { + foreach ($container->findTaggedServiceIds('kernel.reset', true) as $id => $tags) { $services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE); foreach ($tags as $attributes) { if (!isset($attributes['method'])) { - throw new RuntimeException(sprintf('Tag "%s" requires the "method" attribute to be set.', $this->tagName)); + throw new RuntimeException(sprintf('Tag "kernel.reset" requires the "method" attribute to be set on service "%s".', $id)); } if (!isset($methods[$id])) { diff --git a/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php b/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php index 0063deca3..b38ab5658 100644 --- a/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php +++ b/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; +use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Component\VarExporter\LazyObjectInterface; use Symfony\Contracts\Service\ResetInterface; /** @@ -23,8 +25,8 @@ use Symfony\Contracts\Service\ResetInterface; */ class ServicesResetter implements ResetInterface { - private $resettableServices; - private $resetMethods; + private \Traversable $resettableServices; + private array $resetMethods; /** * @param \Traversable $resettableServices @@ -36,9 +38,17 @@ class ServicesResetter implements ResetInterface $this->resetMethods = $resetMethods; } - public function reset() + public function reset(): void { foreach ($this->resettableServices as $id => $service) { + if ($service instanceof LazyObjectInterface && !$service->isLazyObjectInitialized(true)) { + continue; + } + + if ($service instanceof LazyLoadingInterface && !$service->isProxyInitialized()) { + continue; + } + foreach ((array) $this->resetMethods[$id] as $resetMethod) { if ('?' === $resetMethod[0] && !method_exists($service, $resetMethod = substr($resetMethod, 1))) { continue; diff --git a/lib/symfony/http-kernel/Event/ControllerArgumentsEvent.php b/lib/symfony/http-kernel/Event/ControllerArgumentsEvent.php index d075ee90b..c90b7706f 100644 --- a/lib/symfony/http-kernel/Event/ControllerArgumentsEvent.php +++ b/lib/symfony/http-kernel/Event/ControllerArgumentsEvent.php @@ -28,25 +28,34 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ final class ControllerArgumentsEvent extends KernelEvent { - private $controller; - private $arguments; + private ControllerEvent $controllerEvent; + private array $arguments; + private array $namedArguments; - public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, ?int $requestType) + public function __construct(HttpKernelInterface $kernel, callable|ControllerEvent $controller, array $arguments, Request $request, ?int $requestType) { parent::__construct($kernel, $request, $requestType); - $this->controller = $controller; + if (!$controller instanceof ControllerEvent) { + $controller = new ControllerEvent($kernel, $controller, $request, $requestType); + } + + $this->controllerEvent = $controller; $this->arguments = $arguments; } public function getController(): callable { - return $this->controller; + return $this->controllerEvent->getController(); } - public function setController(callable $controller) + /** + * @param array>|null $attributes + */ + public function setController(callable $controller, array $attributes = null): void { - $this->controller = $controller; + $this->controllerEvent->setController($controller, $attributes); + unset($this->namedArguments); } public function getArguments(): array @@ -54,8 +63,47 @@ final class ControllerArgumentsEvent extends KernelEvent return $this->arguments; } - public function setArguments(array $arguments) + public function setArguments(array $arguments): void { $this->arguments = $arguments; + unset($this->namedArguments); + } + + public function getNamedArguments(): array + { + if (isset($this->namedArguments)) { + return $this->namedArguments; + } + + $namedArguments = []; + $arguments = $this->arguments; + + foreach ($this->controllerEvent->getControllerReflector()->getParameters() as $i => $param) { + if ($param->isVariadic()) { + $namedArguments[$param->name] = \array_slice($arguments, $i); + break; + } + if (\array_key_exists($i, $arguments)) { + $namedArguments[$param->name] = $arguments[$i]; + } elseif ($param->isDefaultvalueAvailable()) { + $namedArguments[$param->name] = $param->getDefaultValue(); + } + } + + return $this->namedArguments = $namedArguments; + } + + /** + * @template T of class-string|null + * + * @param T $className + * + * @return array>|list + * + * @psalm-return (T is null ? array> : list) + */ + public function getAttributes(string $className = null): array + { + return $this->controllerEvent->getAttributes($className); } } diff --git a/lib/symfony/http-kernel/Event/ControllerEvent.php b/lib/symfony/http-kernel/Event/ControllerEvent.php index da88800e2..239b00512 100644 --- a/lib/symfony/http-kernel/Event/ControllerEvent.php +++ b/lib/symfony/http-kernel/Event/ControllerEvent.php @@ -27,7 +27,9 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ final class ControllerEvent extends KernelEvent { - private $controller; + private string|array|object $controller; + private \ReflectionFunctionAbstract $controllerReflector; + private array $attributes; public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType) { @@ -41,8 +43,71 @@ final class ControllerEvent extends KernelEvent return $this->controller; } - public function setController(callable $controller): void + public function getControllerReflector(): \ReflectionFunctionAbstract { + return $this->controllerReflector; + } + + /** + * @param array>|null $attributes + */ + public function setController(callable $controller, array $attributes = null): void + { + if (null !== $attributes) { + $this->attributes = $attributes; + } + + if (isset($this->controller) && ($controller instanceof \Closure ? $controller == $this->controller : $controller === $this->controller)) { + $this->controller = $controller; + + return; + } + + if (null === $attributes) { + unset($this->attributes); + } + + if (\is_array($controller) && method_exists(...$controller)) { + $this->controllerReflector = new \ReflectionMethod(...$controller); + } elseif (\is_string($controller) && str_contains($controller, '::')) { + $this->controllerReflector = new \ReflectionMethod($controller); + } else { + $this->controllerReflector = new \ReflectionFunction($controller(...)); + } + $this->controller = $controller; } + + /** + * @template T of class-string|null + * + * @param T $className + * + * @return array>|list + * + * @psalm-return (T is null ? array> : list) + */ + public function getAttributes(string $className = null): array + { + if (isset($this->attributes)) { + return null === $className ? $this->attributes : $this->attributes[$className] ?? []; + } + + if (\is_array($this->controller) && method_exists(...$this->controller)) { + $class = new \ReflectionClass($this->controller[0]); + } elseif (\is_string($this->controller) && false !== $i = strpos($this->controller, '::')) { + $class = new \ReflectionClass(substr($this->controller, 0, $i)); + } else { + $class = str_contains($this->controllerReflector->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $this->controllerReflector->getClosureCalledClass() : $this->controllerReflector->getClosureScopeClass()); + } + $this->attributes = []; + + foreach (array_merge($class?->getAttributes() ?? [], $this->controllerReflector->getAttributes()) as $attribute) { + if (class_exists($attribute->getName())) { + $this->attributes[$attribute->getName()][] = $attribute->newInstance(); + } + } + + return null === $className ? $this->attributes : $this->attributes[$className] ?? []; + } } diff --git a/lib/symfony/http-kernel/Event/ExceptionEvent.php b/lib/symfony/http-kernel/Event/ExceptionEvent.php index a18fbd31f..8bc25f9c3 100644 --- a/lib/symfony/http-kernel/Event/ExceptionEvent.php +++ b/lib/symfony/http-kernel/Event/ExceptionEvent.php @@ -29,12 +29,8 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ final class ExceptionEvent extends RequestEvent { - private $throwable; - - /** - * @var bool - */ - private $allowCustomResponseCode = false; + private \Throwable $throwable; + private bool $allowCustomResponseCode = false; public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, \Throwable $e) { diff --git a/lib/symfony/http-kernel/Event/KernelEvent.php b/lib/symfony/http-kernel/Event/KernelEvent.php index d9d425e11..e64cc419b 100644 --- a/lib/symfony/http-kernel/Event/KernelEvent.php +++ b/lib/symfony/http-kernel/Event/KernelEvent.php @@ -22,9 +22,9 @@ use Symfony\Contracts\EventDispatcher\Event; */ class KernelEvent extends Event { - private $kernel; - private $request; - private $requestType; + private HttpKernelInterface $kernel; + private Request $request; + private ?int $requestType; /** * @param int $requestType The request type the kernel is currently processing; one of @@ -39,20 +39,16 @@ class KernelEvent extends Event /** * Returns the kernel in which this event was thrown. - * - * @return HttpKernelInterface */ - public function getKernel() + public function getKernel(): HttpKernelInterface { return $this->kernel; } /** * Returns the request the kernel is currently processing. - * - * @return Request */ - public function getRequest() + public function getRequest(): Request { return $this->request; } @@ -63,7 +59,7 @@ class KernelEvent extends Event * @return int One of HttpKernelInterface::MAIN_REQUEST and * HttpKernelInterface::SUB_REQUEST */ - public function getRequestType() + public function getRequestType(): int { return $this->requestType; } @@ -75,18 +71,4 @@ class KernelEvent extends Event { return HttpKernelInterface::MAIN_REQUEST === $this->requestType; } - - /** - * Checks if this is a master request. - * - * @return bool - * - * @deprecated since symfony/http-kernel 5.3, use isMainRequest() instead - */ - public function isMasterRequest() - { - trigger_deprecation('symfony/http-kernel', '5.3', '"%s()" is deprecated, use "isMainRequest()" instead.', __METHOD__); - - return $this->isMainRequest(); - } } diff --git a/lib/symfony/http-kernel/Event/RequestEvent.php b/lib/symfony/http-kernel/Event/RequestEvent.php index 30ffcdcbd..b81a79b78 100644 --- a/lib/symfony/http-kernel/Event/RequestEvent.php +++ b/lib/symfony/http-kernel/Event/RequestEvent.php @@ -24,20 +24,20 @@ use Symfony\Component\HttpFoundation\Response; */ class RequestEvent extends KernelEvent { - private $response; + private ?Response $response = null; /** * Returns the response object. - * - * @return Response|null */ - public function getResponse() + public function getResponse(): ?Response { return $this->response; } /** * Sets a response and stops event propagation. + * + * @return void */ public function setResponse(Response $response) { @@ -48,10 +48,8 @@ class RequestEvent extends KernelEvent /** * Returns whether a response was set. - * - * @return bool */ - public function hasResponse() + public function hasResponse(): bool { return null !== $this->response; } diff --git a/lib/symfony/http-kernel/Event/ResponseEvent.php b/lib/symfony/http-kernel/Event/ResponseEvent.php index 1e56ebea2..4a57d989a 100644 --- a/lib/symfony/http-kernel/Event/ResponseEvent.php +++ b/lib/symfony/http-kernel/Event/ResponseEvent.php @@ -26,7 +26,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ final class ResponseEvent extends KernelEvent { - private $response; + private Response $response; public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, Response $response) { diff --git a/lib/symfony/http-kernel/Event/TerminateEvent.php b/lib/symfony/http-kernel/Event/TerminateEvent.php index 014ca535f..0caefdf4d 100644 --- a/lib/symfony/http-kernel/Event/TerminateEvent.php +++ b/lib/symfony/http-kernel/Event/TerminateEvent.php @@ -25,7 +25,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ final class TerminateEvent extends KernelEvent { - private $response; + private Response $response; public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) { diff --git a/lib/symfony/http-kernel/Event/ViewEvent.php b/lib/symfony/http-kernel/Event/ViewEvent.php index 88211da41..bf96985b2 100644 --- a/lib/symfony/http-kernel/Event/ViewEvent.php +++ b/lib/symfony/http-kernel/Event/ViewEvent.php @@ -25,36 +25,23 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ final class ViewEvent extends RequestEvent { - /** - * The return value of the controller. - * - * @var mixed - */ - private $controllerResult; + public readonly ?ControllerArgumentsEvent $controllerArgumentsEvent; + private mixed $controllerResult; - public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, $controllerResult) + public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, mixed $controllerResult, ControllerArgumentsEvent $controllerArgumentsEvent = null) { parent::__construct($kernel, $request, $requestType); $this->controllerResult = $controllerResult; + $this->controllerArgumentsEvent = $controllerArgumentsEvent; } - /** - * Returns the return value of the controller. - * - * @return mixed - */ - public function getControllerResult() + public function getControllerResult(): mixed { return $this->controllerResult; } - /** - * Assigns the return value of the controller. - * - * @param mixed $controllerResult The controller return value - */ - public function setControllerResult($controllerResult): void + public function setControllerResult(mixed $controllerResult): void { $this->controllerResult = $controllerResult; } diff --git a/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php b/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php index 27749b24b..2eb7c473f 100644 --- a/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php +++ b/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php @@ -17,7 +17,6 @@ use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\SessionUtils; -use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException; @@ -43,14 +42,13 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese { public const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl'; - protected $container; - private $sessionUsageStack = []; - private $debug; + protected ?ContainerInterface $container; + private bool $debug; /** * @var array */ - private $sessionOptions; + private array $sessionOptions; public function __construct(ContainerInterface $container = null, bool $debug = false, array $sessionOptions = []) { @@ -59,7 +57,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese $this->sessionOptions = $sessionOptions; } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; @@ -67,11 +65,13 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese $request = $event->getRequest(); if (!$request->hasSession()) { - // This variable prevents calling `$this->getSession()` twice in case the Request (and the below factory) is cloned - $sess = null; - $request->setSessionFactory(function () use (&$sess, $request) { + $request->setSessionFactory(function () use ($request) { + // Prevent calling `$this->getSession()` twice in case the Request (and the below factory) is cloned + static $sess; + if (!$sess) { $sess = $this->getSession(); + $request->setSession($sess); /* * For supporting sessions in php runtime with runners like roadrunner or swoole, the session @@ -88,12 +88,9 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese return $sess; }); } - - $session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null; - $this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : 0; } - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest() || (!$this->container->has('initialized_session') && !$event->getRequest()->hasSession())) { return; @@ -103,10 +100,10 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese $autoCacheControl = !$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER); // Always remove the internal header if present $response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER); - - if (!$session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : ($event->getRequest()->hasSession() ? $event->getRequest()->getSession() : null)) { + if (!$event->getRequest()->hasSession(true)) { return; } + $session = $event->getRequest()->getSession(); if ($session->isStarted()) { /* @@ -156,7 +153,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese $request = $event->getRequest(); $requestSessionCookieId = $request->cookies->get($sessionName); - $isSessionEmpty = $session->isEmpty() && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions + $isSessionEmpty = ($session instanceof Session ? $session->isEmpty() : !$session->all()) && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions if ($requestSessionCookieId && $isSessionEmpty) { // PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument // which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy @@ -195,7 +192,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese } } - if ($session instanceof Session ? $session->getUsageIndex() === end($this->sessionUsageStack) : !$session->isStarted()) { + if ($session instanceof Session ? 0 === $session->getUsageIndex() : !$session->isStarted()) { return; } @@ -221,24 +218,17 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese } } - public function onFinishRequest(FinishRequestEvent $event) - { - if ($event->isMainRequest()) { - array_pop($this->sessionUsageStack); - } - } - public function onSessionUsage(): void { if (!$this->debug) { return; } - if ($this->container && $this->container->has('session_collector')) { + if ($this->container?->has('session_collector')) { $this->container->get('session_collector')(); } - if (!$requestStack = $this->container && $this->container->has('request_stack') ? $this->container->get('request_stack') : null) { + if (!$requestStack = $this->container?->has('request_stack') ? $this->container->get('request_stack') : null) { return; } @@ -252,7 +242,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese return; } - if (!$session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : $requestStack->getCurrentRequest()->getSession()) { + if (!$session = $requestStack->getCurrentRequest()->getSession()) { return; } @@ -267,9 +257,8 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese { return [ KernelEvents::REQUEST => ['onKernelRequest', 128], - // low priority to come after regular response listeners, but higher than StreamedResponseListener + // low priority to come after regular response listeners KernelEvents::RESPONSE => ['onKernelResponse', -1000], - KernelEvents::FINISH_REQUEST => ['onFinishRequest'], ]; } @@ -289,10 +278,8 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese /** * Gets the session object. - * - * @return SessionInterface|null */ - abstract protected function getSession(); + abstract protected function getSession(): ?SessionInterface; private function getSessionOptions(array $sessionOptions): array { diff --git a/lib/symfony/http-kernel/EventListener/AbstractTestSessionListener.php b/lib/symfony/http-kernel/EventListener/AbstractTestSessionListener.php deleted file mode 100644 index 838c2944b..000000000 --- a/lib/symfony/http-kernel/EventListener/AbstractTestSessionListener.php +++ /dev/null @@ -1,120 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\EventListener; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Cookie; -use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\Event\ResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; - -trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated use "%s" instead.', AbstractTestSessionListener::class, AbstractSessionListener::class); - -/** - * TestSessionListener. - * - * Saves session in test environment. - * - * @author Bulat Shakirzyanov - * @author Fabien Potencier - * - * @internal - * - * @deprecated since Symfony 5.4, use AbstractSessionListener instead - */ -abstract class AbstractTestSessionListener implements EventSubscriberInterface -{ - private $sessionId; - private $sessionOptions; - - public function __construct(array $sessionOptions = []) - { - $this->sessionOptions = $sessionOptions; - } - - public function onKernelRequest(RequestEvent $event) - { - if (!$event->isMainRequest()) { - return; - } - - // bootstrap the session - if ($event->getRequest()->hasSession()) { - $session = $event->getRequest()->getSession(); - } elseif (!$session = $this->getSession()) { - return; - } - - $cookies = $event->getRequest()->cookies; - - if ($cookies->has($session->getName())) { - $this->sessionId = $cookies->get($session->getName()); - $session->setId($this->sessionId); - } - } - - /** - * Checks if session was initialized and saves if current request is the main request - * Runs on 'kernel.response' in test environment. - */ - public function onKernelResponse(ResponseEvent $event) - { - if (!$event->isMainRequest()) { - return; - } - - $request = $event->getRequest(); - if (!$request->hasSession()) { - return; - } - - $session = $request->getSession(); - if ($wasStarted = $session->isStarted()) { - $session->save(); - } - - if ($session instanceof Session ? !$session->isEmpty() || (null !== $this->sessionId && $session->getId() !== $this->sessionId) : $wasStarted) { - $params = session_get_cookie_params() + ['samesite' => null]; - foreach ($this->sessionOptions as $k => $v) { - if (str_starts_with($k, 'cookie_')) { - $params[substr($k, 7)] = $v; - } - } - - foreach ($event->getResponse()->headers->getCookies() as $cookie) { - if ($session->getName() === $cookie->getName() && $params['path'] === $cookie->getPath() && $params['domain'] == $cookie->getDomain()) { - return; - } - } - - $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'], false, $params['samesite'] ?: null)); - $this->sessionId = $session->getId(); - } - } - - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::REQUEST => ['onKernelRequest', 127], // AFTER SessionListener - KernelEvents::RESPONSE => ['onKernelResponse', -128], - ]; - } - - /** - * Gets the session object. - * - * @return SessionInterface|null - */ - abstract protected function getSession(); -} diff --git a/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php b/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php index 9e896adb3..d4ef3fe49 100644 --- a/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php +++ b/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php @@ -24,7 +24,7 @@ use Symfony\Component\HttpKernel\KernelEvents; */ class AddRequestFormatsListener implements EventSubscriberInterface { - protected $formats; + private array $formats; public function __construct(array $formats) { @@ -34,7 +34,7 @@ class AddRequestFormatsListener implements EventSubscriberInterface /** * Adds request formats. */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); foreach ($this->formats as $format => $mimeTypes) { @@ -42,9 +42,6 @@ class AddRequestFormatsListener implements EventSubscriberInterface } } - /** - * {@inheritdoc} - */ public static function getSubscribedEvents(): array { return [KernelEvents::REQUEST => ['onKernelRequest', 100]]; diff --git a/lib/symfony/http-kernel/EventListener/CacheAttributeListener.php b/lib/symfony/http-kernel/EventListener/CacheAttributeListener.php new file mode 100644 index 000000000..723e758cd --- /dev/null +++ b/lib/symfony/http-kernel/EventListener/CacheAttributeListener.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\Cache; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Handles HTTP cache headers configured via the Cache attribute. + * + * @author Fabien Potencier + */ +class CacheAttributeListener implements EventSubscriberInterface +{ + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $lastModified; + + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $etags; + + public function __construct( + private ?ExpressionLanguage $expressionLanguage = null, + ) { + $this->lastModified = new \SplObjectStorage(); + $this->etags = new \SplObjectStorage(); + } + + /** + * Handles HTTP validation headers. + * + * @return void + */ + public function onKernelControllerArguments(ControllerArgumentsEvent $event) + { + $request = $event->getRequest(); + + if (!\is_array($attributes = $request->attributes->get('_cache') ?? $event->getAttributes()[Cache::class] ?? null)) { + return; + } + + $request->attributes->set('_cache', $attributes); + $response = null; + $lastModified = null; + $etag = null; + + /** @var Cache[] $attributes */ + foreach ($attributes as $cache) { + if (null !== $cache->lastModified) { + $lastModified = $this->getExpressionLanguage()->evaluate($cache->lastModified, array_merge($request->attributes->all(), $event->getNamedArguments())); + ($response ??= new Response())->setLastModified($lastModified); + } + + if (null !== $cache->etag) { + $etag = hash('sha256', $this->getExpressionLanguage()->evaluate($cache->etag, array_merge($request->attributes->all(), $event->getNamedArguments()))); + ($response ??= new Response())->setEtag($etag); + } + } + + if ($response?->isNotModified($request)) { + $event->setController(static fn () => $response); + $event->stopPropagation(); + + return; + } + + if (null !== $etag) { + $this->etags[$request] = $etag; + } + if (null !== $lastModified) { + $this->lastModified[$request] = $lastModified; + } + } + + /** + * Modifies the response to apply HTTP cache headers when needed. + * + * @return void + */ + public function onKernelResponse(ResponseEvent $event) + { + $request = $event->getRequest(); + + /** @var Cache[] $attributes */ + if (!\is_array($attributes = $request->attributes->get('_cache'))) { + return; + } + $response = $event->getResponse(); + + // http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1 + if (!\in_array($response->getStatusCode(), [200, 203, 300, 301, 302, 304, 404, 410])) { + unset($this->lastModified[$request]); + unset($this->etags[$request]); + + return; + } + + if (isset($this->lastModified[$request]) && !$response->headers->has('Last-Modified')) { + $response->setLastModified($this->lastModified[$request]); + } + + if (isset($this->etags[$request]) && !$response->headers->has('Etag')) { + $response->setEtag($this->etags[$request]); + } + + unset($this->lastModified[$request]); + unset($this->etags[$request]); + $hasVary = $response->headers->has('Vary'); + + foreach (array_reverse($attributes) as $cache) { + if (null !== $cache->smaxage && !$response->headers->hasCacheControlDirective('s-maxage')) { + $response->setSharedMaxAge($this->toSeconds($cache->smaxage)); + } + + if ($cache->mustRevalidate) { + $response->headers->addCacheControlDirective('must-revalidate'); + } + + if (null !== $cache->maxage && !$response->headers->hasCacheControlDirective('max-age')) { + $response->setMaxAge($this->toSeconds($cache->maxage)); + } + + if (null !== $cache->maxStale && !$response->headers->hasCacheControlDirective('max-stale')) { + $response->headers->addCacheControlDirective('max-stale', $this->toSeconds($cache->maxStale)); + } + + if (null !== $cache->staleWhileRevalidate && !$response->headers->hasCacheControlDirective('stale-while-revalidate')) { + $response->headers->addCacheControlDirective('stale-while-revalidate', $this->toSeconds($cache->staleWhileRevalidate)); + } + + if (null !== $cache->staleIfError && !$response->headers->hasCacheControlDirective('stale-if-error')) { + $response->headers->addCacheControlDirective('stale-if-error', $this->toSeconds($cache->staleIfError)); + } + + if (null !== $cache->expires && !$response->headers->has('Expires')) { + $response->setExpires(new \DateTimeImmutable('@'.strtotime($cache->expires, time()))); + } + + if (!$hasVary && $cache->vary) { + $response->setVary($cache->vary, false); + } + } + + foreach ($attributes as $cache) { + if (true === $cache->public) { + $response->setPublic(); + } + + if (false === $cache->public) { + $response->setPrivate(); + } + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 10], + KernelEvents::RESPONSE => ['onKernelResponse', -10], + ]; + } + + private function getExpressionLanguage(): ExpressionLanguage + { + return $this->expressionLanguage ??= class_exists(ExpressionLanguage::class) + ? new ExpressionLanguage() + : throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + + private function toSeconds(int|string $time): int + { + if (!is_numeric($time)) { + $now = time(); + $time = strtotime($time, $now) - $now; + } + + return $time; + } +} diff --git a/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php b/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php index bd124f94d..ce746bd12 100644 --- a/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php +++ b/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php @@ -21,61 +21,46 @@ use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\KernelEvents; /** - * Configures errors and exceptions handlers. + * Sets an exception handler. * * @author Nicolas Grekas * * @final * - * @internal since Symfony 5.3 + * @internal */ class DebugHandlersListener implements EventSubscriberInterface { - private $earlyHandler; - private $exceptionHandler; - private $logger; - private $deprecationLogger; - private $levels; - private $throwAt; - private $scream; - private $scope; - private $firstCall = true; - private $hasTerminatedWithException; + private string|object|null $earlyHandler; + private ?\Closure $exceptionHandler; + private bool $webMode; + private bool $firstCall = true; + private bool $hasTerminatedWithException = false; /** - * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception - * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants - * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value - * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged - * @param bool $scope Enables/disables scoping mode + * @param bool $webMode + * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception */ - public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, $scope = true, $deprecationLogger = null, $fileLinkFormat = null) + public function __construct(callable $exceptionHandler = null, bool|LoggerInterface $webMode = null) { - if (!\is_bool($scope)) { - trigger_deprecation('symfony/http-kernel', '5.4', 'Passing a $fileLinkFormat is deprecated.'); - $scope = $deprecationLogger; - $deprecationLogger = $fileLinkFormat; + if ($webMode instanceof LoggerInterface) { + // BC with Symfony 5 + $webMode = null; } - $handler = set_exception_handler('is_int'); $this->earlyHandler = \is_array($handler) ? $handler[0] : null; restore_exception_handler(); - $this->exceptionHandler = $exceptionHandler; - $this->logger = $logger; - $this->levels = $levels ?? \E_ALL; - $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); - $this->scream = $scream; - $this->scope = $scope; - $this->deprecationLogger = $deprecationLogger; + $this->exceptionHandler = null === $exceptionHandler ? null : $exceptionHandler(...); + $this->webMode = $webMode ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true); } /** * Configures the error handler. */ - public function configure(object $event = null) + public function configure(object $event = null): void { - if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + if ($event instanceof ConsoleEvent && $this->webMode) { return; } if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) { @@ -83,40 +68,6 @@ class DebugHandlersListener implements EventSubscriberInterface } $this->firstCall = $this->hasTerminatedWithException = false; - $handler = set_exception_handler('is_int'); - $handler = \is_array($handler) ? $handler[0] : null; - restore_exception_handler(); - - if (!$handler instanceof ErrorHandler) { - $handler = $this->earlyHandler; - } - - if ($handler instanceof ErrorHandler) { - if ($this->logger || $this->deprecationLogger) { - $this->setDefaultLoggers($handler); - if (\is_array($this->levels)) { - $levels = 0; - foreach ($this->levels as $type => $log) { - $levels |= $type; - } - } else { - $levels = $this->levels; - } - - if ($this->scream) { - $handler->screamAt($levels); - } - if ($this->scope) { - $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); - } else { - $handler->scopeAt(0, true); - } - $this->logger = $this->deprecationLogger = $this->levels = null; - } - if (null !== $this->throwAt) { - $handler->throwAt($this->throwAt, true); - } - } if (!$this->exceptionHandler) { if ($event instanceof KernelEvent) { if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) { @@ -142,6 +93,14 @@ class DebugHandlersListener implements EventSubscriberInterface } } if ($this->exceptionHandler) { + $handler = set_exception_handler(static fn () => null); + $handler = \is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + + if (!$handler instanceof ErrorHandler) { + $handler = $this->earlyHandler; + } + if ($handler instanceof ErrorHandler) { $handler->setExceptionHandler($this->exceptionHandler); } @@ -149,34 +108,6 @@ class DebugHandlersListener implements EventSubscriberInterface } } - private function setDefaultLoggers(ErrorHandler $handler): void - { - if (\is_array($this->levels)) { - $levelsDeprecatedOnly = []; - $levelsWithoutDeprecated = []; - foreach ($this->levels as $type => $log) { - if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) { - $levelsDeprecatedOnly[$type] = $log; - } else { - $levelsWithoutDeprecated[$type] = $log; - } - } - } else { - $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED); - $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED; - } - - $defaultLoggerLevels = $this->levels; - if ($this->deprecationLogger && $levelsDeprecatedOnly) { - $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly); - $defaultLoggerLevels = $levelsWithoutDeprecated; - } - - if ($this->logger && $defaultLoggerLevels) { - $handler->setDefaultLogger($this->logger, $defaultLoggerLevels); - } - } - public static function getSubscribedEvents(): array { $events = [KernelEvents::REQUEST => ['configure', 2048]]; diff --git a/lib/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php b/lib/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php index 6607e49e9..9631096f5 100644 --- a/lib/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php +++ b/lib/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php @@ -31,10 +31,7 @@ class DisallowRobotsIndexingListener implements EventSubscriberInterface } } - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::RESPONSE => ['onResponse', -255], diff --git a/lib/symfony/http-kernel/EventListener/DumpListener.php b/lib/symfony/http-kernel/EventListener/DumpListener.php index 30908a4f4..b10bd37f4 100644 --- a/lib/symfony/http-kernel/EventListener/DumpListener.php +++ b/lib/symfony/http-kernel/EventListener/DumpListener.php @@ -25,9 +25,9 @@ use Symfony\Component\VarDumper\VarDumper; */ class DumpListener implements EventSubscriberInterface { - private $cloner; - private $dumper; - private $connection; + private ClonerInterface $cloner; + private DataDumperInterface $dumper; + private ?Connection $connection; public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, Connection $connection = null) { @@ -36,14 +36,20 @@ class DumpListener implements EventSubscriberInterface $this->connection = $connection; } + /** + * @return void + */ public function configure() { $cloner = $this->cloner; $dumper = $this->dumper; $connection = $this->connection; - VarDumper::setHandler(static function ($var) use ($cloner, $dumper, $connection) { + VarDumper::setHandler(static function ($var, string $label = null) use ($cloner, $dumper, $connection) { $data = $cloner->cloneVar($var); + if (null !== $label) { + $data = $data->withContext(['label' => $label]); + } if (!$connection || !$connection->write($data)) { $dumper->dump($data); @@ -51,7 +57,7 @@ class DumpListener implements EventSubscriberInterface }); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { if (!class_exists(ConsoleEvents::class)) { return []; diff --git a/lib/symfony/http-kernel/EventListener/ErrorListener.php b/lib/symfony/http-kernel/EventListener/ErrorListener.php index b6fd0a357..a2f6db57a 100644 --- a/lib/symfony/http-kernel/EventListener/ErrorListener.php +++ b/lib/symfony/http-kernel/EventListener/ErrorListener.php @@ -12,10 +12,13 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; -use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException; +use Psr\Log\LogLevel; +use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; +use Symfony\Component\HttpKernel\Attribute\WithLogLevel; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; @@ -23,7 +26,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; /** * @author Fabien Potencier @@ -41,7 +44,7 @@ class ErrorListener implements EventSubscriberInterface /** * @param array|null}> $exceptionsMapping */ - public function __construct($controller, LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = []) + public function __construct(string|object|array|null $controller, LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = []) { $this->controller = $controller; $this->logger = $logger; @@ -49,17 +52,13 @@ class ErrorListener implements EventSubscriberInterface $this->exceptionsMapping = $exceptionsMapping; } + /** + * @return void + */ public function logKernelException(ExceptionEvent $event) { $throwable = $event->getThrowable(); - $logLevel = null; - - foreach ($this->exceptionsMapping as $class => $config) { - if ($throwable instanceof $class && $config['log_level']) { - $logLevel = $config['log_level']; - break; - } - } + $logLevel = $this->resolveLogLevel($throwable); foreach ($this->exceptionsMapping as $class => $config) { if (!$throwable instanceof $class || !$config['status_code']) { @@ -73,11 +72,30 @@ class ErrorListener implements EventSubscriberInterface break; } + // There's no specific status code defined in the configuration for this exception + if (!$throwable instanceof HttpExceptionInterface) { + $class = new \ReflectionClass($throwable); + + do { + if ($attributes = $class->getAttributes(WithHttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF)) { + /** @var WithHttpStatus $instance */ + $instance = $attributes[0]->newInstance(); + + $throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers); + $event->setThrowable($throwable); + break; + } + } while ($class = $class->getParentClass()); + } + $e = FlattenException::createFromThrowable($throwable); - $this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel); + $this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel); } + /** + * @return void + */ public function onKernelException(ExceptionEvent $event) { if (null === $this->controller) { @@ -85,6 +103,14 @@ class ErrorListener implements EventSubscriberInterface } $throwable = $event->getThrowable(); + + if ($exceptionHandler = set_exception_handler(var_dump(...))) { + restore_exception_handler(); + if (\is_array($exceptionHandler) && $exceptionHandler[0] instanceof ErrorHandler) { + $throwable = $exceptionHandler[0]->enhanceError($event->getThrowable()); + } + } + $request = $this->duplicateRequest($throwable, $event->getRequest()); try { @@ -92,7 +118,7 @@ class ErrorListener implements EventSubscriberInterface } catch (\Exception $e) { $f = FlattenException::createFromThrowable($e); - $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), $e->getFile(), $e->getLine())); + $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), basename($e->getFile()), $e->getLine())); $prev = $e; do { @@ -102,7 +128,6 @@ class ErrorListener implements EventSubscriberInterface } while ($prev = $wrapper->getPrevious()); $prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous'); - $prev->setAccessible(true); $prev->setValue($wrapper, $throwable); throw $e; @@ -122,6 +147,9 @@ class ErrorListener implements EventSubscriberInterface } } + /** + * @return void + */ public function onControllerArguments(ControllerArgumentsEvent $event) { $e = $event->getRequest()->attributes->get('exception'); @@ -130,10 +158,10 @@ class ErrorListener implements EventSubscriberInterface return; } - $r = new \ReflectionFunction(\Closure::fromCallable($event->getController())); + $r = new \ReflectionFunction($event->getController()(...)); $r = $r->getParameters()[$k] ?? null; - if ($r && (!($r = $r->getType()) instanceof \ReflectionNamedType || \in_array($r->getName(), [FlattenException::class, LegacyFlattenException::class], true))) { + if ($r && (!($r = $r->getType()) instanceof \ReflectionNamedType || FlattenException::class === $r->getName())) { $arguments = $event->getArguments(); $arguments[$k] = FlattenException::createFromThrowable($e); $event->setArguments($arguments); @@ -157,15 +185,42 @@ class ErrorListener implements EventSubscriberInterface */ protected function logException(\Throwable $exception, string $message, string $logLevel = null): void { - if (null !== $this->logger) { - if (null !== $logLevel) { - $this->logger->log($logLevel, $message, ['exception' => $exception]); - } elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { - $this->logger->critical($message, ['exception' => $exception]); - } else { - $this->logger->error($message, ['exception' => $exception]); + if (null === $this->logger) { + return; + } + + $logLevel ??= $this->resolveLogLevel($exception); + + $this->logger->log($logLevel, $message, ['exception' => $exception]); + } + + /** + * Resolves the level to be used when logging the exception. + */ + private function resolveLogLevel(\Throwable $throwable): string + { + foreach ($this->exceptionsMapping as $class => $config) { + if ($throwable instanceof $class && $config['log_level']) { + return $config['log_level']; } } + + $class = new \ReflectionClass($throwable); + + do { + if ($attributes = $class->getAttributes(WithLogLevel::class)) { + /** @var WithLogLevel $instance */ + $instance = $attributes[0]->newInstance(); + + return $instance->level; + } + } while ($class = $class->getParentClass()); + + if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() >= 500) { + return LogLevel::CRITICAL; + } + + return LogLevel::ERROR; } /** @@ -176,7 +231,7 @@ class ErrorListener implements EventSubscriberInterface $attributes = [ '_controller' => $this->controller, 'exception' => $exception, - 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + 'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger), ]; $request = $request->duplicate(null, null, $attributes); $request->setMethod('GET'); diff --git a/lib/symfony/http-kernel/EventListener/FragmentListener.php b/lib/symfony/http-kernel/EventListener/FragmentListener.php index c01d9ad49..562244b33 100644 --- a/lib/symfony/http-kernel/EventListener/FragmentListener.php +++ b/lib/symfony/http-kernel/EventListener/FragmentListener.php @@ -13,10 +13,10 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\UriSigner; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\UriSigner; /** * Handles content fragments represented by special URIs. @@ -33,8 +33,8 @@ use Symfony\Component\HttpKernel\UriSigner; */ class FragmentListener implements EventSubscriberInterface { - private $signer; - private $fragmentPath; + private UriSigner $signer; + private string $fragmentPath; /** * @param string $fragmentPath The path that triggers this listener @@ -50,7 +50,7 @@ class FragmentListener implements EventSubscriberInterface * * @throws AccessDeniedHttpException if the request does not come from a trusted IP */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); @@ -70,12 +70,13 @@ class FragmentListener implements EventSubscriberInterface } parse_str($request->query->get('_path', ''), $attributes); + $attributes['_check_controller_is_allowed'] = -1; // @deprecated, switch to true in Symfony 7 $request->attributes->add($attributes); $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', []), $attributes)); $request->query->remove('_path'); } - protected function validateRequest(Request $request) + protected function validateRequest(Request $request): void { // is the Request safe? if (!$request->isMethodSafe()) { diff --git a/lib/symfony/http-kernel/EventListener/LocaleAwareListener.php b/lib/symfony/http-kernel/EventListener/LocaleAwareListener.php index a126f06ec..83fbb4f8b 100644 --- a/lib/symfony/http-kernel/EventListener/LocaleAwareListener.php +++ b/lib/symfony/http-kernel/EventListener/LocaleAwareListener.php @@ -25,8 +25,8 @@ use Symfony\Contracts\Translation\LocaleAwareInterface; */ class LocaleAwareListener implements EventSubscriberInterface { - private $localeAwareServices; - private $requestStack; + private iterable $localeAwareServices; + private RequestStack $requestStack; /** * @param iterable $localeAwareServices @@ -55,7 +55,7 @@ class LocaleAwareListener implements EventSubscriberInterface $this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale()); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ // must be registered after the Locale listener @@ -69,7 +69,7 @@ class LocaleAwareListener implements EventSubscriberInterface foreach ($this->localeAwareServices as $service) { try { $service->setLocale($locale); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { $service->setLocale($defaultLocale); } } diff --git a/lib/symfony/http-kernel/EventListener/LocaleListener.php b/lib/symfony/http-kernel/EventListener/LocaleListener.php index f19e13649..4516048be 100644 --- a/lib/symfony/http-kernel/EventListener/LocaleListener.php +++ b/lib/symfony/http-kernel/EventListener/LocaleListener.php @@ -29,11 +29,11 @@ use Symfony\Component\Routing\RequestContextAwareInterface; */ class LocaleListener implements EventSubscriberInterface { - private $router; - private $defaultLocale; - private $requestStack; - private $useAcceptLanguageHeader; - private $enabledLocales; + private ?RequestContextAwareInterface $router; + private string $defaultLocale; + private RequestStack $requestStack; + private bool $useAcceptLanguageHeader; + private array $enabledLocales; public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = false, array $enabledLocales = []) { @@ -44,12 +44,12 @@ class LocaleListener implements EventSubscriberInterface $this->enabledLocales = $enabledLocales; } - public function setDefaultLocale(KernelEvent $event) + public function setDefaultLocale(KernelEvent $event): void { $event->getRequest()->setDefaultLocale($this->defaultLocale); } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); @@ -57,28 +57,28 @@ class LocaleListener implements EventSubscriberInterface $this->setRouterContext($request); } - public function onKernelFinishRequest(FinishRequestEvent $event) + public function onKernelFinishRequest(FinishRequestEvent $event): void { if (null !== $parentRequest = $this->requestStack->getParentRequest()) { $this->setRouterContext($parentRequest); } } - private function setLocale(Request $request) + private function setLocale(Request $request): void { if ($locale = $request->attributes->get('_locale')) { $request->setLocale($locale); - } elseif ($this->useAcceptLanguageHeader && $this->enabledLocales && ($preferredLanguage = $request->getPreferredLanguage($this->enabledLocales))) { - $request->setLocale($preferredLanguage); + } elseif ($this->useAcceptLanguageHeader) { + if ($preferredLanguage = $request->getPreferredLanguage($this->enabledLocales)) { + $request->setLocale($preferredLanguage); + } $request->attributes->set('_vary_by_language', true); } } - private function setRouterContext(Request $request) + private function setRouterContext(Request $request): void { - if (null !== $this->router) { - $this->router->getContext()->setParameter('_locale', $request->getLocale()); - } + $this->router?->getContext()->setParameter('_locale', $request->getLocale()); } public static function getSubscribedEvents(): array diff --git a/lib/symfony/http-kernel/EventListener/ProfilerListener.php b/lib/symfony/http-kernel/EventListener/ProfilerListener.php index adbafe62e..716d939fd 100644 --- a/lib/symfony/http-kernel/EventListener/ProfilerListener.php +++ b/lib/symfony/http-kernel/EventListener/ProfilerListener.php @@ -32,17 +32,17 @@ use Symfony\Component\HttpKernel\Profiler\Profiler; */ class ProfilerListener implements EventSubscriberInterface { - protected $profiler; - protected $matcher; - protected $onlyException; - protected $onlyMainRequests; - protected $exception; + private Profiler $profiler; + private ?RequestMatcherInterface $matcher; + private bool $onlyException; + private bool $onlyMainRequests; + private ?\Throwable $exception = null; /** @var \SplObjectStorage */ - protected $profiles; - protected $requestStack; - protected $collectParameter; + private \SplObjectStorage $profiles; + private RequestStack $requestStack; + private ?string $collectParameter; /** @var \SplObjectStorage */ - protected $parents; + private \SplObjectStorage $parents; /** * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise @@ -63,7 +63,7 @@ class ProfilerListener implements EventSubscriberInterface /** * Handles the onKernelException event. */ - public function onKernelException(ExceptionEvent $event) + public function onKernelException(ExceptionEvent $event): void { if ($this->onlyMainRequests && !$event->isMainRequest()) { return; @@ -75,7 +75,7 @@ class ProfilerListener implements EventSubscriberInterface /** * Handles the onKernelResponse event. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if ($this->onlyMainRequests && !$event->isMainRequest()) { return; @@ -87,7 +87,7 @@ class ProfilerListener implements EventSubscriberInterface $request = $event->getRequest(); if (null !== $this->collectParameter && null !== $collectParameterValue = $request->get($this->collectParameter)) { - true === $collectParameterValue || filter_var($collectParameterValue, \FILTER_VALIDATE_BOOLEAN) ? $this->profiler->enable() : $this->profiler->disable(); + true === $collectParameterValue || filter_var($collectParameterValue, \FILTER_VALIDATE_BOOL) ? $this->profiler->enable() : $this->profiler->disable(); } $exception = $this->exception; @@ -97,7 +97,7 @@ class ProfilerListener implements EventSubscriberInterface return; } - $session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null; + $session = $request->hasPreviousSession() ? $request->getSession() : null; if ($session instanceof Session) { $usageIndexValue = $usageIndexReference = &$session->getUsageIndex(); @@ -119,7 +119,7 @@ class ProfilerListener implements EventSubscriberInterface $this->parents[$request] = $this->requestStack->getParentRequest(); } - public function onKernelTerminate(TerminateEvent $event) + public function onKernelTerminate(TerminateEvent $event): void { // attach children to parents foreach ($this->profiles as $request) { diff --git a/lib/symfony/http-kernel/EventListener/ResponseListener.php b/lib/symfony/http-kernel/EventListener/ResponseListener.php index a4090159b..5825e16e4 100644 --- a/lib/symfony/http-kernel/EventListener/ResponseListener.php +++ b/lib/symfony/http-kernel/EventListener/ResponseListener.php @@ -24,8 +24,8 @@ use Symfony\Component\HttpKernel\KernelEvents; */ class ResponseListener implements EventSubscriberInterface { - private $charset; - private $addContentLanguageHeader; + private string $charset; + private bool $addContentLanguageHeader; public function __construct(string $charset, bool $addContentLanguageHeader = false) { @@ -36,7 +36,7 @@ class ResponseListener implements EventSubscriberInterface /** * Filters the Response. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/lib/symfony/http-kernel/EventListener/RouterListener.php b/lib/symfony/http-kernel/EventListener/RouterListener.php index 7c4da9892..bb393c779 100644 --- a/lib/symfony/http-kernel/EventListener/RouterListener.php +++ b/lib/symfony/http-kernel/EventListener/RouterListener.php @@ -17,7 +17,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; -use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; @@ -42,25 +41,20 @@ use Symfony\Component\Routing\RequestContextAwareInterface; */ class RouterListener implements EventSubscriberInterface { - private $matcher; - private $context; - private $logger; - private $requestStack; - private $projectDir; - private $debug; + private RequestMatcherInterface|UrlMatcherInterface $matcher; + private RequestContext $context; + private ?LoggerInterface $logger; + private RequestStack $requestStack; + private ?string $projectDir; + private bool $debug; /** - * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher - * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) * * @throws \InvalidArgumentException */ - public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, string $projectDir = null, bool $debug = true) + public function __construct(UrlMatcherInterface|RequestMatcherInterface $matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, string $projectDir = null, bool $debug = true) { - if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { - throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); - } - if (null === $context && !$matcher instanceof RequestContextAwareInterface) { throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); } @@ -73,7 +67,7 @@ class RouterListener implements EventSubscriberInterface $this->debug = $debug; } - private function setCurrentRequest(Request $request = null) + private function setCurrentRequest(?Request $request): void { if (null !== $request) { try { @@ -88,12 +82,12 @@ class RouterListener implements EventSubscriberInterface * After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator * operates on the correct context again. */ - public function onKernelFinishRequest(FinishRequestEvent $event) + public function onKernelFinishRequest(): void { $this->setCurrentRequest($this->requestStack->getParentRequest()); } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); @@ -113,14 +107,12 @@ class RouterListener implements EventSubscriberInterface $parameters = $this->matcher->match($request->getPathInfo()); } - if (null !== $this->logger) { - $this->logger->info('Matched route "{route}".', [ - 'route' => $parameters['_route'] ?? 'n/a', - 'route_parameters' => $parameters, - 'request_uri' => $request->getUri(), - 'method' => $request->getMethod(), - ]); - } + $this->logger?->info('Matched route "{route}".', [ + 'route' => $parameters['_route'] ?? 'n/a', + 'route_parameters' => $parameters, + 'request_uri' => $request->getUri(), + 'method' => $request->getMethod(), + ]); $request->attributes->add($parameters); unset($parameters['_route'], $parameters['_controller']); @@ -140,7 +132,7 @@ class RouterListener implements EventSubscriberInterface } } - public function onKernelException(ExceptionEvent $event) + public function onKernelException(ExceptionEvent $event): void { if (!$this->debug || !($e = $event->getThrowable()) instanceof NotFoundHttpException) { return; diff --git a/lib/symfony/http-kernel/EventListener/SessionListener.php b/lib/symfony/http-kernel/EventListener/SessionListener.php index 61887fde6..ec23a2e98 100644 --- a/lib/symfony/http-kernel/EventListener/SessionListener.php +++ b/lib/symfony/http-kernel/EventListener/SessionListener.php @@ -12,45 +12,18 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; -use Symfony\Component\HttpKernel\Event\RequestEvent; /** * Sets the session in the request. * - * When the passed container contains a "session_storage" entry which - * holds a NativeSessionStorage instance, the "cookie_secure" option - * will be set to true whenever the current main request is secure. - * * @author Fabien Potencier * * @final */ class SessionListener extends AbstractSessionListener { - public function onKernelRequest(RequestEvent $event) - { - parent::onKernelRequest($event); - - if (!$event->isMainRequest() || (!$this->container->has('session') && !$this->container->has('session_factory'))) { - return; - } - - if ($this->container->has('session_storage') - && ($storage = $this->container->get('session_storage')) instanceof NativeSessionStorage - && ($mainRequest = $this->container->get('request_stack')->getMainRequest()) - && $mainRequest->isSecure() - ) { - $storage->setOptions(['cookie_secure' => true]); - } - } - protected function getSession(): ?SessionInterface { - if ($this->container->has('session')) { - return $this->container->get('session'); - } - if ($this->container->has('session_factory')) { return $this->container->get('session_factory')->createSession(); } diff --git a/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php b/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php index b3f7ca40f..312d5ee23 100644 --- a/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php +++ b/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php @@ -16,6 +16,8 @@ use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +trigger_deprecation('symfony/http-kernel', '6.1', 'The "%s" class is deprecated.', StreamedResponseListener::class); + /** * StreamedResponseListener is responsible for sending the Response * to the client. @@ -23,13 +25,15 @@ use Symfony\Component\HttpKernel\KernelEvents; * @author Fabien Potencier * * @final + * + * @deprecated since Symfony 6.1 */ class StreamedResponseListener implements EventSubscriberInterface { /** * Filters the Response. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/lib/symfony/http-kernel/EventListener/SurrogateListener.php b/lib/symfony/http-kernel/EventListener/SurrogateListener.php index 9081bff65..17bdf2b39 100644 --- a/lib/symfony/http-kernel/EventListener/SurrogateListener.php +++ b/lib/symfony/http-kernel/EventListener/SurrogateListener.php @@ -26,7 +26,7 @@ use Symfony\Component\HttpKernel\KernelEvents; */ class SurrogateListener implements EventSubscriberInterface { - private $surrogate; + private ?SurrogateInterface $surrogate; public function __construct(SurrogateInterface $surrogate = null) { @@ -36,7 +36,7 @@ class SurrogateListener implements EventSubscriberInterface /** * Filters the Response. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/lib/symfony/http-kernel/EventListener/TestSessionListener.php b/lib/symfony/http-kernel/EventListener/TestSessionListener.php deleted file mode 100644 index 45fa312be..000000000 --- a/lib/symfony/http-kernel/EventListener/TestSessionListener.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\EventListener; - -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpFoundation\Session\SessionInterface; - -trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated, use "%s" instead.', TestSessionListener::class, SessionListener::class); - -/** - * Sets the session in the request. - * - * @author Fabien Potencier - * - * @final - * - * @deprecated since Symfony 5.4, use SessionListener instead - */ -class TestSessionListener extends AbstractTestSessionListener -{ - private $container; - - public function __construct(ContainerInterface $container, array $sessionOptions = []) - { - $this->container = $container; - parent::__construct($sessionOptions); - } - - protected function getSession(): ?SessionInterface - { - if ($this->container->has('session')) { - return $this->container->get('session'); - } - - return null; - } -} diff --git a/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php b/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php index caa0f32aa..187633d5d 100644 --- a/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php +++ b/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php @@ -27,7 +27,7 @@ class ValidateRequestListener implements EventSubscriberInterface /** * Performs the validation. */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; @@ -41,9 +41,6 @@ class ValidateRequestListener implements EventSubscriberInterface $request->getHost(); } - /** - * {@inheritdoc} - */ public static function getSubscribedEvents(): array { return [ diff --git a/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php b/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php index 58680a327..78e8fe37d 100644 --- a/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php +++ b/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php @@ -17,19 +17,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class AccessDeniedHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(403, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/BadRequestHttpException.php b/lib/symfony/http-kernel/Exception/BadRequestHttpException.php index f530f7db4..c920fbd0d 100644 --- a/lib/symfony/http-kernel/Exception/BadRequestHttpException.php +++ b/lib/symfony/http-kernel/Exception/BadRequestHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class BadRequestHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(400, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/ConflictHttpException.php b/lib/symfony/http-kernel/Exception/ConflictHttpException.php index 79c36041c..a5a6f8405 100644 --- a/lib/symfony/http-kernel/Exception/ConflictHttpException.php +++ b/lib/symfony/http-kernel/Exception/ConflictHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class ConflictHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(409, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php b/lib/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php index 54c80be90..ecfc2f8f0 100644 --- a/lib/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php +++ b/lib/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php @@ -27,7 +27,6 @@ class ControllerDoesNotReturnResponseException extends \LogicException $this->file = $controllerDefinition['file']; $this->line = $controllerDefinition['line']; $r = new \ReflectionProperty(\Exception::class, 'trace'); - $r->setAccessible(true); $r->setValue($this, array_merge([ [ 'line' => $line, @@ -50,7 +49,7 @@ class ControllerDoesNotReturnResponseException extends \LogicException 'file' => $r->getFileName(), 'line' => $r->getEndLine(), ]; - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { return null; } } @@ -69,7 +68,7 @@ class ControllerDoesNotReturnResponseException extends \LogicException try { $line = $r->getMethod('__invoke')->getEndLine(); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { $line = $r->getEndLine(); } diff --git a/lib/symfony/http-kernel/Exception/GoneHttpException.php b/lib/symfony/http-kernel/Exception/GoneHttpException.php index 9ea65057b..2893f05cb 100644 --- a/lib/symfony/http-kernel/Exception/GoneHttpException.php +++ b/lib/symfony/http-kernel/Exception/GoneHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class GoneHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(410, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/HttpException.php b/lib/symfony/http-kernel/Exception/HttpException.php index 249fe366d..e12abce00 100644 --- a/lib/symfony/http-kernel/Exception/HttpException.php +++ b/lib/symfony/http-kernel/Exception/HttpException.php @@ -18,42 +18,29 @@ namespace Symfony\Component\HttpKernel\Exception; */ class HttpException extends \RuntimeException implements HttpExceptionInterface { - private $statusCode; - private $headers; + private int $statusCode; + private array $headers; - public function __construct(int $statusCode, ?string $message = '', \Throwable $previous = null, array $headers = [], ?int $code = 0) + public function __construct(int $statusCode, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - if (null === $code) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); - - $code = 0; - } - $this->statusCode = $statusCode; $this->headers = $headers; parent::__construct($message, $code, $previous); } - public function getStatusCode() + public function getStatusCode(): int { return $this->statusCode; } - public function getHeaders() + public function getHeaders(): array { return $this->headers; } /** - * Set response headers. - * - * @param array $headers Response headers + * @return void */ public function setHeaders(array $headers) { diff --git a/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php b/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php index 4ae050945..7eb0cbdad 100644 --- a/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php +++ b/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php @@ -20,15 +20,11 @@ interface HttpExceptionInterface extends \Throwable { /** * Returns the status code. - * - * @return int */ - public function getStatusCode(); + public function getStatusCode(): int; /** * Returns response headers. - * - * @return array */ - public function getHeaders(); + public function getHeaders(): array; } diff --git a/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php b/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php index fcac13785..a3dd8b3cd 100644 --- a/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php +++ b/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class LengthRequiredHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(411, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/LockedHttpException.php b/lib/symfony/http-kernel/Exception/LockedHttpException.php new file mode 100644 index 000000000..069619bfc --- /dev/null +++ b/lib/symfony/http-kernel/Exception/LockedHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Peter Dietrich + */ +class LockedHttpException extends HttpException +{ + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(423, $message, $previous, $headers, $code); + } +} diff --git a/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php b/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php index 37576bcac..cfbaf5cb0 100644 --- a/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php +++ b/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php @@ -17,24 +17,10 @@ namespace Symfony\Component\HttpKernel\Exception; class MethodNotAllowedHttpException extends HttpException { /** - * @param string[] $allow An array of allowed methods - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int|null $code The internal exception code + * @param string[] $allow An array of allowed methods */ - public function __construct(array $allow, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) + public function __construct(array $allow, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - if (null === $code) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); - - $code = 0; - } - $headers['Allow'] = strtoupper(implode(', ', $allow)); parent::__construct(405, $message, $previous, $headers, $code); diff --git a/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php b/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php index 5a422406b..ec2bb596f 100644 --- a/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php +++ b/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class NotAcceptableHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(406, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/NotFoundHttpException.php b/lib/symfony/http-kernel/Exception/NotFoundHttpException.php index a475113c5..0e78fcc15 100644 --- a/lib/symfony/http-kernel/Exception/NotFoundHttpException.php +++ b/lib/symfony/http-kernel/Exception/NotFoundHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class NotFoundHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(404, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php b/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php index e23740a28..4431f89d0 100644 --- a/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php +++ b/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class PreconditionFailedHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(412, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php b/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php index 5c31fae82..f75afd370 100644 --- a/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php +++ b/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php @@ -18,19 +18,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class PreconditionRequiredHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(428, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/ResolverNotFoundException.php b/lib/symfony/http-kernel/Exception/ResolverNotFoundException.php new file mode 100644 index 000000000..6d9fb8a01 --- /dev/null +++ b/lib/symfony/http-kernel/Exception/ResolverNotFoundException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +class ResolverNotFoundException extends \RuntimeException +{ + /** + * @param string[] $alternatives + */ + public function __construct(string $name, array $alternatives = []) + { + $msg = sprintf('You have requested a non-existent resolver "%s".', $name); + if ($alternatives) { + if (1 === \count($alternatives)) { + $msg .= ' Did you mean this: "'; + } else { + $msg .= ' Did you mean one of these: "'; + } + $msg .= implode('", "', $alternatives).'"?'; + } + + parent::__construct($msg); + } +} diff --git a/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php b/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php index d5681bbeb..d4862bd10 100644 --- a/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php +++ b/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php @@ -18,23 +18,9 @@ class ServiceUnavailableHttpException extends HttpException { /** * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int|null $code The internal exception code */ - public function __construct($retryAfter = null, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) + public function __construct(int|string $retryAfter = null, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - if (null === $code) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); - - $code = 0; - } - if ($retryAfter) { $headers['Retry-After'] = $retryAfter; } diff --git a/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php b/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php index fd74402b5..b71fb2508 100644 --- a/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php +++ b/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php @@ -20,23 +20,9 @@ class TooManyRequestsHttpException extends HttpException { /** * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int|null $code The internal exception code */ - public function __construct($retryAfter = null, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) + public function __construct(int|string $retryAfter = null, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - if (null === $code) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); - - $code = 0; - } - if ($retryAfter) { $headers['Retry-After'] = $retryAfter; } diff --git a/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php b/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php index aeb9713a3..c86686128 100644 --- a/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php +++ b/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php @@ -17,24 +17,10 @@ namespace Symfony\Component\HttpKernel\Exception; class UnauthorizedHttpException extends HttpException { /** - * @param string $challenge WWW-Authenticate challenge string - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int|null $code The internal exception code + * @param string $challenge WWW-Authenticate challenge string */ - public function __construct(string $challenge, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) + public function __construct(string $challenge, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - if (null === $code) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); - - $code = 0; - } - $headers['WWW-Authenticate'] = $challenge; parent::__construct(401, $message, $previous, $headers, $code); diff --git a/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php b/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php index 7b828b1d9..d58af6c2b 100644 --- a/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php +++ b/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class UnprocessableEntityHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(422, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php b/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php index 7908423f4..3060f1f91 100644 --- a/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php +++ b/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php @@ -16,19 +16,8 @@ namespace Symfony\Component\HttpKernel\Exception; */ class UnsupportedMediaTypeHttpException extends HttpException { - /** - * @param string|null $message The internal exception message - * @param \Throwable|null $previous The previous exception - * @param int $code The internal exception code - */ - public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - if (null === $message) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); - - $message = ''; - } - parent::__construct(415, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php index 4e4d028b4..55305d44e 100644 --- a/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -13,9 +13,9 @@ namespace Symfony\Component\HttpKernel\Fragment; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\UriSigner; use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; -use Symfony\Component\HttpKernel\UriSigner; /** * Implements Surrogate rendering strategy. @@ -24,9 +24,9 @@ use Symfony\Component\HttpKernel\UriSigner; */ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer { - private $surrogate; - private $inlineStrategy; - private $signer; + private ?SurrogateInterface $surrogate; + private FragmentRendererInterface $inlineStrategy; + private ?UriSigner $signer; /** * The "fallback" strategy when surrogate is not available should always be an @@ -34,7 +34,7 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere * * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported */ - public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) + public function __construct(?SurrogateInterface $surrogate, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) { $this->surrogate = $surrogate; $this->inlineStrategy = $inlineStrategy; @@ -42,8 +42,6 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere } /** - * {@inheritdoc} - * * Note that if the current Request has no surrogate capability, this method * falls back to use the inline rendering strategy. * @@ -51,15 +49,18 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere * * * alt: an alternative URI to render in case of an error * * comment: a comment to add when returning the surrogate tag + * * absolute_uri: whether to generate an absolute URI or not. Default is false * * Note, that not all surrogate strategies support all options. For now * 'alt' and 'comment' are only supported by ESI. * * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface */ - public function render($uri, Request $request, array $options = []) + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response { if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { + $request->attributes->set('_check_controller_is_allowed', -1); // @deprecated, switch to true in Symfony 7 + if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { throw new \InvalidArgumentException('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is not supported. Use a different rendering strategy or pass scalar values.'); } @@ -67,13 +68,15 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere return $this->inlineStrategy->render($uri, $request, $options); } + $absolute = $options['absolute_uri'] ?? false; + if ($uri instanceof ControllerReference) { - $uri = $this->generateSignedFragmentUri($uri, $request); + $uri = $this->generateSignedFragmentUri($uri, $request, $absolute); } $alt = $options['alt'] ?? null; if ($alt instanceof ControllerReference) { - $alt = $this->generateSignedFragmentUri($alt, $request); + $alt = $this->generateSignedFragmentUri($alt, $request, $absolute); } $tag = $this->surrogate->renderIncludeTag($uri, $alt, $options['ignore_errors'] ?? false, $options['comment'] ?? ''); @@ -81,9 +84,9 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere return new Response($tag); } - private function generateSignedFragmentUri(ControllerReference $uri, Request $request): string + private function generateSignedFragmentUri(ControllerReference $uri, Request $request, bool $absolute): string { - return (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); + return (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request, $absolute); } private function containsNonScalars(array $values): bool diff --git a/lib/symfony/http-kernel/Fragment/EsiFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/EsiFragmentRenderer.php index a4570e3be..da4c34add 100644 --- a/lib/symfony/http-kernel/Fragment/EsiFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/EsiFragmentRenderer.php @@ -18,10 +18,7 @@ namespace Symfony\Component\HttpKernel\Fragment; */ class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer { - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'esi'; } diff --git a/lib/symfony/http-kernel/Fragment/FragmentHandler.php b/lib/symfony/http-kernel/Fragment/FragmentHandler.php index 1ecaaef1a..62b21e6d4 100644 --- a/lib/symfony/http-kernel/Fragment/FragmentHandler.php +++ b/lib/symfony/http-kernel/Fragment/FragmentHandler.php @@ -29,9 +29,9 @@ use Symfony\Component\HttpKernel\Exception\HttpException; */ class FragmentHandler { - private $debug; - private $renderers = []; - private $requestStack; + private bool $debug; + private array $renderers = []; + private RequestStack $requestStack; /** * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances @@ -48,6 +48,8 @@ class FragmentHandler /** * Adds a renderer. + * + * @return void */ public function addRenderer(FragmentRendererInterface $renderer) { @@ -61,14 +63,10 @@ class FragmentHandler * * * ignore_errors: true to return an empty string in case of an error * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * - * @return string|null - * * @throws \InvalidArgumentException when the renderer does not exist * @throws \LogicException when no main request is being handled */ - public function render($uri, string $renderer = 'inline', array $options = []) + public function render(string|ControllerReference $uri, string $renderer = 'inline', array $options = []): ?string { if (!isset($options['ignore_errors'])) { $options['ignore_errors'] = !$this->debug; @@ -95,7 +93,7 @@ class FragmentHandler * * @throws \RuntimeException when the Response is not successful */ - protected function deliver(Response $response) + protected function deliver(Response $response): ?string { if (!$response->isSuccessful()) { $responseStatusCode = $response->getStatusCode(); diff --git a/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php b/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php index 568b1781a..a61f2e64e 100644 --- a/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php +++ b/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php @@ -24,17 +24,11 @@ interface FragmentRendererInterface { /** * Renders a URI and returns the Response content. - * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * - * @return Response */ - public function render($uri, Request $request, array $options = []); + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response; /** * Gets the name of the strategy. - * - * @return string */ - public function getName(); + public function getName(): string; } diff --git a/lib/symfony/http-kernel/Fragment/FragmentUriGenerator.php b/lib/symfony/http-kernel/Fragment/FragmentUriGenerator.php index 4c0fac997..aeef41546 100644 --- a/lib/symfony/http-kernel/Fragment/FragmentUriGenerator.php +++ b/lib/symfony/http-kernel/Fragment/FragmentUriGenerator.php @@ -13,8 +13,8 @@ namespace Symfony\Component\HttpKernel\Fragment; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\UriSigner; use Symfony\Component\HttpKernel\Controller\ControllerReference; -use Symfony\Component\HttpKernel\UriSigner; /** * Generates a fragment URI. @@ -24,9 +24,9 @@ use Symfony\Component\HttpKernel\UriSigner; */ final class FragmentUriGenerator implements FragmentUriGeneratorInterface { - private $fragmentPath; - private $signer; - private $requestStack; + private string $fragmentPath; + private ?UriSigner $signer; + private ?RequestStack $requestStack; public function __construct(string $fragmentPath, UriSigner $signer = null, RequestStack $requestStack = null) { @@ -35,9 +35,6 @@ final class FragmentUriGenerator implements FragmentUriGeneratorInterface $this->requestStack = $requestStack; } - /** - * {@inheritDoc} - */ public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string { if (null === $request && (null === $this->requestStack || null === $request = $this->requestStack->getCurrentRequest())) { diff --git a/lib/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php b/lib/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php index b211f5e37..968c002b9 100644 --- a/lib/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php +++ b/lib/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php @@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ControllerReference; /** - * Interface implemented by rendering strategies able to generate an URL for a fragment. + * Interface implemented by rendering strategies able to generate a URL for a fragment. * * @author Kévin Dunglas */ diff --git a/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php index 446ce2d9d..d5b6c4cd3 100644 --- a/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php @@ -13,8 +13,8 @@ namespace Symfony\Component\HttpKernel\Fragment; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\UriSigner; use Symfony\Component\HttpKernel\Controller\ControllerReference; -use Symfony\Component\HttpKernel\UriSigner; use Twig\Environment; /** @@ -24,13 +24,13 @@ use Twig\Environment; */ class HIncludeFragmentRenderer extends RoutableFragmentRenderer { - private $globalDefaultTemplate; - private $signer; - private $twig; - private $charset; + private ?string $globalDefaultTemplate; + private ?UriSigner $signer; + private ?Environment $twig; + private string $charset; /** - * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) + * @param string|null $globalDefaultTemplate The global default content (it can be a template name or the content) */ public function __construct(Environment $twig = null, UriSigner $signer = null, string $globalDefaultTemplate = null, string $charset = 'utf-8') { @@ -42,24 +42,20 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer /** * Checks if a templating engine has been set. - * - * @return bool */ - public function hasTemplating() + public function hasTemplating(): bool { return null !== $this->twig; } /** - * {@inheritdoc} - * * Additional available options: * * * default: The default content (it can be a template name or the content) * * id: An optional hx:include tag id attribute * * attributes: An optional array of hx:include tag attributes */ - public function render($uri, Request $request, array $options = []) + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response { if ($uri instanceof ControllerReference) { $uri = (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); @@ -94,10 +90,7 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer return new Response(sprintf('%s', $uri, $renderedAttributes, $content)); } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'hinclude'; } diff --git a/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php index ea45fdcb3..ba3f6be70 100644 --- a/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -27,8 +27,8 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; */ class InlineFragmentRenderer extends RoutableFragmentRenderer { - private $kernel; - private $dispatcher; + private HttpKernelInterface $kernel; + private ?EventDispatcherInterface $dispatcher; public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null) { @@ -37,13 +37,11 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer } /** - * {@inheritdoc} - * * Additional available options: * * * alt: an alternative URI to render in case of an error */ - public function render($uri, Request $request, array $options = []) + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response { $reference = null; if ($uri instanceof ControllerReference) { @@ -105,6 +103,9 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer } } + /** + * @return Request + */ protected function createSubRequest(string $uri, Request $request) { $cookies = $request->cookies->all(); @@ -120,9 +121,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer static $setSession; - if (null === $setSession) { - $setSession = \Closure::bind(static function ($subRequest, $request) { $subRequest->session = $request->session; }, null, Request::class); - } + $setSession ??= \Closure::bind(static function ($subRequest, $request) { $subRequest->session = $request->session; }, null, Request::class); $setSession($subRequest, $request); if ($request->get('_format')) { @@ -131,14 +130,17 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer if ($request->getDefaultLocale() !== $request->getLocale()) { $subRequest->setLocale($request->getLocale()); } + if ($request->attributes->has('_stateless')) { + $subRequest->attributes->set('_stateless', $request->attributes->get('_stateless')); + } + if ($request->attributes->has('_check_controller_is_allowed')) { + $subRequest->attributes->set('_check_controller_is_allowed', $request->attributes->get('_check_controller_is_allowed')); + } return $subRequest; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'inline'; } diff --git a/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php index e922ffb64..6a8989081 100644 --- a/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php @@ -25,12 +25,14 @@ abstract class RoutableFragmentRenderer implements FragmentRendererInterface /** * @internal */ - protected $fragmentPath = '/_fragment'; + protected string $fragmentPath = '/_fragment'; /** * Sets the fragment path that triggers the fragment listener. * * @see FragmentListener + * + * @return void */ public function setFragmentPath(string $path) { @@ -42,10 +44,8 @@ abstract class RoutableFragmentRenderer implements FragmentRendererInterface * * @param bool $absolute Whether to generate an absolute URL or not * @param bool $strict Whether to allow non-scalar attributes or not - * - * @return string */ - protected function generateFragmentUri(ControllerReference $reference, Request $request, bool $absolute = false, bool $strict = true) + protected function generateFragmentUri(ControllerReference $reference, Request $request, bool $absolute = false, bool $strict = true): string { return (new FragmentUriGenerator($this->fragmentPath))->generate($reference, $request, $absolute, $strict, false); } diff --git a/lib/symfony/http-kernel/Fragment/SsiFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/SsiFragmentRenderer.php index 45e7122f0..6319bcd83 100644 --- a/lib/symfony/http-kernel/Fragment/SsiFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/SsiFragmentRenderer.php @@ -18,10 +18,7 @@ namespace Symfony\Component\HttpKernel\Fragment; */ class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer { - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'ssi'; } diff --git a/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php b/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php index f2d809e8d..95518bed2 100644 --- a/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php +++ b/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php @@ -24,6 +24,10 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; abstract class AbstractSurrogate implements SurrogateInterface { protected $contentTypes; + + /** + * @deprecated since Symfony 6.3 + */ protected $phpEscapeMap = [ ['', '', '', ''], @@ -40,18 +44,13 @@ abstract class AbstractSurrogate implements SurrogateInterface /** * Returns a new cache strategy instance. - * - * @return ResponseCacheStrategyInterface */ - public function createCacheStrategy() + public function createCacheStrategy(): ResponseCacheStrategyInterface { return new ResponseCacheStrategy(); } - /** - * {@inheritdoc} - */ - public function hasSurrogateCapability(Request $request) + public function hasSurrogateCapability(Request $request): bool { if (null === $value = $request->headers->get('Surrogate-Capability')) { return false; @@ -61,7 +60,7 @@ abstract class AbstractSurrogate implements SurrogateInterface } /** - * {@inheritdoc} + * @return void */ public function addSurrogateCapability(Request $request) { @@ -71,10 +70,7 @@ abstract class AbstractSurrogate implements SurrogateInterface $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); } - /** - * {@inheritdoc} - */ - public function needsParsing(Response $response) + public function needsParsing(Response $response): bool { if (!$control = $response->headers->get('Surrogate-Control')) { return false; @@ -85,10 +81,7 @@ abstract class AbstractSurrogate implements SurrogateInterface return (bool) preg_match($pattern, $control); } - /** - * {@inheritdoc} - */ - public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors) + public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors): string { $subRequest = Request::create($uri, Request::METHOD_GET, [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all()); @@ -115,6 +108,8 @@ abstract class AbstractSurrogate implements SurrogateInterface /** * Remove the Surrogate from the Surrogate-Control header. + * + * @return void */ protected function removeFromControl(Response $response) { @@ -133,4 +128,15 @@ abstract class AbstractSurrogate implements SurrogateInterface $response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value)); } } + + protected static function generateBodyEvalBoundary(): string + { + static $cookie; + $cookie = hash('xxh128', $cookie ?? $cookie = random_bytes(16), true); + $boundary = base64_encode($cookie); + + \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === \strlen($boundary)); + + return $boundary; + } } diff --git a/lib/symfony/http-kernel/HttpCache/Esi.php b/lib/symfony/http-kernel/HttpCache/Esi.php index cd6a00a10..5db840a80 100644 --- a/lib/symfony/http-kernel/HttpCache/Esi.php +++ b/lib/symfony/http-kernel/HttpCache/Esi.php @@ -27,13 +27,13 @@ use Symfony\Component\HttpFoundation\Response; */ class Esi extends AbstractSurrogate { - public function getName() + public function getName(): string { return 'esi'; } /** - * {@inheritdoc} + * @return void */ public function addSurrogateControl(Response $response) { @@ -42,10 +42,7 @@ class Esi extends AbstractSurrogate } } - /** - * {@inheritdoc} - */ - public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = '') + public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''): string { $html = sprintf('', $uri, @@ -60,10 +57,7 @@ class Esi extends AbstractSurrogate return $html; } - /** - * {@inheritdoc} - */ - public function process(Request $request, Response $response) + public function process(Request $request, Response $response): Response { $type = $response->headers->get('Content-Type'); if (empty($type)) { @@ -80,8 +74,8 @@ class Esi extends AbstractSurrogate $content = preg_replace('#.*?#s', '', $content); $content = preg_replace('#]+>#s', '', $content); + $boundary = self::generateBodyEvalBoundary(); $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); - $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); $i = 1; while (isset($chunks[$i])) { @@ -95,16 +89,10 @@ class Esi extends AbstractSurrogate throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); } - $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n", - var_export($options['src'], true), - var_export($options['alt'] ?? '', true), - isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' - ); - ++$i; - $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); - ++$i; + $chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n"; + $i += 2; } - $content = implode('', $chunks); + $content = $boundary.implode('', $chunks).$boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'ESI'); diff --git a/lib/symfony/http-kernel/HttpCache/HttpCache.php b/lib/symfony/http-kernel/HttpCache/HttpCache.php index 28be364c1..eabacfec6 100644 --- a/lib/symfony/http-kernel/HttpCache/HttpCache.php +++ b/lib/symfony/http-kernel/HttpCache/HttpCache.php @@ -29,13 +29,15 @@ use Symfony\Component\HttpKernel\TerminableInterface; */ class HttpCache implements HttpKernelInterface, TerminableInterface { - private $kernel; - private $store; - private $request; - private $surrogate; - private $surrogateCacheStrategy; - private $options = []; - private $traces = []; + public const BODY_EVAL_BOUNDARY_LENGTH = 24; + + private HttpKernelInterface $kernel; + private StoreInterface $store; + private Request $request; + private ?SurrogateInterface $surrogate; + private ?ResponseCacheStrategyInterface $surrogateCacheStrategy = null; + private array $options = []; + private array $traces = []; /** * Constructor. @@ -60,6 +62,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * on responses that don't explicitly state whether the response is * public or private via a Cache-Control directive. (default: Authorization and Cookie) * + * * skip_response_headers Set of response headers that are never cached even if a response is cacheable (public). + * (default: Set-Cookie) + * * * allow_reload Specifies whether the client can force a cache reload by including a * Cache-Control "no-cache" directive in the request. Set it to ``true`` * for compliance with RFC 2616. (default: false) @@ -78,6 +83,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * the cache can serve a stale response when an error is encountered (default: 60). * This setting is overridden by the stale-if-error HTTP Cache-Control extension * (see RFC 5861). + * + * * terminate_on_cache_hit Specifies if the kernel.terminate event should be dispatched even when the cache + * was hit (default: true). + * Unless your application needs to process events on cache hits, it is recommended + * to set this to false to avoid having to bootstrap the Symfony framework on a cache hit. */ public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = []) { @@ -86,18 +96,20 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $this->surrogate = $surrogate; // needed in case there is a fatal error because the backend is too slow to respond - register_shutdown_function([$this->store, 'cleanup']); + register_shutdown_function($this->store->cleanup(...)); $this->options = array_merge([ 'debug' => false, 'default_ttl' => 0, 'private_headers' => ['Authorization', 'Cookie'], + 'skip_response_headers' => ['Set-Cookie'], 'allow_reload' => false, 'allow_revalidate' => false, 'stale_while_revalidate' => 2, 'stale_if_error' => 60, 'trace_level' => 'none', 'trace_header' => 'X-Symfony-Cache', + 'terminate_on_cache_hit' => true, ], $options); if (!isset($options['trace_level'])) { @@ -107,25 +119,21 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Gets the current store. - * - * @return StoreInterface */ - public function getStore() + public function getStore(): StoreInterface { return $this->store; } /** * Returns an array of events that took place during processing of the last request. - * - * @return array */ - public function getTraces() + public function getTraces(): array { return $this->traces; } - private function addTraces(Response $response) + private function addTraces(Response $response): void { $traceString = null; @@ -144,10 +152,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Returns a log message for the events of the last request processing. - * - * @return string */ - public function getLog() + public function getLog(): string { $log = []; foreach ($this->traces as $request => $traces) { @@ -159,20 +165,16 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Gets the Request instance associated with the main request. - * - * @return Request */ - public function getRequest() + public function getRequest(): Request { return $this->request; } /** * Gets the Kernel instance. - * - * @return HttpKernelInterface */ - public function getKernel() + public function getKernel(): HttpKernelInterface { return $this->kernel; } @@ -180,19 +182,14 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Gets the Surrogate instance. * - * @return SurrogateInterface - * * @throws \LogicException */ - public function getSurrogate() + public function getSurrogate(): SurrogateInterface { return $this->surrogate; } - /** - * {@inheritdoc} - */ - public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism if (HttpKernelInterface::MAIN_REQUEST === $type) { @@ -246,10 +243,19 @@ class HttpCache implements HttpKernelInterface, TerminableInterface } /** - * {@inheritdoc} + * @return void */ public function terminate(Request $request, Response $response) { + // Do not call any listeners in case of a cache hit. + // This ensures identical behavior as if you had a separate + // reverse caching proxy such as Varnish and the like. + if ($this->options['terminate_on_cache_hit']) { + trigger_deprecation('symfony/http-kernel', '6.2', 'Setting "terminate_on_cache_hit" to "true" is deprecated and will be changed to "false" in Symfony 7.0.'); + } elseif (\in_array('fresh', $this->traces[$this->getTraceKey($request)] ?? [], true)) { + return; + } + if ($this->getKernel() instanceof TerminableInterface) { $this->getKernel()->terminate($request, $response); } @@ -259,10 +265,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * Forwards the Request to the backend without storing the Response in the cache. * * @param bool $catch Whether to process exceptions - * - * @return Response */ - protected function pass(Request $request, bool $catch = false) + protected function pass(Request $request, bool $catch = false): Response { $this->record($request, 'pass'); @@ -274,13 +278,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * * @param bool $catch Whether to process exceptions * - * @return Response - * * @throws \Exception * * @see RFC2616 13.10 */ - protected function invalidate(Request $request, bool $catch = false) + protected function invalidate(Request $request, bool $catch = false): Response { $response = $this->pass($request, $catch); @@ -322,11 +324,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * * @param bool $catch Whether to process exceptions * - * @return Response - * * @throws \Exception */ - protected function lookup(Request $request, bool $catch = false) + protected function lookup(Request $request, bool $catch = false): Response { try { $entry = $this->store->lookup($request); @@ -370,10 +370,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * GET request with the backend. * * @param bool $catch Whether to process exceptions - * - * @return Response */ - protected function validate(Request $request, Response $entry, bool $catch = false) + protected function validate(Request $request, Response $entry, bool $catch = false): Response { $subRequest = clone $request; @@ -433,10 +431,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * stores it in the cache if is cacheable. * * @param bool $catch Whether to process exceptions - * - * @return Response */ - protected function fetch(Request $request, bool $catch = false) + protected function fetch(Request $request, bool $catch = false): Response { $subRequest = clone $request; @@ -471,9 +467,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface */ protected function forward(Request $request, bool $catch = false, Response $entry = null) { - if ($this->surrogate) { - $this->surrogate->addSurrogateCapability($request); - } + $this->surrogate?->addSurrogateCapability($request); // always a "master" request (as the real master request can be in cache) $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST, $catch); @@ -523,7 +517,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface Anyway, a client that received a message without a "Date" header MUST add it. */ if (!$response->headers->has('Date')) { - $response->setDate(\DateTime::createFromFormat('U', time())); + $response->setDate(\DateTimeImmutable::createFromFormat('U', time())); } $this->processResponseBody($request, $response); @@ -539,10 +533,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Checks whether the cache entry is "fresh enough" to satisfy the Request. - * - * @return bool */ - protected function isFreshEnough(Request $request, Response $entry) + protected function isFreshEnough(Request $request, Response $entry): bool { if (!$entry->isFresh()) { return $this->lock($request, $entry); @@ -560,7 +552,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * * @return bool true if the cache entry can be returned even if it is staled, false otherwise */ - protected function lock(Request $request, Response $entry) + protected function lock(Request $request, Response $entry): bool { // try to acquire a lock to call the backend $lock = $this->store->lock($request); @@ -603,13 +595,24 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Writes the Response to the cache. * + * @return void + * * @throws \Exception */ protected function store(Request $request, Response $response) { try { - $this->store->write($request, $response); + $restoreHeaders = []; + foreach ($this->options['skip_response_headers'] as $header) { + if (!$response->headers->has($header)) { + continue; + } + $restoreHeaders[$header] = $response->headers->all($header); + $response->headers->remove($header); + } + + $this->store->write($request, $response); $this->record($request, 'store'); $response->headers->set('Age', $response->getAge()); @@ -619,6 +622,10 @@ class HttpCache implements HttpKernelInterface, TerminableInterface if ($this->options['debug']) { throw $e; } + } finally { + foreach ($restoreHeaders as $header => $values) { + $response->headers->set($header, $values); + } } // now that the response is cached, release the lock @@ -628,15 +635,25 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Restores the Response body. */ - private function restoreResponseBody(Request $request, Response $response) + private function restoreResponseBody(Request $request, Response $response): void { if ($response->headers->has('X-Body-Eval')) { + \assert(self::BODY_EVAL_BOUNDARY_LENGTH === 24); + ob_start(); - if ($response->headers->has('X-Body-File')) { - include $response->headers->get('X-Body-File'); - } else { - eval('; ?>'.$response->getContent().'getContent(); + $boundary = substr($content, 0, 24); + $j = strpos($content, $boundary, 24); + echo substr($content, 24, $j - 24); + $i = $j + 24; + + while (false !== $j = strpos($content, $boundary, $i)) { + [$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4); + $i = $j + 24; + + echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors); + echo $part; } $response->setContent(ob_get_clean()); @@ -657,9 +674,12 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $response->headers->remove('X-Body-File'); } + /** + * @return void + */ protected function processResponseBody(Request $request, Response $response) { - if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) { + if ($this->surrogate?->needsParsing($response)) { $this->surrogate->process($request, $response); } } @@ -688,7 +708,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Records that an event took place. */ - private function record(Request $request, string $event) + private function record(Request $request, string $event): void { $this->traces[$this->getTraceKey($request)][] = $event; } @@ -713,12 +733,13 @@ class HttpCache implements HttpKernelInterface, TerminableInterface private function mayServeStaleWhileRevalidate(Response $entry): bool { $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); + $timeout ??= $this->options['stale_while_revalidate']; - if (null === $timeout) { - $timeout = $this->options['stale_while_revalidate']; - } + $age = $entry->getAge(); + $maxAge = $entry->getMaxAge() ?? 0; + $ttl = $maxAge - $age; - return abs($entry->getTtl() ?? 0) < $timeout; + return abs($ttl) < $timeout; } /** diff --git a/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php b/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php index cf8682257..57b5d2196 100644 --- a/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php +++ b/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php @@ -34,10 +34,11 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface */ private const INHERIT_DIRECTIVES = ['public', 'immutable']; - private $embeddedResponses = 0; - private $isNotCacheableResponseEmbedded = false; - private $age = 0; - private $flagDirectives = [ + private int $embeddedResponses = 0; + private bool $isNotCacheableResponseEmbedded = false; + private int $age = 0; + private \DateTimeInterface|null|false $lastModified = null; + private array $flagDirectives = [ 'no-cache' => null, 'no-store' => null, 'no-transform' => null, @@ -47,14 +48,14 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface 'private' => null, 'immutable' => null, ]; - private $ageDirectives = [ + private array $ageDirectives = [ 'max-age' => null, 's-maxage' => null, 'expires' => null, ]; /** - * {@inheritdoc} + * @return void */ public function add(Response $response) { @@ -90,10 +91,15 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface $expires = $response->getExpires(); $expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null; $this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0, $isHeuristicallyCacheable); + + if (false !== $this->lastModified) { + $lastModified = $response->getLastModified(); + $this->lastModified = $lastModified ? max($this->lastModified, $lastModified) : false; + } } /** - * {@inheritdoc} + * @return void */ public function update(Response $response) { @@ -102,17 +108,16 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface return; } - // Remove validation related headers of the master response, - // because some of the response content comes from at least - // one embedded response (which likely has a different caching strategy). + // Remove Etag since it cannot be merged from embedded responses. $response->setEtag(null); - $response->setLastModified(null); $this->add($response); $response->headers->set('Age', $this->age); if ($this->isNotCacheableResponseEmbedded) { + $response->setLastModified(null); + if ($this->flagDirectives['no-store']) { $response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate'); } else { @@ -122,6 +127,8 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface return; } + $response->setLastModified($this->lastModified ?: null); + $flags = array_filter($this->flagDirectives); if (isset($flags['must-revalidate'])) { @@ -147,7 +154,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface if (is_numeric($this->ageDirectives['expires'])) { $date = clone $response->getDate(); - $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds'); + $date = $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds'); $response->setExpires($date); } } @@ -162,17 +169,14 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface // RFC2616: A response received with a status code of 200, 203, 300, 301 or 410 // MAY be stored by a cache […] unless a cache-control directive prohibits caching. if ($response->headers->hasCacheControlDirective('no-cache') - || $response->headers->getCacheControlDirective('no-store') + || $response->headers->hasCacheControlDirective('no-store') ) { return true; } - // Last-Modified and Etag headers cannot be merged, they render the response uncacheable + // Etag headers cannot be merged, they render the response uncacheable // by default (except if the response also has max-age etc.). - if (\in_array($response->getStatusCode(), [200, 203, 300, 301, 410]) - && null === $response->getLastModified() - && null === $response->getEtag() - ) { + if (null === $response->getEtag() && \in_array($response->getStatusCode(), [200, 203, 300, 301, 410])) { return false; } @@ -211,7 +215,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface * as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve * as an upper bound. In this case, we can proceed and possibly keep the directive on the final response. */ - private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable) + private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable): void { if (null === $value) { if ($isHeuristicallyCacheable) { diff --git a/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php b/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php index e282299ae..33c8bd941 100644 --- a/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php +++ b/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php @@ -27,11 +27,15 @@ interface ResponseCacheStrategyInterface { /** * Adds a Response. + * + * @return void */ public function add(Response $response); /** * Updates the Response HTTP headers based on the embedded Responses. + * + * @return void */ public function update(Response $response); } diff --git a/lib/symfony/http-kernel/HttpCache/Ssi.php b/lib/symfony/http-kernel/HttpCache/Ssi.php index f114e05cf..b17c90ac6 100644 --- a/lib/symfony/http-kernel/HttpCache/Ssi.php +++ b/lib/symfony/http-kernel/HttpCache/Ssi.php @@ -21,16 +21,13 @@ use Symfony\Component\HttpFoundation\Response; */ class Ssi extends AbstractSurrogate { - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'ssi'; } /** - * {@inheritdoc} + * @return void */ public function addSurrogateControl(Response $response) { @@ -39,18 +36,12 @@ class Ssi extends AbstractSurrogate } } - /** - * {@inheritdoc} - */ - public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = '') + public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''): string { return sprintf('', $uri); } - /** - * {@inheritdoc} - */ - public function process(Request $request, Response $response) + public function process(Request $request, Response $response): Response { $type = $response->headers->get('Content-Type'); if (empty($type)) { @@ -64,9 +55,8 @@ class Ssi extends AbstractSurrogate // we don't use a proper XML parser here as we can have SSI tags in a plain text response $content = $response->getContent(); - + $boundary = self::generateBodyEvalBoundary(); $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); - $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); $i = 1; while (isset($chunks[$i])) { @@ -80,14 +70,10 @@ class Ssi extends AbstractSurrogate throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); } - $chunks[$i] = sprintf('surrogate->handle($this, %s, \'\', false) ?>'."\n", - var_export($options['virtual'], true) - ); - ++$i; - $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); - ++$i; + $chunks[$i] = $boundary.$options['virtual']."\n\n\n"; + $i += 2; } - $content = implode('', $chunks); + $content = $boundary.implode('', $chunks).$boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'SSI'); diff --git a/lib/symfony/http-kernel/HttpCache/Store.php b/lib/symfony/http-kernel/HttpCache/Store.php index 8087e0cb1..3f21e383f 100644 --- a/lib/symfony/http-kernel/HttpCache/Store.php +++ b/lib/symfony/http-kernel/HttpCache/Store.php @@ -26,10 +26,10 @@ class Store implements StoreInterface { protected $root; /** @var \SplObjectStorage */ - private $keyCache; + private \SplObjectStorage $keyCache; /** @var array */ - private $locks = []; - private $options; + private array $locks = []; + private array $options; /** * Constructor. @@ -55,6 +55,8 @@ class Store implements StoreInterface /** * Cleanups storage. + * + * @return void */ public function cleanup() { @@ -72,7 +74,7 @@ class Store implements StoreInterface * * @return bool|string true if the lock is acquired, the path to the current lock otherwise */ - public function lock(Request $request) + public function lock(Request $request): bool|string { $key = $this->getCacheKey($request); @@ -99,7 +101,7 @@ class Store implements StoreInterface * * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise */ - public function unlock(Request $request) + public function unlock(Request $request): bool { $key = $this->getCacheKey($request); @@ -114,7 +116,7 @@ class Store implements StoreInterface return false; } - public function isLocked(Request $request) + public function isLocked(Request $request): bool { $key = $this->getCacheKey($request); @@ -136,10 +138,8 @@ class Store implements StoreInterface /** * Locates a cached Response for the Request provided. - * - * @return Response|null */ - public function lookup(Request $request) + public function lookup(Request $request): ?Response { $key = $this->getCacheKey($request); @@ -178,11 +178,9 @@ class Store implements StoreInterface * Existing entries are read and any that match the response are removed. This * method calls write with the new list of cache entries. * - * @return string - * * @throws \RuntimeException */ - public function write(Request $request, Response $response) + public function write(Request $request, Response $response): string { $key = $this->getCacheKey($request); $storedEnv = $this->persistRequest($request); @@ -197,7 +195,7 @@ class Store implements StoreInterface if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) { throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.'); } - // Everything seems ok, omit writing content to disk + // Everything seems ok, omit writing content to disk } else { $digest = $this->generateContentDigest($response); $response->headers->set('X-Content-Digest', $digest); @@ -242,17 +240,17 @@ class Store implements StoreInterface /** * Returns content digest for $response. - * - * @return string */ - protected function generateContentDigest(Response $response) + protected function generateContentDigest(Response $response): string { - return 'en'.hash('sha256', $response->getContent()); + return 'en'.hash('xxh128', $response->getContent()); } /** * Invalidates all cache entries that match the request. * + * @return void + * * @throws \RuntimeException */ public function invalidate(Request $request) @@ -324,7 +322,7 @@ class Store implements StoreInterface * * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise */ - public function purge(string $url) + public function purge(string $url): bool { $http = preg_replace('#^https:#', 'http:', $url); $https = preg_replace('#^http:#', 'https:', $url); @@ -419,6 +417,9 @@ class Store implements StoreInterface return true; } + /** + * @return string + */ public function getPath(string $key) { return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); @@ -433,10 +434,8 @@ class Store implements StoreInterface * If the same URI can have more than one representation, based on some * headers, use a Vary header to indicate them, and each representation will * be stored independently under the same cache key. - * - * @return string */ - protected function generateCacheKey(Request $request) + protected function generateCacheKey(Request $request): string { return 'md'.hash('sha256', $request->getUri()); } @@ -475,15 +474,25 @@ class Store implements StoreInterface /** * Restores a Response from the HTTP headers and body. */ - private function restoreResponse(array $headers, string $path = null): Response + private function restoreResponse(array $headers, string $path = null): ?Response { $status = $headers['X-Status'][0]; unset($headers['X-Status']); + $content = null; if (null !== $path) { $headers['X-Body-File'] = [$path]; + unset($headers['x-body-file']); + + if ($headers['X-Body-Eval'] ?? $headers['x-body-eval'] ?? false) { + $content = file_get_contents($path); + \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === 24); + if (48 > \strlen($content) || substr($content, -24) !== substr($content, 0, 24)) { + return null; + } + } } - return new Response($path, $status, $headers); + return new Response($content, $status, $headers); } } diff --git a/lib/symfony/http-kernel/HttpCache/StoreInterface.php b/lib/symfony/http-kernel/HttpCache/StoreInterface.php index 3d07ef3fc..b73cb7a9e 100644 --- a/lib/symfony/http-kernel/HttpCache/StoreInterface.php +++ b/lib/symfony/http-kernel/HttpCache/StoreInterface.php @@ -26,10 +26,8 @@ interface StoreInterface { /** * Locates a cached Response for the Request provided. - * - * @return Response|null */ - public function lookup(Request $request); + public function lookup(Request $request): ?Response; /** * Writes a cache entry to the store for the given Request and Response. @@ -39,10 +37,12 @@ interface StoreInterface * * @return string The key under which the response is stored */ - public function write(Request $request, Response $response); + public function write(Request $request, Response $response): string; /** * Invalidates all cache entries that match the request. + * + * @return void */ public function invalidate(Request $request); @@ -51,31 +51,33 @@ interface StoreInterface * * @return bool|string true if the lock is acquired, the path to the current lock otherwise */ - public function lock(Request $request); + public function lock(Request $request): bool|string; /** * Releases the lock for the given Request. * * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise */ - public function unlock(Request $request); + public function unlock(Request $request): bool; /** * Returns whether or not a lock exists. * * @return bool true if lock exists, false otherwise */ - public function isLocked(Request $request); + public function isLocked(Request $request): bool; /** * Purges data for the given URL. * * @return bool true if the URL exists and has been purged, false otherwise */ - public function purge(string $url); + public function purge(string $url): bool; /** * Cleanups storage. + * + * @return void */ public function cleanup(); } diff --git a/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php b/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php index 3f3c74a97..e444458f7 100644 --- a/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php +++ b/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php @@ -18,27 +18,23 @@ interface SurrogateInterface { /** * Returns surrogate name. - * - * @return string */ - public function getName(); + public function getName(): string; /** * Returns a new cache strategy instance. - * - * @return ResponseCacheStrategyInterface */ - public function createCacheStrategy(); + public function createCacheStrategy(): ResponseCacheStrategyInterface; /** * Checks that at least one surrogate has Surrogate capability. - * - * @return bool */ - public function hasSurrogateCapability(Request $request); + public function hasSurrogateCapability(Request $request): bool; /** * Adds Surrogate-capability to the given Request. + * + * @return void */ public function addSurrogateCapability(Request $request); @@ -46,42 +42,36 @@ interface SurrogateInterface * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. * * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. + * + * @return void */ public function addSurrogateControl(Response $response); /** * Checks that the Response needs to be parsed for Surrogate tags. - * - * @return bool */ - public function needsParsing(Response $response); + public function needsParsing(Response $response): bool; /** * Renders a Surrogate tag. * - * @param string $alt An alternate URI - * @param string $comment A comment to add as an esi:include tag - * - * @return string + * @param string|null $alt An alternate URI + * @param string $comment A comment to add as an esi:include tag */ - public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''); + public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''): string; /** * Replaces a Response Surrogate tags with the included resource content. - * - * @return Response */ - public function process(Request $request, Response $response); + public function process(Request $request, Response $response): Response; /** * Handles a Surrogate from the cache. * * @param string $alt An alternative URI * - * @return string - * * @throws \RuntimeException * @throws \Exception */ - public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors); + public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors): string; } diff --git a/lib/symfony/http-kernel/HttpClientKernel.php b/lib/symfony/http-kernel/HttpClientKernel.php index 58ca82e5a..1d8c30278 100644 --- a/lib/symfony/http-kernel/HttpClientKernel.php +++ b/lib/symfony/http-kernel/HttpClientKernel.php @@ -31,7 +31,7 @@ class_exists(ResponseHeaderBag::class); */ final class HttpClientKernel implements HttpKernelInterface { - private $client; + private HttpClientInterface $client; public function __construct(HttpClientInterface $client = null) { diff --git a/lib/symfony/http-kernel/HttpKernel.php b/lib/symfony/http-kernel/HttpKernel.php index 38102e252..d2cf4eaee 100644 --- a/lib/symfony/http-kernel/HttpKernel.php +++ b/lib/symfony/http-kernel/HttpKernel.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; @@ -53,27 +54,31 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface protected $dispatcher; protected $resolver; protected $requestStack; - private $argumentResolver; + private ArgumentResolverInterface $argumentResolver; + private bool $handleAllThrowables; - public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null, ArgumentResolverInterface $argumentResolver = null) + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null, ArgumentResolverInterface $argumentResolver = null, bool $handleAllThrowables = false) { $this->dispatcher = $dispatcher; $this->resolver = $resolver; $this->requestStack = $requestStack ?? new RequestStack(); $this->argumentResolver = $argumentResolver ?? new ArgumentResolver(); + $this->handleAllThrowables = $handleAllThrowables; } - /** - * {@inheritdoc} - */ - public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); $this->requestStack->push($request); + $response = null; try { - return $this->handleRaw($request, $type); - } catch (\Exception $e) { + return $response = $this->handleRaw($request, $type); + } catch (\Throwable $e) { + if ($e instanceof \Error && !$this->handleAllThrowables) { + throw $e; + } + if ($e instanceof RequestExceptionInterface) { $e = new BadRequestHttpException($e->getMessage(), $e); } @@ -83,14 +88,27 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface throw $e; } - return $this->handleThrowable($e, $request, $type); + return $response = $this->handleThrowable($e, $request, $type); } finally { $this->requestStack->pop(); + + if ($response instanceof StreamedResponse && $callback = $response->getCallback()) { + $requestStack = $this->requestStack; + + $response->setCallback(static function () use ($request, $callback, $requestStack) { + $requestStack->push($request); + try { + $callback(); + } finally { + $requestStack->pop(); + } + }); + } } } /** - * {@inheritdoc} + * @return void */ public function terminate(Request $request, Response $response) { @@ -100,9 +118,9 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface /** * @internal */ - public function terminateWithException(\Throwable $exception, Request $request = null) + public function terminateWithException(\Throwable $exception, Request $request = null): void { - if (!$request = $request ?: $this->requestStack->getMainRequest()) { + if (!$request ??= $this->requestStack->getMainRequest()) { throw $exception; } @@ -152,9 +170,9 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface $controller = $event->getController(); // controller arguments - $arguments = $this->argumentResolver->getArguments($request, $controller); + $arguments = $this->argumentResolver->getArguments($request, $controller, $event->getControllerReflector()); - $event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type); + $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); $arguments = $event->getArguments(); @@ -164,7 +182,7 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface // view if (!$response instanceof Response) { - $event = new ViewEvent($this, $request, $type, $response); + $event = new ViewEvent($this, $request, $type, $response, $event); $this->dispatcher->dispatch($event, KernelEvents::VIEW); if ($event->hasResponse()) { @@ -207,15 +225,13 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface * operations such as {@link RequestStack::getParentRequest()} can lead to * weird results. */ - private function finishRequest(Request $request, int $type) + private function finishRequest(Request $request, int $type): void { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); } /** * Handles a throwable by trying to convert it to a Response. - * - * @throws \Exception */ private function handleThrowable(\Throwable $e, Request $request, int $type): Response { @@ -247,7 +263,11 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface try { return $this->filterResponse($response, $request, $type); - } catch (\Exception $e) { + } catch (\Throwable $e) { + if ($e instanceof \Error && !$this->handleAllThrowables) { + throw $e; + } + return $response; } } @@ -255,10 +275,10 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface /** * Returns a human-readable string for the specified variable. */ - private function varToString($var): string + private function varToString(mixed $var): string { if (\is_object($var)) { - return sprintf('an object of type %s', \get_class($var)); + return sprintf('an object of type %s', $var::class); } if (\is_array($var)) { diff --git a/lib/symfony/http-kernel/HttpKernelBrowser.php b/lib/symfony/http-kernel/HttpKernelBrowser.php index 643134f1b..0f3630e7f 100644 --- a/lib/symfony/http-kernel/HttpKernelBrowser.php +++ b/lib/symfony/http-kernel/HttpKernelBrowser.php @@ -31,7 +31,7 @@ use Symfony\Component\HttpFoundation\Response; class HttpKernelBrowser extends AbstractBrowser { protected $kernel; - private $catchExceptions = true; + private bool $catchExceptions = true; /** * @param array $server The server parameters (equivalent of $_SERVER) @@ -47,6 +47,8 @@ class HttpKernelBrowser extends AbstractBrowser /** * Sets whether to catch exceptions when the kernel is handling a request. + * + * @return void */ public function catchExceptions(bool $catchExceptions) { @@ -54,8 +56,6 @@ class HttpKernelBrowser extends AbstractBrowser } /** - * {@inheritdoc} - * * @param Request $request * * @return Response @@ -72,8 +72,6 @@ class HttpKernelBrowser extends AbstractBrowser } /** - * {@inheritdoc} - * * @param Request $request * * @return string @@ -87,7 +85,7 @@ class HttpKernelBrowser extends AbstractBrowser $requires = ''; foreach (get_declared_classes() as $class) { - if (0 === strpos($class, 'ComposerAutoloaderInit')) { + if (str_starts_with($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); $file = \dirname($r->getFileName(), 2).'/autoload.php'; if (file_exists($file)) { @@ -114,6 +112,9 @@ EOF; return $code.$this->getHandleScript(); } + /** + * @return string + */ protected function getHandleScript() { return <<<'EOF' @@ -127,12 +128,7 @@ echo serialize($response); EOF; } - /** - * {@inheritdoc} - * - * @return Request - */ - protected function filterRequest(DomRequest $request) + protected function filterRequest(DomRequest $request): Request { $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $server = $request->getServer(), $request->getContent()); if (!isset($server['HTTP_ACCEPT'])) { @@ -156,10 +152,8 @@ EOF; * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. * * @see UploadedFile - * - * @return array */ - protected function filterFiles(array $files) + protected function filterFiles(array $files): array { $filtered = []; foreach ($files as $key => $value) { @@ -190,13 +184,9 @@ EOF; } /** - * {@inheritdoc} - * * @param Response $response - * - * @return DomResponse */ - protected function filterResponse(object $response) + protected function filterResponse(object $response): DomResponse { // this is needed to support StreamedResponse ob_start(); diff --git a/lib/symfony/http-kernel/HttpKernelInterface.php b/lib/symfony/http-kernel/HttpKernelInterface.php index 0449240e7..f6c017a4c 100644 --- a/lib/symfony/http-kernel/HttpKernelInterface.php +++ b/lib/symfony/http-kernel/HttpKernelInterface.php @@ -40,9 +40,7 @@ interface HttpKernelInterface * (one of HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST) * @param bool $catch Whether to catch exceptions or not * - * @return Response - * * @throws \Exception When an Exception occurs during processing */ - public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true); + public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true): Response; } diff --git a/lib/symfony/http-kernel/Kernel.php b/lib/symfony/http-kernel/Kernel.php index b2ccc7d95..39170e833 100644 --- a/lib/symfony/http-kernel/Kernel.php +++ b/lib/symfony/http-kernel/Kernel.php @@ -11,15 +11,13 @@ namespace Symfony\Component\HttpKernel; -use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; -use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; use Symfony\Component\Config\Builder\ConfigBuilderGenerator; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Loader\DelegatingLoader; use Symfony\Component\Config\Loader\LoaderResolver; -use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\RemoveBuildParametersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; @@ -68,25 +66,25 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl protected $booted = false; protected $startTime; - private $projectDir; - private $warmupDir; - private $requestStackSize = 0; - private $resetServices = false; + private string $projectDir; + private ?string $warmupDir = null; + private int $requestStackSize = 0; + private bool $resetServices = false; /** * @var array */ - private static $freshCache = []; + private static array $freshCache = []; - public const VERSION = '5.4.20'; - public const VERSION_ID = 50420; - public const MAJOR_VERSION = 5; + public const VERSION = '6.4.0'; + public const VERSION_ID = 60400; + public const MAJOR_VERSION = 6; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 20; + public const RELEASE_VERSION = 0; public const EXTRA_VERSION = ''; - public const END_OF_MAINTENANCE = '11/2024'; - public const END_OF_LIFE = '11/2025'; + public const END_OF_MAINTENANCE = '11/2026'; + public const END_OF_LIFE = '11/2027'; public function __construct(string $environment, bool $debug) { @@ -106,7 +104,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl } /** - * {@inheritdoc} + * @return void */ public function boot() { @@ -137,7 +135,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl } /** - * {@inheritdoc} + * @return void */ public function reboot(?string $warmupDir) { @@ -147,7 +145,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl } /** - * {@inheritdoc} + * @return void */ public function terminate(Request $request, Response $response) { @@ -161,7 +159,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl } /** - * {@inheritdoc} + * @return void */ public function shutdown() { @@ -181,10 +179,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl $this->resetServices = false; } - /** - * {@inheritdoc} - */ - public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { if (!$this->booted) { $container = $this->container ?? $this->preBoot(); @@ -207,26 +202,18 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Gets an HTTP kernel from the container. - * - * @return HttpKernelInterface */ - protected function getHttpKernel() + protected function getHttpKernel(): HttpKernelInterface { return $this->container->get('http_kernel'); } - /** - * {@inheritdoc} - */ - public function getBundles() + public function getBundles(): array { return $this->bundles; } - /** - * {@inheritdoc} - */ - public function getBundle(string $name) + public function getBundle(string $name): BundleInterface { if (!isset($this->bundles[$name])) { throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, get_debug_type($this))); @@ -235,10 +222,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl return $this->bundles[$name]; } - /** - * {@inheritdoc} - */ - public function locateResource(string $name) + public function locateResource(string $name): string { if ('@' !== $name[0]) { throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); @@ -262,30 +246,22 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); } - /** - * {@inheritdoc} - */ - public function getEnvironment() + public function getEnvironment(): string { return $this->environment; } - /** - * {@inheritdoc} - */ - public function isDebug() + public function isDebug(): bool { return $this->debug; } /** * Gets the application root dir (path of the project's composer file). - * - * @return string */ - public function getProjectDir() + public function getProjectDir(): string { - if (null === $this->projectDir) { + if (!isset($this->projectDir)) { $r = new \ReflectionObject($this); if (!is_file($dir = $r->getFileName())) { @@ -305,10 +281,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl return $this->projectDir; } - /** - * {@inheritdoc} - */ - public function getContainer() + public function getContainer(): ContainerInterface { if (!$this->container) { throw new \LogicException('Cannot retrieve the container from a non-booted kernel.'); @@ -320,48 +293,33 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * @internal */ - public function setAnnotatedClassCache(array $annotatedClasses) + public function setAnnotatedClassCache(array $annotatedClasses): void { file_put_contents(($this->warmupDir ?: $this->getBuildDir()).'/annotations.map', sprintf('debug && null !== $this->startTime ? $this->startTime : -\INF; } - /** - * {@inheritdoc} - */ - public function getCacheDir() + public function getCacheDir(): string { return $this->getProjectDir().'/var/cache/'.$this->environment; } - /** - * {@inheritdoc} - */ public function getBuildDir(): string { // Returns $this->getCacheDir() for backward compatibility return $this->getCacheDir(); } - /** - * {@inheritdoc} - */ - public function getLogDir() + public function getLogDir(): string { return $this->getProjectDir().'/var/log'; } - /** - * {@inheritdoc} - */ - public function getCharset() + public function getCharset(): string { return 'UTF-8'; } @@ -377,6 +335,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Initializes bundles. * + * @return void + * * @throws \LogicException if two bundles share a common name */ protected function initializeBundles() @@ -396,6 +356,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl * The extension point similar to the Bundle::build() method. * * Use this method to register compiler passes and manipulate the container during the building process. + * + * @return void */ protected function build(ContainerBuilder $container) { @@ -405,10 +367,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl * Gets the container class. * * @throws \InvalidArgumentException If the generated classname is invalid - * - * @return string */ - protected function getContainerClass() + protected function getContainerClass(): string { $class = static::class; $class = str_contains($class, "@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class; @@ -425,10 +385,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl * Gets the container's base class. * * All names except Container must be fully qualified. - * - * @return string */ - protected function getContainerBaseClass() + protected function getContainerBaseClass(): string { return 'Container'; } @@ -438,6 +396,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl * * The built version of the service container is used when fresh, otherwise the * container is built. + * + * @return void */ protected function initializeContainer() { @@ -467,13 +427,13 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl try { is_dir($buildDir) ?: mkdir($buildDir, 0777, true); - if ($lock = fopen($cachePath.'.lock', 'w')) { + if ($lock = fopen($cachePath.'.lock', 'w+')) { if (!flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock) && !flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) { fclose($lock); $lock = null; } elseif (!is_file($cachePath) || !\is_object($this->container = include $cachePath)) { $this->container = null; - } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { + } elseif (!$oldContainer || $this->container::class !== $oldContainer->name) { flock($lock, \LOCK_UN); fclose($lock); $this->container->set('kernel', $this); @@ -521,7 +481,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl // Remove frames added by DebugClassLoader. for ($i = \count($backtrace) - 2; 0 < $i; --$i) { - if (\in_array($backtrace[$i]['class'] ?? null, [DebugClassLoader::class, LegacyDebugClassLoader::class], true)) { + if (DebugClassLoader::class === ($backtrace[$i]['class'] ?? null)) { $backtrace = [$backtrace[$i + 1]]; break; } @@ -563,7 +523,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl $this->container = require $cachePath; $this->container->set('kernel', $this); - if ($oldContainer && \get_class($this->container) !== $oldContainer->name) { + if ($oldContainer && $this->container::class !== $oldContainer->name) { // Because concurrent requests might still be using them, // old container files are not removed immediately, // but on a next dump of the container. @@ -579,29 +539,27 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl touch($oldContainerDir.'.legacy'); } - $preload = $this instanceof WarmableInterface ? (array) $this->warmUp($this->container->getParameter('kernel.cache_dir')) : []; + $preload = $this instanceof WarmableInterface ? (array) $this->warmUp($this->container->getParameter('kernel.cache_dir'), $buildDir) : []; if ($this->container->has('cache_warmer')) { - $preload = array_merge($preload, (array) $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'))); + $preload = array_merge($preload, (array) $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'), $buildDir)); } - if ($preload && method_exists(Preloader::class, 'append') && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { + if ($preload && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { Preloader::append($preloadFile, $preload); } } /** * Returns the kernel parameters. - * - * @return array */ - protected function getKernelParameters() + protected function getKernelParameters(): array { $bundles = []; $bundlesMetadata = []; foreach ($this->bundles as $name => $bundle) { - $bundles[$name] = \get_class($bundle); + $bundles[$name] = $bundle::class; $bundlesMetadata[$name] = [ 'path' => $bundle->getPath(), 'namespace' => $bundle->getNamespace(), @@ -612,6 +570,10 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), 'kernel.environment' => $this->environment, 'kernel.runtime_environment' => '%env(default:kernel.environment:APP_RUNTIME_ENV)%', + 'kernel.runtime_mode' => '%env(query_string:default:container.runtime_mode:APP_RUNTIME_MODE)%', + 'kernel.runtime_mode.web' => '%env(bool:default::key:web:default:kernel.runtime_mode:)%', + 'kernel.runtime_mode.cli' => '%env(not:default:kernel.runtime_mode.web:)%', + 'kernel.runtime_mode.worker' => '%env(bool:default::key:worker:default:kernel.runtime_mode:)%', 'kernel.debug' => $this->debug, 'kernel.build_dir' => realpath($buildDir = $this->warmupDir ?: $this->getBuildDir()) ?: $buildDir, 'kernel.cache_dir' => realpath($cacheDir = ($this->getCacheDir() === $this->getBuildDir() ? ($this->warmupDir ?: $this->getCacheDir()) : $this->getCacheDir())) ?: $cacheDir, @@ -626,11 +588,9 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Builds the service container. * - * @return ContainerBuilder - * * @throws \RuntimeException */ - protected function buildContainer() + protected function buildContainer(): ContainerBuilder { foreach (['cache' => $this->getCacheDir(), 'build' => $this->warmupDir ?: $this->getBuildDir(), 'logs' => $this->getLogDir()] as $name => $dir) { if (!is_dir($dir)) { @@ -645,11 +605,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl $container = $this->getContainerBuilder(); $container->addObjectResource($this); $this->prepareContainer($container); - - if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { - trigger_deprecation('symfony/http-kernel', '5.3', 'Returning a ContainerBuilder from "%s::registerContainerConfiguration()" is deprecated.', get_debug_type($this)); - $container->merge($cont); - } + $this->registerContainerConfiguration($this->getContainerLoader($container)); $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); @@ -658,6 +614,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Prepares the ContainerBuilder before it is compiled. + * + * @return void */ protected function prepareContainer(ContainerBuilder $container) { @@ -688,10 +646,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Gets a new ContainerBuilder instance used to build the service container. - * - * @return ContainerBuilder */ - protected function getContainerBuilder() + protected function getContainerBuilder(): ContainerBuilder { $container = new ContainerBuilder(); $container->getParameterBag()->add($this->getKernelParameters()); @@ -702,9 +658,6 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl if ($this instanceof CompilerPassInterface) { $container->addCompilerPass($this, PassConfig::TYPE_BEFORE_OPTIMIZATION, -10000); } - if (class_exists(\ProxyManager\Configuration::class) && class_exists(RuntimeInstantiator::class)) { - $container->setProxyInstantiator(new RuntimeInstantiator()); - } return $container; } @@ -714,14 +667,35 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl * * @param string $class The name of the class to generate * @param string $baseClass The name of the container's base class + * + * @return void */ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, string $class, string $baseClass) { // cache the container $dumper = new PhpDumper($container); - if (class_exists(\ProxyManager\Configuration::class) && class_exists(ProxyDumper::class)) { - $dumper->setProxyDumper(new ProxyDumper()); + $buildParameters = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof RemoveBuildParametersPass) { + $buildParameters = array_merge($buildParameters, $pass->getRemovedParameters()); + } + } + + $inlineFactories = false; + if (isset($buildParameters['.container.dumper.inline_factories'])) { + $inlineFactories = $buildParameters['.container.dumper.inline_factories']; + } elseif ($container->hasParameter('container.dumper.inline_factories')) { + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%1$s" instead.', 'container.dumper.inline_factories'); + $inlineFactories = $container->getParameter('container.dumper.inline_factories'); + } + + $inlineClassLoader = $this->debug; + if (isset($buildParameters['.container.dumper.inline_class_loader'])) { + $inlineClassLoader = $buildParameters['.container.dumper.inline_class_loader']; + } elseif ($container->hasParameter('container.dumper.inline_class_loader')) { + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%1$s" instead.', 'container.dumper.inline_class_loader'); + $inlineClassLoader = $container->getParameter('container.dumper.inline_class_loader'); } $content = $dumper->dump([ @@ -730,6 +704,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug, + 'inline_factories' => $inlineFactories, + 'inline_class_loader' => $inlineClassLoader, 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(), 'preload_classes' => array_map('get_class', $this->bundles), ]); @@ -752,10 +728,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Returns a loader for the container. - * - * @return DelegatingLoader */ - protected function getContainerLoader(ContainerInterface $container) + protected function getContainerLoader(ContainerInterface $container): DelegatingLoader { $env = $this->getEnvironment(); $locator = new FileLocator($this); @@ -778,7 +752,9 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl $this->startTime = microtime(true); } if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) { - putenv('SHELL_VERBOSITY=3'); + if (\function_exists('putenv')) { + putenv('SHELL_VERBOSITY=3'); + } $_ENV['SHELL_VERBOSITY'] = 3; $_SERVER['SHELL_VERBOSITY'] = 3; } @@ -805,10 +781,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl * We don't use the PHP php_strip_whitespace() function * as we want the content to be readable and well-formatted. * - * @return string + * @deprecated since Symfony 6.4 without replacement */ - public static function stripComments(string $source) + public static function stripComments(string $source): string { + trigger_deprecation('symfony/http-kernel', '6.4', 'Method "%s()" is deprecated without replacement.', __METHOD__); + if (!\function_exists('token_get_all')) { return $source; } @@ -862,14 +840,14 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl return $output; } - /** - * @return array - */ - public function __sleep() + public function __sleep(): array { return ['environment', 'debug']; } + /** + * @return void + */ public function __wakeup() { if (\is_object($this->environment) || \is_object($this->debug)) { diff --git a/lib/symfony/http-kernel/KernelInterface.php b/lib/symfony/http-kernel/KernelInterface.php index 41135a6d8..14a053ab3 100644 --- a/lib/symfony/http-kernel/KernelInterface.php +++ b/lib/symfony/http-kernel/KernelInterface.php @@ -20,10 +20,6 @@ use Symfony\Component\HttpKernel\Bundle\BundleInterface; * * It manages an environment made of application kernel and bundles. * - * @method string getBuildDir() Returns the build directory - not implementing it is deprecated since Symfony 5.2. - * This directory should be used to store build artifacts, and can be read-only at runtime. - * Caches written at runtime should be stored in the "cache directory" ({@see KernelInterface::getCacheDir()}). - * * @author Fabien Potencier */ interface KernelInterface extends HttpKernelInterface @@ -33,15 +29,19 @@ interface KernelInterface extends HttpKernelInterface * * @return iterable */ - public function registerBundles(); + public function registerBundles(): iterable; /** * Loads the container configuration. + * + * @return void */ public function registerContainerConfiguration(LoaderInterface $loader); /** * Boots the current kernel. + * + * @return void */ public function boot(); @@ -49,6 +49,8 @@ interface KernelInterface extends HttpKernelInterface * Shutdowns the kernel. * * This method is mainly useful when doing functional testing. + * + * @return void */ public function shutdown(); @@ -57,16 +59,14 @@ interface KernelInterface extends HttpKernelInterface * * @return array */ - public function getBundles(); + public function getBundles(): array; /** * Returns a bundle. * - * @return BundleInterface - * * @throws \InvalidArgumentException when the bundle is not enabled */ - public function getBundle(string $name); + public function getBundle(string $name): BundleInterface; /** * Returns the file path for a given bundle resource. @@ -80,47 +80,35 @@ interface KernelInterface extends HttpKernelInterface * where BundleName is the name of the bundle * and the remaining part is the relative path in the bundle. * - * @return string - * * @throws \InvalidArgumentException if the file cannot be found or the name is not valid * @throws \RuntimeException if the name contains invalid/unsafe characters */ - public function locateResource(string $name); + public function locateResource(string $name): string; /** * Gets the environment. - * - * @return string */ - public function getEnvironment(); + public function getEnvironment(): string; /** * Checks if debug mode is enabled. - * - * @return bool */ - public function isDebug(); + public function isDebug(): bool; /** * Gets the project dir (path of the project's composer file). - * - * @return string */ - public function getProjectDir(); + public function getProjectDir(): string; /** * Gets the current container. - * - * @return ContainerInterface */ - public function getContainer(); + public function getContainer(): ContainerInterface; /** * Gets the request start time (not available if debug is disabled). - * - * @return float */ - public function getStartTime(); + public function getStartTime(): float; /** * Gets the cache directory. @@ -128,22 +116,24 @@ interface KernelInterface extends HttpKernelInterface * Since Symfony 5.2, the cache directory should be used for caches that are written at runtime. * For caches and artifacts that can be warmed at compile-time and deployed as read-only, * use the new "build directory" returned by the {@see getBuildDir()} method. - * - * @return string */ - public function getCacheDir(); + public function getCacheDir(): string; + + /** + * Returns the build directory. + * + * This directory should be used to store build artifacts, and can be read-only at runtime. + * Caches written at runtime should be stored in the "cache directory" ({@see KernelInterface::getCacheDir()}). + */ + public function getBuildDir(): string; /** * Gets the log directory. - * - * @return string */ - public function getLogDir(); + public function getLogDir(): string; /** * Gets the charset of the application. - * - * @return string */ - public function getCharset(); + public function getCharset(): string; } diff --git a/lib/symfony/http-kernel/LICENSE b/lib/symfony/http-kernel/LICENSE index 008370457..0138f8f07 100644 --- a/lib/symfony/http-kernel/LICENSE +++ b/lib/symfony/http-kernel/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2023 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/http-kernel/Log/DebugLoggerConfigurator.php b/lib/symfony/http-kernel/Log/DebugLoggerConfigurator.php new file mode 100644 index 000000000..537c10040 --- /dev/null +++ b/lib/symfony/http-kernel/Log/DebugLoggerConfigurator.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +use Monolog\Logger; + +/** + * @author Nicolas Grekas + */ +class DebugLoggerConfigurator +{ + private ?object $processor = null; + + public function __construct(callable $processor, bool $enable = null) + { + if ($enable ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + $this->processor = \is_object($processor) ? $processor : $processor(...); + } + } + + public function pushDebugLogger(Logger $logger): void + { + if ($this->processor) { + $logger->pushProcessor($this->processor); + } + } + + public static function getDebugLogger(mixed $logger): ?DebugLoggerInterface + { + if ($logger instanceof DebugLoggerInterface) { + return $logger; + } + + if (!$logger instanceof Logger) { + return null; + } + + foreach ($logger->getProcessors() as $processor) { + if ($processor instanceof DebugLoggerInterface) { + return $processor; + } + } + + return null; + } +} diff --git a/lib/symfony/http-kernel/Log/DebugLoggerInterface.php b/lib/symfony/http-kernel/Log/DebugLoggerInterface.php index 19ff0db18..1940c80a9 100644 --- a/lib/symfony/http-kernel/Log/DebugLoggerInterface.php +++ b/lib/symfony/http-kernel/Log/DebugLoggerInterface.php @@ -23,11 +23,15 @@ interface DebugLoggerInterface /** * Returns an array of logs. * - * A log is an array with the following mandatory keys: - * timestamp, message, priority, and priorityName. - * It can also have an optional context key containing an array. - * - * @return array + * @return array, + * message: string, + * priority: int, + * priorityName: string, + * timestamp: int, + * timestamp_rfc3339: string, + * }> */ public function getLogs(Request $request = null); @@ -40,6 +44,8 @@ interface DebugLoggerInterface /** * Removes all log records. + * + * @return void */ public function clear(); } diff --git a/lib/symfony/http-kernel/Log/Logger.php b/lib/symfony/http-kernel/Log/Logger.php index c2a45bb95..11d35b7e2 100644 --- a/lib/symfony/http-kernel/Log/Logger.php +++ b/lib/symfony/http-kernel/Log/Logger.php @@ -14,13 +14,15 @@ namespace Symfony\Component\HttpKernel\Log; use Psr\Log\AbstractLogger; use Psr\Log\InvalidArgumentException; use Psr\Log\LogLevel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; /** * Minimalist PSR-3 logger designed to write in stderr or any other stream. * * @author Kévin Dunglas */ -class Logger extends AbstractLogger +class Logger extends AbstractLogger implements DebugLoggerInterface { private const LEVELS = [ LogLevel::DEBUG => 0, @@ -32,9 +34,22 @@ class Logger extends AbstractLogger LogLevel::ALERT => 6, LogLevel::EMERGENCY => 7, ]; + private const PRIORITIES = [ + LogLevel::DEBUG => 100, + LogLevel::INFO => 200, + LogLevel::NOTICE => 250, + LogLevel::WARNING => 300, + LogLevel::ERROR => 400, + LogLevel::CRITICAL => 500, + LogLevel::ALERT => 550, + LogLevel::EMERGENCY => 600, + ]; - private $minLevelIndex; - private $formatter; + private int $minLevelIndex; + private \Closure $formatter; + private bool $debug = false; + private array $logs = []; + private array $errorCount = []; /** @var resource|null */ private $handle; @@ -42,22 +57,19 @@ class Logger extends AbstractLogger /** * @param string|resource|null $output */ - public function __construct(string $minLevel = null, $output = null, callable $formatter = null) + public function __construct(string $minLevel = null, $output = null, callable $formatter = null, private readonly ?RequestStack $requestStack = null, bool $debug = false) { if (null === $minLevel) { $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING; if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { - switch ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) { - case -1: $minLevel = LogLevel::ERROR; - break; - case 1: $minLevel = LogLevel::NOTICE; - break; - case 2: $minLevel = LogLevel::INFO; - break; - case 3: $minLevel = LogLevel::DEBUG; - break; - } + $minLevel = match ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) { + -1 => LogLevel::ERROR, + 1 => LogLevel::NOTICE, + 2 => LogLevel::INFO, + 3 => LogLevel::DEBUG, + default => $minLevel, + }; } } @@ -66,18 +78,19 @@ class Logger extends AbstractLogger } $this->minLevelIndex = self::LEVELS[$minLevel]; - $this->formatter = $formatter ?: [$this, 'format']; + $this->formatter = null !== $formatter ? $formatter(...) : $this->format(...); if ($output && false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) { throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output)); } + $this->debug = $debug; } - /** - * {@inheritdoc} - * - * @return void - */ - public function log($level, $message, array $context = []) + public function enableDebug(): void + { + $this->debug = true; + } + + public function log($level, $message, array $context = []): void { if (!isset(self::LEVELS[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); @@ -93,6 +106,34 @@ class Logger extends AbstractLogger } else { error_log($formatter($level, $message, $context, false)); } + + if ($this->debug && $this->requestStack) { + $this->record($level, $message, $context); + } + } + + public function getLogs(Request $request = null): array + { + if ($request) { + return $this->logs[spl_object_id($request)] ?? []; + } + + return array_merge(...array_values($this->logs)); + } + + public function countErrors(Request $request = null): int + { + if ($request) { + return $this->errorCount[spl_object_id($request)] ?? 0; + } + + return array_sum($this->errorCount); + } + + public function clear(): void + { + $this->logs = []; + $this->errorCount = []; } private function format(string $level, string $message, array $context, bool $prefixDate = true): string @@ -100,12 +141,12 @@ class Logger extends AbstractLogger if (str_contains($message, '{')) { $replacements = []; foreach ($context as $key => $val) { - if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + if (null === $val || \is_scalar($val) || $val instanceof \Stringable) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof \DateTimeInterface) { - $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); + $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339); } elseif (\is_object($val)) { - $replacements["{{$key}}"] = '[object '.\get_class($val).']'; + $replacements["{{$key}}"] = '[object '.$val::class.']'; } else { $replacements["{{$key}}"] = '['.\gettype($val).']'; } @@ -116,9 +157,34 @@ class Logger extends AbstractLogger $log = sprintf('[%s] %s', $level, $message); if ($prefixDate) { - $log = date(\DateTime::RFC3339).' '.$log; + $log = date(\DateTimeInterface::RFC3339).' '.$log; } return $log; } + + private function record($level, $message, array $context): void + { + $request = $this->requestStack->getCurrentRequest(); + $key = $request ? spl_object_id($request) : ''; + + $this->logs[$key][] = [ + 'channel' => null, + 'context' => $context, + 'message' => $message, + 'priority' => self::PRIORITIES[$level], + 'priorityName' => $level, + 'timestamp' => time(), + 'timestamp_rfc3339' => date(\DATE_RFC3339_EXTENDED), + ]; + + $this->errorCount[$key] ??= 0; + switch ($level) { + case LogLevel::ERROR: + case LogLevel::CRITICAL: + case LogLevel::ALERT: + case LogLevel::EMERGENCY: + ++$this->errorCount[$key]; + } + } } diff --git a/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php b/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php index 0b5a780ab..df61f716f 100644 --- a/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php +++ b/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -20,10 +20,8 @@ class FileProfilerStorage implements ProfilerStorageInterface { /** * Folder where profiler data are stored. - * - * @var string */ - private $folder; + private string $folder; /** * Constructs the file storage using a "dsn-like" path. @@ -45,10 +43,11 @@ class FileProfilerStorage implements ProfilerStorageInterface } /** - * {@inheritdoc} + * @param \Closure|null $filter A filter to apply on the list of tokens */ - public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null): array + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null/* , \Closure $filter = null */): array { + $filter = 7 < \func_num_args() ? func_get_arg(7) : null; $file = $this->getIndexFilename(); if (!file_exists($file)) { @@ -61,10 +60,21 @@ class FileProfilerStorage implements ProfilerStorageInterface $result = []; while (\count($result) < $limit && $line = $this->readLineFromFile($file)) { $values = str_getcsv($line); - [$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values; + + if (7 > \count($values)) { + // skip invalid lines + continue; + } + + [$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode, $csvVirtualType] = $values + [7 => null]; $csvTime = (int) $csvTime; - if ($ip && !str_contains($csvIp, $ip) || $url && !str_contains($csvUrl, $url) || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) { + $urlFilter = false; + if ($url) { + $urlFilter = str_starts_with($url, '!') ? str_contains($csvUrl, substr($url, 1)) : !str_contains($csvUrl, $url); + } + + if ($ip && !str_contains($csvIp, $ip) || $urlFilter || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) { continue; } @@ -76,7 +86,7 @@ class FileProfilerStorage implements ProfilerStorageInterface continue; } - $result[$csvToken] = [ + $profile = [ 'token' => $csvToken, 'ip' => $csvIp, 'method' => $csvMethod, @@ -84,7 +94,14 @@ class FileProfilerStorage implements ProfilerStorageInterface 'time' => $csvTime, 'parent' => $csvParent, 'status_code' => $csvStatusCode, + 'virtual_type' => $csvVirtualType ?: 'request', ]; + + if ($filter && !$filter($profile)) { + continue; + } + + $result[$csvToken] = $profile; } fclose($file); @@ -93,7 +110,7 @@ class FileProfilerStorage implements ProfilerStorageInterface } /** - * {@inheritdoc} + * @return void */ public function purge() { @@ -110,17 +127,12 @@ class FileProfilerStorage implements ProfilerStorageInterface } } - /** - * {@inheritdoc} - */ public function read(string $token): ?Profile { return $this->doRead($token); } /** - * {@inheritdoc} - * * @throws \RuntimeException */ public function write(Profile $profile): bool @@ -140,9 +152,7 @@ class FileProfilerStorage implements ProfilerStorageInterface // when there are errors in sub-requests, the parent and/or children tokens // may equal the profile token, resulting in infinite loops $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; - $childrenToken = array_filter(array_map(function (Profile $p) use ($profileToken) { - return $profileToken !== $p->getToken() ? $p->getToken() : null; - }, $profile->getChildren())); + $childrenToken = array_filter(array_map(fn (Profile $p) => $profileToken !== $p->getToken() ? $p->getToken() : null, $profile->getChildren())); // Store profile $data = [ @@ -155,6 +165,7 @@ class FileProfilerStorage implements ProfilerStorageInterface 'url' => $profile->getUrl(), 'time' => $profile->getTime(), 'status_code' => $profile->getStatusCode(), + 'virtual_type' => $profile->getVirtualType() ?? 'request', ]; $data = serialize($data); @@ -178,11 +189,16 @@ class FileProfilerStorage implements ProfilerStorageInterface $profile->getIp(), $profile->getMethod(), $profile->getUrl(), - $profile->getTime(), + $profile->getTime() ?: time(), $profile->getParentToken(), $profile->getStatusCode(), + $profile->getVirtualType() ?? 'request', ]); fclose($file); + + if (1 === mt_rand(1, 10)) { + $this->removeExpiredProfiles(); + } } return true; @@ -190,10 +206,8 @@ class FileProfilerStorage implements ProfilerStorageInterface /** * Gets filename to store data, associated to the token. - * - * @return string */ - protected function getFilename(string $token) + protected function getFilename(string $token): string { // Uses 4 last characters, because first are mostly the same. $folderA = substr($token, -2, 2); @@ -204,10 +218,8 @@ class FileProfilerStorage implements ProfilerStorageInterface /** * Gets the index filename. - * - * @return string */ - protected function getIndexFilename() + protected function getIndexFilename(): string { return $this->folder.'/index.csv'; } @@ -218,10 +230,8 @@ class FileProfilerStorage implements ProfilerStorageInterface * This function automatically skips the empty lines and do not include the line return in result value. * * @param resource $file The file resource, with the pointer placed at the end of the line to read - * - * @return mixed */ - protected function readLineFromFile($file) + protected function readLineFromFile($file): mixed { $line = ''; $position = ftell($file); @@ -259,6 +269,9 @@ class FileProfilerStorage implements ProfilerStorageInterface return '' === $line ? null : $line; } + /** + * @return Profile + */ protected function createProfileFromData(string $token, array $data, Profile $parent = null) { $profile = new Profile($token); @@ -267,6 +280,7 @@ class FileProfilerStorage implements ProfilerStorageInterface $profile->setUrl($data['url']); $profile->setTime($data['time']); $profile->setStatusCode($data['status_code']); + $profile->setVirtualType($data['virtual_type'] ?: 'request'); $profile->setCollectors($data['data']); if (!$parent && $data['parent']) { @@ -308,4 +322,37 @@ class FileProfilerStorage implements ProfilerStorageInterface return $this->createProfileFromData($token, $data, $profile); } + + private function removeExpiredProfiles(): void + { + $minimalProfileTimestamp = time() - 2 * 86400; + $file = $this->getIndexFilename(); + $handle = fopen($file, 'r'); + + if ($offset = is_file($file.'.offset') ? (int) file_get_contents($file.'.offset') : 0) { + fseek($handle, $offset); + } + + while ($line = fgets($handle)) { + $values = str_getcsv($line); + + if (7 > \count($values)) { + // skip invalid lines + $offset += \strlen($line); + continue; + } + + [$csvToken, , , , $csvTime] = $values; + + if ($csvTime >= $minimalProfileTimestamp) { + break; + } + + @unlink($this->getFilename($csvToken)); + $offset += \strlen($line); + } + fclose($handle); + + file_put_contents($file.'.offset', $offset); + } } diff --git a/lib/symfony/http-kernel/Profiler/Profile.php b/lib/symfony/http-kernel/Profiler/Profile.php index a622403e1..08e7b65a2 100644 --- a/lib/symfony/http-kernel/Profiler/Profile.php +++ b/lib/symfony/http-kernel/Profiler/Profile.php @@ -20,34 +20,34 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; */ class Profile { - private $token; + private string $token; /** * @var DataCollectorInterface[] */ - private $collectors = []; + private array $collectors = []; - private $ip; - private $method; - private $url; - private $time; - private $statusCode; - - /** - * @var Profile - */ - private $parent; + private ?string $ip = null; + private ?string $method = null; + private ?string $url = null; + private ?int $time = null; + private ?int $statusCode = null; + private ?self $parent = null; + private ?string $virtualType = null; /** * @var Profile[] */ - private $children = []; + private array $children = []; public function __construct(string $token) { $this->token = $token; } + /** + * @return void + */ public function setToken(string $token) { $this->token = $token; @@ -55,16 +55,16 @@ class Profile /** * Gets the token. - * - * @return string */ - public function getToken() + public function getToken(): string { return $this->token; } /** * Sets the parent token. + * + * @return void */ public function setParent(self $parent) { @@ -73,34 +73,31 @@ class Profile /** * Returns the parent profile. - * - * @return self|null */ - public function getParent() + public function getParent(): ?self { return $this->parent; } /** * Returns the parent token. - * - * @return string|null */ - public function getParentToken() + public function getParentToken(): ?string { - return $this->parent ? $this->parent->getToken() : null; + return $this->parent?->getToken(); } /** * Returns the IP. - * - * @return string|null */ - public function getIp() + public function getIp(): ?string { return $this->ip; } + /** + * @return void + */ public function setIp(?string $ip) { $this->ip = $ip; @@ -108,14 +105,15 @@ class Profile /** * Returns the request method. - * - * @return string|null */ - public function getMethod() + public function getMethod(): ?string { return $this->method; } + /** + * @return void + */ public function setMethod(string $method) { $this->method = $method; @@ -123,51 +121,68 @@ class Profile /** * Returns the URL. - * - * @return string|null */ - public function getUrl() + public function getUrl(): ?string { return $this->url; } + /** + * @return void + */ public function setUrl(?string $url) { $this->url = $url; } - /** - * @return int - */ - public function getTime() + public function getTime(): int { return $this->time ?? 0; } + /** + * @return void + */ public function setTime(int $time) { $this->time = $time; } + /** + * @return void + */ public function setStatusCode(int $statusCode) { $this->statusCode = $statusCode; } - /** - * @return int|null - */ - public function getStatusCode() + public function getStatusCode(): ?int { return $this->statusCode; } + /** + * @internal + */ + public function setVirtualType(?string $virtualType): void + { + $this->virtualType = $virtualType; + } + + /** + * @internal + */ + public function getVirtualType(): ?string + { + return $this->virtualType; + } + /** * Finds children profilers. * * @return self[] */ - public function getChildren() + public function getChildren(): array { return $this->children; } @@ -176,6 +191,8 @@ class Profile * Sets children profiler. * * @param Profile[] $children + * + * @return void */ public function setChildren(array $children) { @@ -187,6 +204,8 @@ class Profile /** * Adds the child token. + * + * @return void */ public function addChild(self $child) { @@ -208,11 +227,9 @@ class Profile /** * Gets a Collector by name. * - * @return DataCollectorInterface - * * @throws \InvalidArgumentException if the collector does not exist */ - public function getCollector(string $name) + public function getCollector(string $name): DataCollectorInterface { if (!isset($this->collectors[$name])) { throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); @@ -226,7 +243,7 @@ class Profile * * @return DataCollectorInterface[] */ - public function getCollectors() + public function getCollectors(): array { return $this->collectors; } @@ -235,6 +252,8 @@ class Profile * Sets the Collectors associated with this profile. * * @param DataCollectorInterface[] $collectors + * + * @return void */ public function setCollectors(array $collectors) { @@ -246,25 +265,21 @@ class Profile /** * Adds a Collector. + * + * @return void */ public function addCollector(DataCollectorInterface $collector) { $this->collectors[$collector->getName()] = $collector; } - /** - * @return bool - */ - public function hasCollector(string $name) + public function hasCollector(string $name): bool { return isset($this->collectors[$name]); } - /** - * @return array - */ - public function __sleep() + public function __sleep(): array { - return ['token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode']; + return ['token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode', 'virtualType']; } } diff --git a/lib/symfony/http-kernel/Profiler/Profiler.php b/lib/symfony/http-kernel/Profiler/Profiler.php index 25e126f73..04ab0670d 100644 --- a/lib/symfony/http-kernel/Profiler/Profiler.php +++ b/lib/symfony/http-kernel/Profiler/Profiler.php @@ -26,16 +26,16 @@ use Symfony\Contracts\Service\ResetInterface; */ class Profiler implements ResetInterface { - private $storage; + private ProfilerStorageInterface $storage; /** * @var DataCollectorInterface[] */ - private $collectors = []; + private array $collectors = []; - private $logger; - private $initiallyEnabled = true; - private $enabled = true; + private ?LoggerInterface $logger; + private bool $initiallyEnabled = true; + private bool $enabled = true; public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null, bool $enable = true) { @@ -46,6 +46,8 @@ class Profiler implements ResetInterface /** * Disables the profiler. + * + * @return void */ public function disable() { @@ -54,18 +56,23 @@ class Profiler implements ResetInterface /** * Enables the profiler. + * + * @return void */ public function enable() { $this->enabled = true; } + public function isEnabled(): bool + { + return $this->enabled; + } + /** * Loads the Profile for the given Response. - * - * @return Profile|null */ - public function loadProfileFromResponse(Response $response) + public function loadProfileFromResponse(Response $response): ?Profile { if (!$token = $response->headers->get('X-Debug-Token')) { return null; @@ -76,20 +83,16 @@ class Profiler implements ResetInterface /** * Loads the Profile for the given token. - * - * @return Profile|null */ - public function loadProfile(string $token) + public function loadProfile(string $token): ?Profile { return $this->storage->read($token); } /** * Saves a Profile. - * - * @return bool */ - public function saveProfile(Profile $profile) + public function saveProfile(Profile $profile): bool { // late collect foreach ($profile->getCollectors() as $collector) { @@ -99,7 +102,7 @@ class Profiler implements ResetInterface } if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { - $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => \get_class($this->storage)]); + $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => $this->storage::class]); } return $ret; @@ -107,6 +110,8 @@ class Profiler implements ResetInterface /** * Purges all data from the storage. + * + * @return void */ public function purge() { @@ -116,25 +121,24 @@ class Profiler implements ResetInterface /** * Finds profiler tokens for the given criteria. * - * @param string|null $limit The maximum number of tokens to return - * @param string|null $start The start date to search from - * @param string|null $end The end date to search to - * - * @return array + * @param int|null $limit The maximum number of tokens to return + * @param string|null $start The start date to search from + * @param string|null $end The end date to search to + * @param \Closure|null $filter A filter to apply on the list of tokens * * @see https://php.net/datetime.formats for the supported date/time formats */ - public function find(?string $ip, ?string $url, ?string $limit, ?string $method, ?string $start, ?string $end, string $statusCode = null) + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?string $start, ?string $end, string $statusCode = null/* , \Closure $filter = null */): array { - return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode); + $filter = 7 < \func_num_args() ? func_get_arg(7) : null; + + return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode, $filter); } /** * Collects data for the given Response. - * - * @return Profile|null */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): ?Profile { if (false === $this->enabled) { return null; @@ -147,10 +151,14 @@ class Profiler implements ResetInterface $profile->setStatusCode($response->getStatusCode()); try { $profile->setIp($request->getClientIp()); - } catch (ConflictingHeadersException $e) { + } catch (ConflictingHeadersException) { $profile->setIp('Unknown'); } + if ($request->attributes->has('_virtual_type')) { + $profile->setVirtualType($request->attributes->get('_virtual_type')); + } + if ($prevToken = $response->headers->get('X-Debug-Token')) { $response->headers->set('X-Previous-Debug-Token', $prevToken); } @@ -167,6 +175,9 @@ class Profiler implements ResetInterface return $profile; } + /** + * @return void + */ public function reset() { foreach ($this->collectors as $collector) { @@ -177,10 +188,8 @@ class Profiler implements ResetInterface /** * Gets the Collectors associated with this profiler. - * - * @return array */ - public function all() + public function all(): array { return $this->collectors; } @@ -189,6 +198,8 @@ class Profiler implements ResetInterface * Sets the Collectors associated with this profiler. * * @param DataCollectorInterface[] $collectors An array of collectors + * + * @return void */ public function set(array $collectors = []) { @@ -200,6 +211,8 @@ class Profiler implements ResetInterface /** * Adds a Collector. + * + * @return void */ public function add(DataCollectorInterface $collector) { @@ -210,10 +223,8 @@ class Profiler implements ResetInterface * Returns true if a Collector for the given name exists. * * @param string $name A collector name - * - * @return bool */ - public function has(string $name) + public function has(string $name): bool { return isset($this->collectors[$name]); } @@ -223,11 +234,9 @@ class Profiler implements ResetInterface * * @param string $name A collector name * - * @return DataCollectorInterface - * * @throws \InvalidArgumentException if the collector does not exist */ - public function get(string $name) + public function get(string $name): DataCollectorInterface { if (!isset($this->collectors[$name])) { throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); @@ -243,8 +252,8 @@ class Profiler implements ResetInterface } try { - $value = new \DateTime(is_numeric($value) ? '@'.$value : $value); - } catch (\Exception $e) { + $value = new \DateTimeImmutable(is_numeric($value) ? '@'.$value : $value); + } catch (\Exception) { return null; } diff --git a/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php b/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php index 95d72f46b..14b8993b6 100644 --- a/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php +++ b/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php @@ -29,11 +29,13 @@ interface ProfilerStorageInterface /** * Finds profiler tokens for the given criteria. * - * @param int|null $limit The maximum number of tokens to return - * @param int|null $start The start date to search from - * @param int|null $end The end date to search to + * @param int|null $limit The maximum number of tokens to return + * @param int|null $start The start date to search from + * @param int|null $end The end date to search to + * @param string|null $statusCode The response status code + * @param \Closure|null $filter A filter to apply on the list of tokens */ - public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null): array; + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null/* , string $statusCode = null, \Closure $filter = null */): array; /** * Reads data associated with the given token. @@ -49,6 +51,8 @@ interface ProfilerStorageInterface /** * Purges all data from the database. + * + * @return void */ public function purge(); } diff --git a/lib/symfony/http-kernel/README.md b/lib/symfony/http-kernel/README.md index ca5041782..18d15f5ad 100644 --- a/lib/symfony/http-kernel/README.md +++ b/lib/symfony/http-kernel/README.md @@ -5,18 +5,6 @@ The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component. It's flexible enough to create full-stack frameworks, micro-frameworks or advanced CMS systems like Drupal. -Sponsor -------- - -The HttpKernel component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2]. - -Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and -fix your projects. We provide a wide range of professional services including development, -consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps. -We are a worker cooperative! - -Help Symfony by [sponsoring][3] its development! - Resources --------- @@ -25,7 +13,3 @@ Resources * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) - -[1]: https://symfony.com/backers -[2]: https://les-tilleuls.coop -[3]: https://symfony.com/sponsor diff --git a/lib/symfony/http-kernel/RebootableInterface.php b/lib/symfony/http-kernel/RebootableInterface.php index e257237da..e973f5540 100644 --- a/lib/symfony/http-kernel/RebootableInterface.php +++ b/lib/symfony/http-kernel/RebootableInterface.php @@ -25,6 +25,8 @@ interface RebootableInterface * while building the container. Use the %kernel.build_dir% parameter instead. * * @param string|null $warmupDir pass null to reboot in the regular build directory + * + * @return void */ public function reboot(?string $warmupDir); } diff --git a/lib/symfony/http-kernel/Resources/welcome.html.php b/lib/symfony/http-kernel/Resources/welcome.html.php index b25f99b3d..d36b97527 100644 --- a/lib/symfony/http-kernel/Resources/welcome.html.php +++ b/lib/symfony/http-kernel/Resources/welcome.html.php @@ -1,13 +1,14 @@ - - + + Welcome to Symfony! + +{% endblock %} + {% block toolbar %} {% if 'unknown' == collector.symfonyState %} {% set block_status = '' %} @@ -20,7 +66,7 @@ {% set icon %} - {{ include('@WebProfiler/Icon/symfony.svg') }} + {{ source('@WebProfiler/Icon/symfony.svg') }} {{ collector.symfonyState is defined ? collector.symfonyversion : 'n/a' }} {% endset %} @@ -64,7 +110,13 @@
    PHP Extensions - xdebug {{ collector.hasxdebug ? '✓' : '✗' }} + {% if collector.hasXdebugInfo %} + + {% endif %} + Xdebug {{ collector.hasXdebug ? '✓' : '✗' }} + {% if collector.hasXdebugInfo %} + + {% endif %} APCu {{ collector.hasapcu ? '✓' : '✗' }} OPcache {{ collector.haszendopcache ? '✓' : '✗' }}
    @@ -102,7 +154,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/config.svg') }} + {{ source('@WebProfiler/Icon/config.svg') }} Configuration {% endblock %} @@ -112,7 +164,13 @@
    - {{ collector.symfonyversion }} + + {{ collector.symfonyversion }} + + {% if collector.symfonylts %} + (LTS) + {% endif %} + Symfony version
    @@ -131,33 +189,39 @@ {% endif %}
    - {% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %} + {% set symfony_status = { dev: 'In Development', stable: 'Maintained', eom: 'Security Fixes Only', eol: 'Unmaintained' } %} {% set symfony_status_class = { dev: 'warning', stable: 'success', eom: 'warning', eol: 'error' } %} - - - - - - - - - - - - - - - - - -
    Symfony StatusBugs {{ collector.symfonystate in ['eom', 'eol'] ? 'were' : 'are' }} fixed untilSecurity issues {{ collector.symfonystate == 'eol' ? 'were' : 'are' }} fixed until
    - {{ symfony_status[collector.symfonystate]|upper }} - {% if collector.symfonylts %} -   Long-Term Support - {% endif %} - {{ collector.symfonyeom }}{{ collector.symfonyeol }} - View roadmap -
    + +
    +
    +
    + + {{ symfony_status[collector.symfonystate]|upper }} + + Your Symfony version status +
    + + {% if collector.symfonylts %} +
    + + {{ collector.symfonyeom }} + + Bug fixes {{ collector.symfonystate in ['eom', 'eol'] ? 'ended on' : 'until' }} +
    + {% endif %} + +
    + + {{ collector.symfonyeol }} + + + {{ collector.symfonylts ? 'Security fixes' : 'Bug fixes and security fixes' }} + {{ 'eol' == collector.symfonystate ? 'ended on' : 'until' }} +
    +
    +
    + + View Symfony {{ collector.symfonyversion }} release details

    PHP Configuration

    @@ -184,19 +248,21 @@
    -
    - {{ include('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }} - OPcache -
    +
    +
    + {{ source('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }} + OPcache +
    -
    - {{ include('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no-gray') ~ '.svg') }} - APCu -
    +
    + {{ source('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no') ~ '.svg') }} + APCu +
    -
    - {{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.svg') }} - Xdebug +
    + {{ source('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no') ~ '.svg') }} + Xdebug +
    diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig index c0be48a37..1e53aaba4 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig @@ -1,85 +1,92 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block menu %} - {{ include('@WebProfiler/Icon/event.svg') }} + {{ source('@WebProfiler/Icon/event.svg') }} Events {% endblock %} {% block panel %} -

    Event Dispatcher

    +

    Dispatched Events

    - {% if collector.calledlisteners is empty %} -
    -

    No events have been recorded. Check that debugging is enabled in the kernel.

    -
    - {% else %} -
    +
    + {% for dispatcherName, dispatcherData in collector.data %}
    -

    Called Listeners {{ collector.calledlisteners|length }}

    - +

    {{ dispatcherName }}

    - {{ helper.render_table(collector.calledlisteners) }} -
    -
    - -
    -

    Not Called Listeners {{ collector.notcalledlisteners|length }}

    -
    - {% if collector.notcalledlisteners is empty %} -
    -

    - There are no uncalled listeners. -

    -

    - All listeners were called for this request or an error occurred - when trying to collect uncalled listeners (in which case check the - logs to get more information). -

    + {% if dispatcherData['called_listeners'] is empty %} +
    +

    No events have been recorded. Check that debugging is enabled in the kernel.

    {% else %} - {{ helper.render_table(collector.notcalledlisteners) }} +
    +
    +

    Called Listeners {{ dispatcherData['called_listeners']|length }}

    + +
    + {{ _self.render_table(dispatcherData['called_listeners']) }} +
    +
    + +
    +

    Not Called Listeners {{ dispatcherData['not_called_listeners']|length }}

    +
    + {% if dispatcherData['not_called_listeners'] is empty %} +
    +

    + There are no uncalled listeners. +

    +

    + All listeners were called or an error occurred + when trying to collect uncalled listeners (in which case check the + logs to get more information). +

    +
    + {% else %} + {{ _self.render_table(dispatcherData['not_called_listeners']) }} + {% endif %} +
    +
    + +
    +

    Orphaned Events {{ dispatcherData['orphaned_events']|length }}

    +
    + {% if dispatcherData['orphaned_events'] is empty %} +
    +

    + There are no orphaned events. +

    +

    + All dispatched events were handled or an error occurred + when trying to collect orphaned events (in which case check the + logs to get more information). +

    +
    + {% else %} + + + + + + + + {% for event in dispatcherData['orphaned_events'] %} + + + + {% endfor %} + +
    Event
    {{ event }}
    + {% endif %} +
    +
    +
    {% endif %}
    - -
    -

    Orphaned Events {{ collector.orphanedEvents|length }}

    -
    - {% if collector.orphanedEvents is empty %} -
    -

    - There are no orphaned events. -

    -

    - All dispatched events were handled or an error occurred - when trying to collect orphaned events (in which case check the - logs to get more information). -

    -
    - {% else %} - - - - - - - - {% for event in collector.orphanedEvents %} - - - - {% endfor %} - -
    Event
    {{ event }}
    - {% endif %} -
    -
    -
    - {% endif %} + {% endfor %} +
    {% endblock %} {% macro render_table(listeners) %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig index 1fe0f5d47..e60d83f32 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig @@ -12,7 +12,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/exception.svg') }} + {{ source('@WebProfiler/Icon/exception.svg') }} Exception {% if collector.hasexception %} @@ -23,11 +23,19 @@ {% endblock %} {% block panel %} + {# these styles are needed to override some styles from Exception page, which wasn't + updated yet to the new style of the Symfony Profiler #} + +

    Exceptions

    {% if not collector.hasexception %} -
    -

    No exception was thrown and caught during the request.

    +
    +

    No exception was thrown and caught.

    {% else %}
    diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig index d99ad4f77..727717fbb 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig @@ -1,12 +1,10 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% from _self import form_tree_entry, form_tree_details %} - {% block toolbar %} {% if collector.data.nb_errors > 0 or collector.data.forms|length %} {% set status_color = collector.data.nb_errors ? 'red' %} {% set icon %} - {{ include('@WebProfiler/Icon/form.svg') }} + {{ source('@WebProfiler/Icon/form.svg') }} {{ collector.data.nb_errors ?: collector.data.forms|length }} @@ -29,7 +27,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/form.svg') }} + {{ source('@WebProfiler/Icon/form.svg') }} Forms {% if collector.data.nb_errors > 0 %} @@ -39,14 +37,35 @@ {% endblock %} -{% block head %} +{% block stylesheets %} {{ parent() }} {% endblock %} +{% block javascripts %} + {{ parent() }} + + +{% endblock %} + {% block panel %}

    Forms

    @@ -211,243 +382,24 @@
      {% for formName, formData in collector.data.forms %} - {{ form_tree_entry(formName, formData, true) }} + {{ _self.form_tree_entry(formName, formData, true) }} {% endfor %}
    {% for formName, formData in collector.data.forms %} - {{ form_tree_details(formName, formData, collector.data.forms_by_hash, loop.first) }} + {{ _self.form_tree_details(formName, formData, collector.data.forms_by_hash, loop.first) }} {% endfor %}
    {% else %} -
    -

    No forms were submitted for this request.

    +
    +

    No forms were submitted.

    {% endif %} - - {% endblock %} {% macro form_tree_entry(name, data, is_root) %} - {% import _self as tree %} {% set has_error = data.errors is defined and data.errors|length > 0 %}
  • @@ -456,7 +408,9 @@ {% endif %} {% if data.children is not empty %} - + {% else %}
    {% endif %} @@ -469,7 +423,7 @@ {% if data.children is not empty %}
      {% for childName, childData in data.children %} - {{ tree.form_tree_entry(childName, childData, false) }} + {{ _self.form_tree_entry(childName, childData, false) }} {% endfor %}
    {% endif %} @@ -477,174 +431,216 @@ {% endmacro %} {% macro form_tree_details(name, data, forms_by_hash, show) %} - {% import _self as tree %}

    {{ name|default('(no name)') }}

    {% if data.type_class is defined %} -

    {{ profiler_dump(data.type_class) }}

    +
    + Form type: + {{ profiler_dump(data.type_class) }} +
    {% endif %} - {% if data.errors is defined and data.errors|length > 0 %} -
    -

    - - Errors - -

    + {% set form_has_errors = data.errors ?? [] is not empty %} +
    +
    +

    Errors

    +
    + {{ _self.render_form_errors(data) }} +
    +
    + +
    +

    Default Data

    + +
    + {{ _self.render_form_default_data(data) }} +
    +
    + +
    +

    Submitted Data

    + +
    + {{ _self.render_form_submitted_data(data) }} +
    +
    + +
    +

    Passed Options

    + +
    + {{ _self.render_form_passed_options(data) }} +
    +
    + +
    +

    Resolved Options

    + +
    + {{ _self.render_form_resolved_options(data) }} +
    +
    + +
    +

    View Vars

    + +
    + {{ _self.render_form_view_variables(data) }} +
    +
    +
    +
    + + {% for childName, childData in data.children %} + {{ _self.form_tree_details(childName, childData, forms_by_hash) }} + {% endfor %} +{% endmacro %} + +{% macro render_form_errors(data) %} + {% if data.errors is defined and data.errors|length > 0 %} +
    - - - - - + + + + + {% for error in data.errors %} - - - - - + + + + + {% endfor %}
    MessageOriginCause
    MessageOriginCause
    {{ error.message }} - {% if error.origin is empty %} - This form. - {% elseif forms_by_hash[error.origin] is not defined %} - Unknown. - {% else %} - {{ forms_by_hash[error.origin].name }} - {% endif %} - - {% if error.trace %} - Caused by: - {% for stacked in error.trace %} - {{ profiler_dump(stacked) }} - {% endfor %} - {% else %} - Unknown. - {% endif %} -
    {{ error.message }} + {% if error.origin is empty %} + This form. + {% elseif forms_by_hash[error.origin] is not defined %} + Unknown. + {% else %} + {{ forms_by_hash[error.origin].name }} + {% endif %} + + {% if error.trace %} + Caused by: + {% for stacked in error.trace %} + {{ profiler_dump(stacked) }} + {% endfor %} + {% else %} + Unknown. + {% endif %} +
    - {% endif %} - - {% if data.default_data is defined %} -

    - - Default Data - -

    - -
    - - - - - - - - - - - - - - - - - - - - - -
    PropertyValue
    Model Format - {% if data.default_data.model is defined %} - {{ profiler_dump(data.default_data.seek('model')) }} - {% else %} - same as normalized format - {% endif %} -
    Normalized Format{{ profiler_dump(data.default_data.seek('norm')) }}
    View Format - {% if data.default_data.view is defined %} - {{ profiler_dump(data.default_data.seek('view')) }} - {% else %} - same as normalized format - {% endif %} -
    + {% else %} +
    +

    This form has no errors.

    - {% endif %} + {% endif %} +{% endmacro %} - {% if data.submitted_data is defined %} -

    - - Submitted Data - -

    - -
    - {% if data.submitted_data.norm is defined %} - - - - - - - - - - - - - - - - - - - - - -
    PropertyValue
    View Format - {% if data.submitted_data.view is defined %} - {{ profiler_dump(data.submitted_data.seek('view')) }} - {% else %} - same as normalized format - {% endif %} -
    Normalized Format{{ profiler_dump(data.submitted_data.seek('norm')) }}
    Model Format - {% if data.submitted_data.model is defined %} - {{ profiler_dump(data.submitted_data.seek('model')) }} - {% else %} - same as normalized format - {% endif %} -
    - {% else %} -
    -

    This form was not submitted.

    -
    - {% endif %} +{% macro render_form_default_data(data) %} + {% if data.default_data is defined %} + + + + + + + + + + + + + + + + + + + + + +
    PropertyValue
    Model Format + {% if data.default_data.model is defined %} + {{ profiler_dump(data.default_data.seek('model')) }} + {% else %} + same as normalized format + {% endif %} +
    Normalized Format{{ profiler_dump(data.default_data.seek('norm')) }}
    View Format + {% if data.default_data.view is defined %} + {{ profiler_dump(data.default_data.seek('view')) }} + {% else %} + same as normalized format + {% endif %} +
    + {% else %} +
    +

    This form has default data defined.

    - {% endif %} + {% endif %} +{% endmacro %} - {% if data.passed_options is defined %} -

    - - Passed Options - -

    +{% macro render_form_submitted_data(data) %} + {% if data.submitted_data.norm is defined %} + + + + + + + + + + + + + + + + + + + + + +
    PropertyValue
    View Format + {% if data.submitted_data.view is defined %} + {{ profiler_dump(data.submitted_data.seek('view')) }} + {% else %} + same as normalized format + {% endif %} +
    Normalized Format{{ profiler_dump(data.submitted_data.seek('norm')) }}
    Model Format + {% if data.submitted_data.model is defined %} + {{ profiler_dump(data.submitted_data.seek('model')) }} + {% else %} + same as normalized format + {% endif %} +
    + {% else %} +
    +

    This form was not submitted.

    +
    + {% endif %} +{% endmacro %} -
    - {% if data.passed_options|length %} - - - - - - - - - - {% for option, value in data.passed_options %} +{% macro render_form_passed_options(data) %} + {% if data.passed_options ?? [] is not empty %} +
    OptionPassed ValueResolved Value
    + + + + + + + + + {% for option, value in data.passed_options %} @@ -659,73 +655,50 @@ {% endif %} - {% endfor %} - -
    OptionPassed ValueResolved Value
    {{ option }} {{ profiler_dump(value) }}
    - {% else %} -
    -

    No options were passed when constructing this form.

    -
    - {% endif %} + {% endfor %} + + + {% else %} +
    +

    No options were passed when constructing this form.

    - {% endif %} - - {% if data.resolved_options is defined %} -

    - - Resolved Options - -

    - - - {% endif %} - - {% if data.view_vars is defined %} -

    - - View Variables - -

    - - - {% endif %} -
    - - {% for childName, childData in data.children %} - {{ tree.form_tree_details(childName, childData, forms_by_hash) }} - {% endfor %} + {% endif %} +{% endmacro %} + +{% macro render_form_resolved_options(data) %} + + + + + + + + + {% for option, value in data.resolved_options ?? [] %} + + + + + {% endfor %} + +
    OptionValue
    {{ option }}{{ profiler_dump(value) }}
    +{% endmacro %} + +{% macro render_form_view_variables(data) %} + + + + + + + + + {% for variable, value in data.view_vars ?? [] %} + + + + + {% endfor %} + +
    VariableValue
    {{ variable }}{{ profiler_dump(value) }}
    {% endmacro %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/http_client.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/http_client.html.twig index 8496ef186..f3ee1393f 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/http_client.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/http_client.html.twig @@ -1,9 +1,45 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + + {% block toolbar %} {% if collector.requestCount %} {% set icon %} - {{ include('@WebProfiler/Icon/http-client.svg') }} + {{ source('@WebProfiler/Icon/http-client.svg') }} {% set status_color = '' %} {{ collector.requestCount }} {% endset %} @@ -25,7 +61,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/http-client.svg') }} + {{ source('@WebProfiler/Icon/http-client.svg') }} HTTP Client {% if collector.requestCount %} @@ -38,7 +74,7 @@ {% block panel %}

    HTTP Client

    {% if collector.requestCount == 0 %} -
    +

    No HTTP requests were made.

    {% else %} @@ -77,28 +113,37 @@ {% endif %} {% endfor %} {% endif %} - +
    {% if profiler_token and profiler_link %} {% endif %} + {% if trace.curlCommand is defined and trace.curlCommand %} + + {% endif %} + {% if trace.options is not empty %} + + + + + {% endif %} - + {% if trace.http_code >= 500 %} {% set responseStatus = 'error' %} {% elseif trace.http_code >= 400 %} @@ -106,11 +151,10 @@ {% else %} {% set responseStatus = 'success' %} {% endif %} - + {{ trace.http_code }} - - {% if profiler_token and profiler_link %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig index b1642d4e1..8055016e0 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig @@ -1,12 +1,330 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} +{% block stylesheets %} + {{ parent() }} + + +{% endblock %} + +{% block javascripts %} + +{% endblock %} {% block toolbar %} {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} {% set icon %} {% set status_color = collector.counterrors ? 'red' : collector.countwarnings ? 'yellow' : 'none' %} - {{ include('@WebProfiler/Icon/logger.svg') }} + {{ source('@WebProfiler/Icon/logger.svg') }} {{ collector.counterrors ?: (collector.countdeprecations + collector.countwarnings) }} {% endset %} @@ -33,7 +351,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/logger.svg') }} + {{ source('@WebProfiler/Icon/logger.svg') }} Logs {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} @@ -47,7 +365,7 @@

    Log Messages

    {% if collector.processedLogs is empty %} -
    +

    No log messages available.

    {% else %} @@ -57,33 +375,30 @@ {% set filters = collector.filters %}
    -
      -
    • - - -
    • +
      + {% set initially_active_tab = has_error_logs ? 'error' : has_deprecation_logs ? 'deprecation' : 'all' %} + + -
    • - - -
    • + + -
    • - - -
    • -
    + + +
    - {{ include('@WebProfiler/Icon/filter.svg') }} + {{ source('@WebProfiler/Icon/filter.svg') }} Level ({{ filters.priority|length - 1 }}) @@ -104,7 +419,7 @@
    - {{ include('@WebProfiler/Icon/filter.svg') }} + {{ source('@WebProfiler/Icon/filter.svg') }} Channel ({{ filters.channel|length - 1 }}) @@ -126,13 +441,15 @@
    - {{ trace.method }} + {{ trace.method }} {{ trace.url }} - {% if trace.options is not empty %} - {{ profiler_dump(trace.options, maxDepth=1) }} - {% endif %} Profile + +
    Request options{{ profiler_dump(trace.options, maxDepth=1) }}
    + Response + {{ profiler_dump(trace.info, maxDepth=1) }}
    - + - - + + + + @@ -143,7 +460,7 @@ %} {% endfor %} @@ -173,19 +490,13 @@

    There are no log messages.

    - - {% endif %} - {% set compilerLogTotal = 0 %} - {% for logs in collector.compilerLogs %} - {% set compilerLogTotal = compilerLogTotal + logs|length %} - {% endfor %} - + {% set compilerLogTotal = collector.compilerLogs|reduce((total, logs) => total + logs|length, 0) %}

    Container Compilation Logs ({{ compilerLogTotal }})

    -

    Log messages generated during the compilation of the service container.

    + Log messages generated during the compilation of the service container.
    {% if collector.compilerLogs is empty %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/mailer.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/mailer.html.twig index dab2e9c6c..386438b82 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/mailer.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/mailer.html.twig @@ -1,11 +1,199 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block stylesheets %} + {{ parent() }} + + +{% endblock %} + +{% block javascripts %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set events = collector.events %} {% if events.messages|length %} {% set icon %} - {% include('@WebProfiler/Icon/mailer.svg') %} + {{ source('@WebProfiler/Icon/mailer.svg') }} {{ events.messages|length }} {% endset %} @@ -24,50 +212,13 @@ {% endif %} {% endblock %} -{% block head %} - {{ parent() }} - -{% endblock %} - {% block menu %} {% set events = collector.events %} - {{ include('@WebProfiler/Icon/mailer.svg') }} + {{ source('@WebProfiler/Icon/mailer.svg') }} - E-mails + Emails {% if events.messages|length > 0 %} {{ events.messages|length }} @@ -78,140 +229,225 @@ {% block panel %} {% set events = collector.events %} -

    Emails

    {% if not events.messages|length %} -
    +

    No emails were sent.

    + {% else %} +
    +
    +
    + {{ events.events|filter(e => e.isQueued())|length }} + Queued +
    + +
    + {{ events.events|filter(e => not e.isQueued())|length }} + Sent +
    +
    +
    {% endif %} -
    -
    - {{ events.events|filter(e => e.isQueued())|length }} - Queued + {% if events.transports|length > 1 %} + {% for transport in events.transports %} +

    {{ transport }} transport

    + {{ _self.render_transport_details(collector, transport) }} + {% endfor %} + {% elseif events.transports is not empty %} + {{ _self.render_transport_details(collector, events.transports|first, true) }} + {% endif %} + + {% macro render_transport_details(collector, transport, show_transport_name = false) %} +
    + {% set num_emails = collector.events.events(transport)|length %} + {% if num_emails > 1 %} +
    +
    TimeMessage
    TimeMessage
    - - {{ helper.render_log_message('debug', loop.index, log) }} + {{ _self.render_log_message('debug', loop.index, log) }}
    + + + + + + + + + + {% for event in collector.events.events(transport) %} + + + + + + + {% endfor %} + +
    #SubjectToActions
    {{ loop.index }}{{ event.message.getSubject() ?? '(No subject)' }}{{ event.message.getTo()|map(addr => addr.toString())|join(', ')|default('(empty)') }}
    +
    + + {% for event in collector.events.events(transport) %} +
    + {{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }} +
    + {% endfor %} + {% else %} + {% set event = (collector.events.events(transport)|first) %} + {{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }} + {% endif %}
    + {% endmacro %} -
    - {{ events.events|filter(e => not e.isQueued())|length }} - Sent -
    -
    + {% macro render_email_details(collector, transport, message, message_is_queued, show_transport_name = false) %} + {% if show_transport_name %} +

    + Status: {{ message_is_queued ? 'Queued' : 'Sent' }} + • + Transport: {{ transport }} +

    + {% endif %} - {% for transport in events.transports %} -
    -
    - {% for event in events.events(transport) %} - {% set message = event.message %} -
    -

    Email {{ event.isQueued() ? 'queued' : 'sent via ' ~ transport }}

    -
    -
    - {% if message.headers is not defined %} - {# RawMessage instance #} -
    -
    {{ message.toString() }}
    -
    - {% else %} - {# Message instance #} -
    -
    -
    -

    Headers

    -
    - Subject -

    {{ message.headers.get('subject').bodyAsString() ?? '(empty)' }}

    -
    -
    - From -
    {{ (message.headers.get('from').bodyAsString() ?? '(empty)')|replace({'From:': ''}) }}
    + {% if message.headers is not defined %} + {# render the raw message contents #} + + {{ source('@WebProfiler/Icon/download.svg') }} + Download as EML file + - To -
    {{ (message.headers.get('to').bodyAsString() ?? '(empty)')|replace({'To:': ''}) }}
    -
    -
    - Headers -
    {% for header in message.headers.all|filter(header => (header.name ?? '') not in ['Subject', 'From', 'To']) %}
    -                                                                {{- header.toString }}
    -                                                            {%~ endfor %}
    -
    -
    -
    -
    - {% if message.htmlBody is defined %} - {# Email instance #} - {% set htmlBody = message.htmlBody() %} - {% if htmlBody is not null %} -
    -

    HTML Preview

    -
    -
    -                                                                
    -                                                            
    -
    -
    -
    -

    HTML Content

    -
    -
    -                                                                {%- if message.htmlCharset() %}
    -                                                                    {{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
    -                                                                {%- else %}
    -                                                                    {{- htmlBody }}
    -                                                                {%- endif -%}
    -                                                            
    -
    -
    - {% endif %} - {% set textBody = message.textBody() %} - {% if textBody is not null %} -
    -

    Text Content

    -
    -
    -                                                                {%- if message.textCharset() %}
    -                                                                    {{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
    -                                                                {%- else %}
    -                                                                    {{- textBody }}
    -                                                                {%- endif -%}
    -                                                            
    -
    -
    - {% endif %} - {% for attachment in message.attachments %} -
    -

    Attachment #{{ loop.index }}

    -
    -
    {{ attachment.toString() }}
    -
    -
    - {% endfor %} +
    {{ message.toString() }}
    + {% else %} +
    +
    +

    Email contents

    +
    +
    +

    + {{ message.getSubject() ?? '(No subject)' }} +

    +
    +

    From: {{ message.getFrom()|map(addr => addr.toString())|join(', ')|default('(empty)') }}

    +

    To: {{ message.getTo()|map(addr => addr.toString())|join(', ')|default('(empty)') }}

    + {% for header in message.headers.all|filter(header => (header.name ?? '')|lower not in ['subject', 'from', 'to']) %} +

    {{ header.toString }}

    + {% endfor %} +
    +
    + + {% if message.attachments %} +
    + {% set num_of_attachments = message.attachments|length %} + {% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length, 0) %} +

    + {{ source('@WebProfiler/Icon/attachment.svg') }} + Attachments ({{ num_of_attachments }} file{{ num_of_attachments > 1 ? 's' }} / {{ _self.render_file_size_humanized(total_attachments_size_in_bytes) }}) +

    + +
      + {% for attachment in message.attachments %} +
    • + {{ source('@WebProfiler/Icon/file.svg') }} + + {% if attachment.filename|default %} + {{ attachment.filename }} + {% else %} + (no filename) {% endif %} -
      -

      Parts Hierarchy

      -
      -
      {{ message.body().asDebugString() }}
      -
      -
      -
      -

      Raw

      -
      -
      {{ message.toString() }}
      -
      + + ({{ _self.render_file_size_humanized(attachment.body|length) }}) + + Download +
    • + {% endfor %} +
    +
    + {% endif %} + +
    + {% set textBody = message.textBody %} + {% set htmlBody = message.htmlBody %} +
    +
    +

    Text content

    +
    + {% if textBody %} +
    +                                                {%- if message.textCharset() %}
    +                                                    {{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
    +                                                {%- else %}
    +                                                    {{- textBody }}
    +                                                {%- endif -%}
    +                                            
    + {% else %} +
    +

    The text body is empty.

    + {% endif %} +
    +
    + + {% if htmlBody %} +
    +

    HTML preview

    +
    +
    
    +                                            
    {% endif %} + +
    +

    HTML content

    +
    + {% if htmlBody %} +
    +                                                {%- if message.htmlCharset() %}
    +                                                    {{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
    +                                                {%- else %}
    +                                                    {{- htmlBody }}
    +                                                {%- endif -%}
    +                                            
    + {% else %} +
    +

    The HTML body is empty.

    +
    + {% endif %} +
    +
    - {% endfor %} +
    + +
    +

    MIME parts

    +
    +
    {{ message.body().asDebugString() }}
    +
    +
    + +
    +

    Raw Message

    + +
    -
    - {% endfor %} + {% endif %} + {% endmacro %} + + {% macro render_file_size_humanized(bytes) %} + {%- if bytes < 1000 -%} + {{- bytes ~ ' bytes' -}} + {%- elseif bytes < 1000 ** 2 -%} + {{- (bytes / 1000)|number_format(2) ~ ' kB' -}} + {%- else -%} + {{- (bytes / 1000 ** 2)|number_format(2) ~ ' MB' -}} + {%- endif -%} + {% endmacro %} {% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig index 1336a57a2..4688272ef 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig @@ -3,7 +3,7 @@ {% block toolbar %} {% set icon %} {% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' %} - {{ include('@WebProfiler/Icon/memory.svg') }} + {{ source('@WebProfiler/Icon/memory.svg') }} {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MiB {% endset %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/messenger.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/messenger.html.twig index b48aaa82e..d6630e678 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/messenger.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/messenger.html.twig @@ -1,12 +1,49 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} +{% block head %} + {{ parent() }} + + +{% endblock %} {% block toolbar %} {% if collector.messages|length > 0 %} {% set status_color = collector.exceptionsCount ? 'red' %} {% set icon %} - {{ include('@WebProfiler/Icon/messenger.svg') }} + {{ source('@WebProfiler/Icon/messenger.svg') }} {{ collector.messages|length }} {% endset %} @@ -31,7 +68,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/messenger.svg') }} + {{ source('@WebProfiler/Icon/messenger.svg') }} Messages {% if collector.exceptionsCount > 0 %} @@ -41,36 +78,16 @@ {% endblock %} -{% block head %} - {{ parent() }} - -{% endblock %} - {% block panel %} - {% import _self as helper %} -

    Messages

    {% if collector.messages is empty %} -
    +

    No messages have been collected.

    + {% elseif 1 == collector.buses|length %} +

    Ordered list of dispatched messages across all your buses

    + {{ _self.render_bus_messages(collector.messages, true) }} {% else %}
    @@ -80,7 +97,7 @@

    Ordered list of dispatched messages across all your buses

    - {{ helper.render_bus_messages(messages, true) }} + {{ _self.render_bus_messages(messages, true) }}
    @@ -92,7 +109,7 @@

    Ordered list of messages dispatched on the {{ bus }} bus

    - {{ helper.render_bus_messages(messages) }} + {{ _self.render_bus_messages(messages) }}
    {% endfor %} @@ -112,36 +129,33 @@ data-toggle-initial="{{ loop.first ? 'display' }}" > {{ profiler_dump(dispatchCall.message.type) }} - {% if showBus %} - {{ dispatchCall.bus }} - {% endif %} {% if dispatchCall.exception is defined %} exception {% endif %} - - {{ include('@WebProfiler/images/icon-minus-square.svg') }} - {{ include('@WebProfiler/images/icon-plus-square.svg') }} - + - - + {% else %} + {{ caller.name }} + {% endif %} + line