From 6ad0169c4feea4881b64dd5cb00acf5e9ecd11dd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Nov 2020 15:08:49 +0100 Subject: [PATCH 001/188] [Intl] deprecate polyfills in favor of symfony/polyfill-intl-icu --- src/Symfony/Component/Form/composer.json | 2 +- .../Component/Intl/Collator/Collator.php | 2 + .../DateFormat/AmPmTransformer.php | 2 + .../DateFormat/DayOfWeekTransformer.php | 2 + .../DateFormat/DayOfYearTransformer.php | 2 + .../DateFormat/DayTransformer.php | 2 + .../DateFormat/FullTransformer.php | 2 + .../DateFormat/Hour1200Transformer.php | 2 + .../DateFormat/Hour1201Transformer.php | 2 + .../DateFormat/Hour2400Transformer.php | 2 + .../DateFormat/Hour2401Transformer.php | 2 + .../DateFormat/HourTransformer.php | 2 + .../DateFormat/MinuteTransformer.php | 2 + .../DateFormat/MonthTransformer.php | 2 + .../DateFormat/QuarterTransformer.php | 2 + .../DateFormat/SecondTransformer.php | 2 + .../DateFormat/TimezoneTransformer.php | 2 + .../DateFormatter/DateFormat/Transformer.php | 2 + .../DateFormat/YearTransformer.php | 2 + .../Intl/DateFormatter/IntlDateFormatter.php | 2 + .../MethodArgumentNotImplementedException.php | 2 + ...odArgumentValueNotImplementedException.php | 2 + .../MethodNotImplementedException.php | 2 + .../Exception/NotImplementedException.php | 2 + .../Component/Intl/Globals/IntlGlobals.php | 32 ++++++++++++++++ src/Symfony/Component/Intl/Locale/Locale.php | 2 + .../Intl/NumberFormatter/NumberFormatter.php | 2 + src/Symfony/Component/Intl/README.md | 8 +--- .../Component/Intl/Resources/functions.php | 37 +++++++++++++++++++ .../Intl/Resources/stubs/Collator.php | 28 ++++++++++---- .../Resources/stubs/IntlDateFormatter.php | 34 ++++++++++++----- .../Component/Intl/Resources/stubs/Locale.php | 34 ++++++++++++----- .../Intl/Resources/stubs/NumberFormatter.php | 34 ++++++++++++----- .../Intl/Tests/Collator/CollatorTest.php | 3 ++ .../AbstractIntlDateFormatterTest.php | 2 + .../DateFormatter/IntlDateFormatterTest.php | 3 ++ .../Tests/Globals/AbstractIntlGlobalsTest.php | 2 + .../Intl/Tests/Globals/IntlGlobalsTest.php | 3 ++ .../Globals/Verification/IntlGlobalsTest.php | 2 + .../AbstractNumberFormatterTest.php | 2 + .../NumberFormatter/NumberFormatterTest.php | 2 + src/Symfony/Component/Intl/composer.json | 10 ++--- .../Translation/Test/TranslatorTest.php | 2 + 43 files changed, 242 insertions(+), 48 deletions(-) create mode 100644 src/Symfony/Component/Intl/Resources/functions.php diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index b17ef9804901d..9f1656ac3cb4f 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -22,6 +22,7 @@ "symfony/intl": "^4.4|^5.0", "symfony/options-resolver": "^5.1", "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.15", "symfony/property-access": "^5.0.8", @@ -48,7 +49,6 @@ "symfony/error-handler": "<4.4.5", "symfony/framework-bundle": "<4.4", "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.4", "symfony/translation": "<4.4", "symfony/translation-contracts": "<1.1.7", "symfony/twig-bridge": "<4.4" diff --git a/src/Symfony/Component/Intl/Collator/Collator.php b/src/Symfony/Component/Intl/Collator/Collator.php index a43a4f69bf0eb..37a312280346a 100644 --- a/src/Symfony/Component/Intl/Collator/Collator.php +++ b/src/Symfony/Component/Intl/Collator/Collator.php @@ -32,6 +32,8 @@ * @author Bernhard Schussek * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ abstract class Collator { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/AmPmTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/AmPmTransformer.php index 36a1294c70b92..7d370bfb87d95 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/AmPmTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/AmPmTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class AmPmTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfWeekTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfWeekTransformer.php index f18abb9024b51..626d304b54f81 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfWeekTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfWeekTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class DayOfWeekTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php index 93c09d8b22da3..0d6f0b60cdf9d 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class DayOfYearTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php index 91676d3e58f19..47295252991ec 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class DayTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php index 2641528989609..709632f3172dd 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php @@ -20,6 +20,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class FullTransformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1200Transformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1200Transformer.php index 59d10fe20dae4..34e4b3a5c5594 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1200Transformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1200Transformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class Hour1200Transformer extends HourTransformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1201Transformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1201Transformer.php index f090ba4c27759..8e5eba1daf4fe 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1201Transformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour1201Transformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class Hour1201Transformer extends HourTransformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2400Transformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2400Transformer.php index 9ca6dfaacf94a..4296978713f13 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2400Transformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2400Transformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class Hour2400Transformer extends HourTransformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2401Transformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2401Transformer.php index e4db51cdf0e1b..0db1a888b5ee7 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2401Transformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Hour2401Transformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class Hour2401Transformer extends HourTransformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/HourTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/HourTransformer.php index 349cd794de3ae..54dcbfe25d24d 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/HourTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/HourTransformer.php @@ -17,6 +17,8 @@ * @author Eriksen Costa * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ abstract class HourTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MinuteTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MinuteTransformer.php index 32f60d0928c55..30b76c9779383 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MinuteTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MinuteTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class MinuteTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php index fcc4c4fee9fe5..5db91114f8c79 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class MonthTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/QuarterTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/QuarterTransformer.php index 6efea71f2ff44..71b95c8b9e7e0 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/QuarterTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/QuarterTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class QuarterTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/SecondTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/SecondTransformer.php index bce89af1c95f3..b6428e114f21e 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/SecondTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/SecondTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class SecondTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimezoneTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimezoneTransformer.php index 4a4e38e3e1a1a..ad243634d3790 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimezoneTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimezoneTransformer.php @@ -19,6 +19,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class TimezoneTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Transformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Transformer.php index 2351323946090..4ab993338224c 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/Transformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/Transformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ abstract class Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php index 8718094c06df0..8ed7b4165741b 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php @@ -17,6 +17,8 @@ * @author Igor Wiedler * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class YearTransformer extends Transformer { diff --git a/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php b/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php index fee4f0a5d951b..c1c4715601897 100644 --- a/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php +++ b/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php @@ -45,6 +45,8 @@ * @author Bernhard Schussek * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ abstract class IntlDateFormatter { diff --git a/src/Symfony/Component/Intl/Exception/MethodArgumentNotImplementedException.php b/src/Symfony/Component/Intl/Exception/MethodArgumentNotImplementedException.php index bd6e4791debb1..d0a1d61c4c773 100644 --- a/src/Symfony/Component/Intl/Exception/MethodArgumentNotImplementedException.php +++ b/src/Symfony/Component/Intl/Exception/MethodArgumentNotImplementedException.php @@ -13,6 +13,8 @@ /** * @author Eriksen Costa + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class MethodArgumentNotImplementedException extends NotImplementedException { diff --git a/src/Symfony/Component/Intl/Exception/MethodArgumentValueNotImplementedException.php b/src/Symfony/Component/Intl/Exception/MethodArgumentValueNotImplementedException.php index ee9ebe5e2fb55..611e6ed02fb63 100644 --- a/src/Symfony/Component/Intl/Exception/MethodArgumentValueNotImplementedException.php +++ b/src/Symfony/Component/Intl/Exception/MethodArgumentValueNotImplementedException.php @@ -13,6 +13,8 @@ /** * @author Eriksen Costa + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class MethodArgumentValueNotImplementedException extends NotImplementedException { diff --git a/src/Symfony/Component/Intl/Exception/MethodNotImplementedException.php b/src/Symfony/Component/Intl/Exception/MethodNotImplementedException.php index 6a45d71a483b2..6eb7dc120d9e6 100644 --- a/src/Symfony/Component/Intl/Exception/MethodNotImplementedException.php +++ b/src/Symfony/Component/Intl/Exception/MethodNotImplementedException.php @@ -13,6 +13,8 @@ /** * @author Eriksen Costa + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class MethodNotImplementedException extends NotImplementedException { diff --git a/src/Symfony/Component/Intl/Exception/NotImplementedException.php b/src/Symfony/Component/Intl/Exception/NotImplementedException.php index 1413886b3e86d..83668993c4418 100644 --- a/src/Symfony/Component/Intl/Exception/NotImplementedException.php +++ b/src/Symfony/Component/Intl/Exception/NotImplementedException.php @@ -15,6 +15,8 @@ * Base exception class for not implemented behaviors of the intl extension in the Locale component. * * @author Eriksen Costa + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ class NotImplementedException extends RuntimeException { diff --git a/src/Symfony/Component/Intl/Globals/IntlGlobals.php b/src/Symfony/Component/Intl/Globals/IntlGlobals.php index d353368900d02..9eaf04355b512 100644 --- a/src/Symfony/Component/Intl/Globals/IntlGlobals.php +++ b/src/Symfony/Component/Intl/Globals/IntlGlobals.php @@ -11,12 +11,16 @@ namespace Symfony\Component\Intl\Globals; +use Symfony\Polyfill\Intl\Icu\Icu; + /** * Provides fake static versions of the global functions in the intl extension. * * @author Bernhard Schussek * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ abstract class IntlGlobals { @@ -61,6 +65,12 @@ abstract class IntlGlobals */ public static function isFailure(int $errorCode): bool { + if (class_exists(Icu::class)) { + return Icu::isFailure($errorCode); + } + + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + return isset(self::$errorCodes[$errorCode]) && $errorCode > self::U_ZERO_ERROR; } @@ -74,6 +84,12 @@ public static function isFailure(int $errorCode): bool */ public static function getErrorCode() { + if (class_exists(Icu::class)) { + return Icu::getErrorCode(); + } + + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + return self::$errorCode; } @@ -84,6 +100,12 @@ public static function getErrorCode() */ public static function getErrorMessage(): string { + if (class_exists(Icu::class)) { + return Icu::getErrorMessage(); + } + + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + return self::$errorMessage; } @@ -94,6 +116,12 @@ public static function getErrorMessage(): string */ public static function getErrorName(int $code): string { + if (class_exists(Icu::class)) { + return Icu::getErrorName($code); + } + + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + return self::$errorCodes[$code] ?? '[BOGUS UErrorCode]'; } @@ -107,6 +135,10 @@ public static function getErrorName(int $code): string */ public static function setError(int $code, string $message = '') { + if (class_exists(Icu::class)) { + return Icu::setError($code, $message); + } + if (!isset(self::$errorCodes[$code])) { throw new \InvalidArgumentException(sprintf('No such error code: "%s".', $code)); } diff --git a/src/Symfony/Component/Intl/Locale/Locale.php b/src/Symfony/Component/Intl/Locale/Locale.php index 6fbe50e3faa3b..b300e03599f5b 100644 --- a/src/Symfony/Component/Intl/Locale/Locale.php +++ b/src/Symfony/Component/Intl/Locale/Locale.php @@ -23,6 +23,8 @@ * @author Bernhard Schussek * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ abstract class Locale { diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index 40223dc86da6d..a298dcbf3ce49 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -39,6 +39,8 @@ * @author Bernhard Schussek * * @internal + * + * @deprecated since Symfony 5.3, use symfony/polyfill-intl-icu ^1.21 instead */ abstract class NumberFormatter { diff --git a/src/Symfony/Component/Intl/README.md b/src/Symfony/Component/Intl/README.md index 03b50c91a048f..3911053dd759d 100644 --- a/src/Symfony/Component/Intl/README.md +++ b/src/Symfony/Component/Intl/README.md @@ -1,11 +1,7 @@ Intl Component ============= -A PHP replacement layer for the C intl extension that also provides access to -the localization data of the ICU library. - -The replacement layer is limited to the locale "en". If you want to use other -locales, you should [install the intl PHP extension][0] instead. +This package provides access to the CLDR localization data of the ICU library. Resources --------- @@ -17,5 +13,3 @@ Resources in the [main Symfony repository](https://github.com/symfony/symfony) * [Docker images with intl support](https://hub.docker.com/r/jakzal/php-intl) (for the Intl component development) - -[0]: https://php.net/intl.setup diff --git a/src/Symfony/Component/Intl/Resources/functions.php b/src/Symfony/Component/Intl/Resources/functions.php new file mode 100644 index 0000000000000..251d039fdd843 --- /dev/null +++ b/src/Symfony/Component/Intl/Resources/functions.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Intl\Globals\IntlGlobals; + +if (!function_exists('intl_is_failure')) { + function intl_is_failure($error_code) + { + return IntlGlobals::isFailure($error_code); + } +} +if (!function_exists('intl_get_error_code')) { + function intl_get_error_code() + { + return IntlGlobals::getErrorCode(); + } +} +if (!function_exists('intl_get_error_message')) { + function intl_get_error_message() + { + return IntlGlobals::getErrorMessage(); + } +} +if (!function_exists('intl_error_name')) { + function intl_error_name($error_code) + { + return IntlGlobals::getErrorName($error_code); + } +} diff --git a/src/Symfony/Component/Intl/Resources/stubs/Collator.php b/src/Symfony/Component/Intl/Resources/stubs/Collator.php index 1977fdf6173f9..91acdf146d2c6 100644 --- a/src/Symfony/Component/Intl/Resources/stubs/Collator.php +++ b/src/Symfony/Component/Intl/Resources/stubs/Collator.php @@ -10,12 +10,26 @@ */ use Symfony\Component\Intl\Collator\Collator as IntlCollator; +use Symfony\Polyfill\Intl\Icu\Collator as CollatorPolyfill; -/** - * Stub implementation for the Collator class of the intl extension. - * - * @author Bernhard Schussek - */ -class Collator extends IntlCollator -{ +if (!class_exists(CollatorPolyfill::class)) { + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + + /** + * Stub implementation for the Collator class of the intl extension. + * + * @author Bernhard Schussek + */ + class Collator extends IntlCollator + { + } +} else { + /** + * Stub implementation for the Collator class of the intl extension. + * + * @author Bernhard Schussek + */ + class Collator extends CollatorPolyfill + { + } } diff --git a/src/Symfony/Component/Intl/Resources/stubs/IntlDateFormatter.php b/src/Symfony/Component/Intl/Resources/stubs/IntlDateFormatter.php index e5209b62cccd4..b8948c157bbd2 100644 --- a/src/Symfony/Component/Intl/Resources/stubs/IntlDateFormatter.php +++ b/src/Symfony/Component/Intl/Resources/stubs/IntlDateFormatter.php @@ -10,14 +10,30 @@ */ use Symfony\Component\Intl\DateFormatter\IntlDateFormatter as BaseIntlDateFormatter; +use Symfony\Polyfill\Intl\Icu\IntlDateFormatter as IntlDateFormatterPolyfill; -/** - * Stub implementation for the IntlDateFormatter class of the intl extension. - * - * @author Bernhard Schussek - * - * @see BaseIntlDateFormatter - */ -class IntlDateFormatter extends BaseIntlDateFormatter -{ +if (!class_exists(IntlDateFormatterPolyfill::class)) { + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + + /** + * Stub implementation for the IntlDateFormatter class of the intl extension. + * + * @author Bernhard Schussek + * + * @see BaseIntlDateFormatter + */ + class IntlDateFormatter extends BaseIntlDateFormatter + { + } +} else { + /** + * Stub implementation for the IntlDateFormatter class of the intl extension. + * + * @author Bernhard Schussek + * + * @see BaseIntlDateFormatter + */ + class IntlDateFormatter extends IntlDateFormatterPolyfill + { + } } diff --git a/src/Symfony/Component/Intl/Resources/stubs/Locale.php b/src/Symfony/Component/Intl/Resources/stubs/Locale.php index 8a3b89bc3efe7..5021fa0ceb773 100644 --- a/src/Symfony/Component/Intl/Resources/stubs/Locale.php +++ b/src/Symfony/Component/Intl/Resources/stubs/Locale.php @@ -10,14 +10,30 @@ */ use Symfony\Component\Intl\Locale\Locale as IntlLocale; +use Symfony\Polyfill\Intl\Icu\Locale as LocalePolyfill; -/** - * Stub implementation for the Locale class of the intl extension. - * - * @author Bernhard Schussek - * - * @see IntlLocale - */ -class Locale extends IntlLocale -{ +if (!class_exists(LocalePolyfill::class)) { + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + + /** + * Stub implementation for the Locale class of the intl extension. + * + * @author Bernhard Schussek + * + * @see IntlLocale + */ + class Locale extends IntlLocale + { + } +} else { + /** + * Stub implementation for the Locale class of the intl extension. + * + * @author Bernhard Schussek + * + * @see IntlLocale + */ + class Locale extends LocalePolyfill + { + } } diff --git a/src/Symfony/Component/Intl/Resources/stubs/NumberFormatter.php b/src/Symfony/Component/Intl/Resources/stubs/NumberFormatter.php index c8e689b3abbd6..7ec6e1c1028fb 100644 --- a/src/Symfony/Component/Intl/Resources/stubs/NumberFormatter.php +++ b/src/Symfony/Component/Intl/Resources/stubs/NumberFormatter.php @@ -10,14 +10,30 @@ */ use Symfony\Component\Intl\NumberFormatter\NumberFormatter as IntlNumberFormatter; +use Symfony\Polyfill\Intl\Icu\NumberFormatter as NumberFormatterPolyfill; -/** - * Stub implementation for the NumberFormatter class of the intl extension. - * - * @author Bernhard Schussek - * - * @see IntlNumberFormatter - */ -class NumberFormatter extends IntlNumberFormatter -{ +if (!class_exists(NumberFormatterPolyfill::class)) { + trigger_deprecation('symfony/intl', '5.3', 'Polyfills are deprecated, try running "composer require symfony/polyfill-intl-icu ^1.21" instead.'); + + /** + * Stub implementation for the NumberFormatter class of the intl extension. + * + * @author Bernhard Schussek + * + * @see IntlNumberFormatter + */ + class NumberFormatter extends IntlNumberFormatter + { + } +} else { + /** + * Stub implementation for the NumberFormatter class of the intl extension. + * + * @author Bernhard Schussek + * + * @see IntlNumberFormatter + */ + class NumberFormatter extends NumberFormatterPolyfill + { + } } diff --git a/src/Symfony/Component/Intl/Tests/Collator/CollatorTest.php b/src/Symfony/Component/Intl/Tests/Collator/CollatorTest.php index 0964e31e34c1e..2cf74afc828d0 100644 --- a/src/Symfony/Component/Intl/Tests/Collator/CollatorTest.php +++ b/src/Symfony/Component/Intl/Tests/Collator/CollatorTest.php @@ -14,6 +14,9 @@ use Symfony\Component\Intl\Collator\Collator; use Symfony\Component\Intl\Globals\IntlGlobals; +/** + * @group legacy + */ class CollatorTest extends AbstractCollatorTest { public function testConstructorWithUnsupportedLocale() diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php index cee6b548a29fb..e18b01f604e21 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php @@ -21,6 +21,8 @@ * Test case for IntlDateFormatter implementations. * * @author Bernhard Schussek + * + * @group legacy */ abstract class AbstractIntlDateFormatterTest extends TestCase { diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php index f674c657ed85f..145f4c8da663a 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php @@ -14,6 +14,9 @@ use Symfony\Component\Intl\DateFormatter\IntlDateFormatter; use Symfony\Component\Intl\Globals\IntlGlobals; +/** + * @group legacy + */ class IntlDateFormatterTest extends AbstractIntlDateFormatterTest { public function testConstructor() diff --git a/src/Symfony/Component/Intl/Tests/Globals/AbstractIntlGlobalsTest.php b/src/Symfony/Component/Intl/Tests/Globals/AbstractIntlGlobalsTest.php index 2cb49ef8275b3..5c4731babd8d2 100644 --- a/src/Symfony/Component/Intl/Tests/Globals/AbstractIntlGlobalsTest.php +++ b/src/Symfony/Component/Intl/Tests/Globals/AbstractIntlGlobalsTest.php @@ -17,6 +17,8 @@ * Test case for intl function implementations. * * @author Bernhard Schussek + * + * @group legacy */ abstract class AbstractIntlGlobalsTest extends TestCase { diff --git a/src/Symfony/Component/Intl/Tests/Globals/IntlGlobalsTest.php b/src/Symfony/Component/Intl/Tests/Globals/IntlGlobalsTest.php index 34e3a6a3a7875..27400e65fd74c 100644 --- a/src/Symfony/Component/Intl/Tests/Globals/IntlGlobalsTest.php +++ b/src/Symfony/Component/Intl/Tests/Globals/IntlGlobalsTest.php @@ -13,6 +13,9 @@ use Symfony\Component\Intl\Globals\IntlGlobals; +/** + * @group legacy + */ class IntlGlobalsTest extends AbstractIntlGlobalsTest { protected function getIntlErrorName($errorCode) diff --git a/src/Symfony/Component/Intl/Tests/Globals/Verification/IntlGlobalsTest.php b/src/Symfony/Component/Intl/Tests/Globals/Verification/IntlGlobalsTest.php index 4b390d58c1ea0..c7bc125b2e7c4 100644 --- a/src/Symfony/Component/Intl/Tests/Globals/Verification/IntlGlobalsTest.php +++ b/src/Symfony/Component/Intl/Tests/Globals/Verification/IntlGlobalsTest.php @@ -19,6 +19,8 @@ * intl functions with a specific version of ICU. * * @author Bernhard Schussek + * + * @group legacy */ class IntlGlobalsTest extends AbstractIntlGlobalsTest { diff --git a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php index 73c845180a34a..99c095977d769 100644 --- a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php @@ -20,6 +20,8 @@ /** * Note that there are some values written like -2147483647 - 1. This is the lower 32bit int max and is a known * behavior of PHP. + * + * @group legacy */ abstract class AbstractNumberFormatterTest extends TestCase { diff --git a/src/Symfony/Component/Intl/Tests/NumberFormatter/NumberFormatterTest.php b/src/Symfony/Component/Intl/Tests/NumberFormatter/NumberFormatterTest.php index 9e5a82fe5cfb5..aacd698994a78 100644 --- a/src/Symfony/Component/Intl/Tests/NumberFormatter/NumberFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/NumberFormatter/NumberFormatterTest.php @@ -17,6 +17,8 @@ /** * Note that there are some values written like -2147483647 - 1. This is the lower 32bit int max and is a known * behavior of PHP. + * + * @group legacy */ class NumberFormatterTest extends AbstractNumberFormatterTest { diff --git a/src/Symfony/Component/Intl/composer.json b/src/Symfony/Component/Intl/composer.json index ff93e44b860f4..2f29e466dac30 100644 --- a/src/Symfony/Component/Intl/composer.json +++ b/src/Symfony/Component/Intl/composer.json @@ -1,8 +1,8 @@ { "name": "symfony/intl", "type": "library", - "description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.", - "keywords": ["intl", "icu", "internationalization", "localization", "i18n", "l10n"], + "description": "This package provides access to the CLDR localization data of the ICU library.", + "keywords": ["intl", "icu", "internationalization", "localization", "i18n", "l10n", "cldr"], "homepage": "https://symfony.com", "license": "MIT", "authors": [ @@ -25,18 +25,16 @@ ], "require": { "php": ">=7.2.5", - "symfony/polyfill-intl-icu": "~1.0", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-php80": "^1.15" }, "require-dev": { "symfony/filesystem": "^4.4|^5.0" }, - "suggest": { - "ext-intl": "to use the component with locales other than \"en\"" - }, "autoload": { "psr-4": { "Symfony\\Component\\Intl\\": "" }, "classmap": [ "Resources/stubs" ], + "files": [ "Resources/functions.php" ], "exclude-from-classmap": [ "/Tests/" ] diff --git a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php index 5bfb0f8df616a..4d84f4729f830 100644 --- a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php +++ b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php @@ -59,6 +59,8 @@ public function testTransChoiceWithExplicitLocale($expected, $id, $number) } /** + * @requires extension intl + * * @dataProvider getTransChoiceTests */ public function testTransChoiceWithDefaultLocale($expected, $id, $number) From b285584e77e69ad9740711a12581c3fce46535ee Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 10 Nov 2020 08:38:36 +0100 Subject: [PATCH 002/188] updated version to 5.3 --- src/Symfony/Component/HttpKernel/Kernel.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 3522aaa770665..03c3bb0412dca 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -74,15 +74,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.2.0-DEV'; - const VERSION_ID = 50200; + const VERSION = '5.3.0-DEV'; + const VERSION_ID = 50300; const MAJOR_VERSION = 5; - const MINOR_VERSION = 2; + const MINOR_VERSION = 3; const RELEASE_VERSION = 0; const EXTRA_VERSION = 'DEV'; - const END_OF_MAINTENANCE = '07/2021'; - const END_OF_LIFE = '07/2021'; + const END_OF_MAINTENANCE = '05/2021'; + const END_OF_LIFE = '01/2022'; public function __construct(string $environment, bool $debug) { From 66edc59b56f2e1436543b700a8360bfb154e2c75 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 5 Nov 2020 09:31:41 +0100 Subject: [PATCH 003/188] [Messenger][SQS] Make sure one can enable debug logs --- composer.json | 1 + .../Resources/config/messenger.php | 3 + .../Messenger/Bridge/AmazonSqs/CHANGELOG.md | 5 ++ .../Tests/Transport/ConnectionTest.php | 88 ++++++++++++++++++- .../Transport/AmazonSqsTransportFactory.php | 10 ++- .../Bridge/AmazonSqs/Transport/Connection.php | 10 ++- .../Messenger/Bridge/AmazonSqs/composer.json | 4 +- 7 files changed, 114 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 3034f796f7084..99b5dd8ac405b 100644 --- a/composer.json +++ b/composer.json @@ -138,6 +138,7 @@ "twig/markdown-extra": "^2.12" }, "conflict": { + "async-aws/core": "<1.5", "doctrine/dbal": "<2.10", "masterminds/html5": "<2.6", "phpdocumentor/reflection-docblock": "<3.2.2", diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index c7838ff615360..d3b9bbd681ed8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -128,6 +128,9 @@ ->tag('kernel.reset', ['method' => 'reset']) ->set('messenger.transport.sqs.factory', AmazonSqsTransportFactory::class) + ->args([ + service('logger')->ignoreOnInvalid(), + ]) ->set('messenger.transport.beanstalkd.factory', BeanstalkdTransportFactory::class) diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md index 67a5252f0b21c..03a7df3723902 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * Added new `debug` option to log HTTP requests and responses. + 5.2.0 ----- diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php index 52e716981d647..a285f3321b4ba 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php @@ -18,6 +18,9 @@ use AsyncAws\Sqs\SqsClient; use AsyncAws\Sqs\ValueObject\Message; use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\Connection; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -179,7 +182,7 @@ public function testFromDsnWithAccountAndEndpointOption() public function testFromDsnWithInvalidQueryString() { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Unknown option found in DSN: [foo]. Allowed options are [buffer_size, wait_time, poll_timeout, visibility_timeout, auto_setup, access_key, secret_key, endpoint, region, queue_name, account, sslmode].'); + $this->expectExceptionMessageMatches('|Unknown option found in DSN: \[foo\]\. Allowed options are \[buffer_size, |'); Connection::fromDsn('sqs://default?foo=foo'); } @@ -187,7 +190,7 @@ public function testFromDsnWithInvalidQueryString() public function testFromDsnWithInvalidOption() { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Unknown option found: [bar]. Allowed options are [buffer_size, wait_time, poll_timeout, visibility_timeout, auto_setup, access_key, secret_key, endpoint, region, queue_name, account, sslmode].'); + $this->expectExceptionMessageMatches('|Unknown option found: \[bar\]\. Allowed options are \[buffer_size, |'); Connection::fromDsn('sqs://default', ['bar' => 'bar']); } @@ -195,7 +198,7 @@ public function testFromDsnWithInvalidOption() public function testFromDsnWithInvalidQueryStringAndOption() { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Unknown option found: [bar]. Allowed options are [buffer_size, wait_time, poll_timeout, visibility_timeout, auto_setup, access_key, secret_key, endpoint, region, queue_name, account, sslmode].'); + $this->expectExceptionMessageMatches('|Unknown option found: \[bar\]\. Allowed options are \[buffer_size, |'); Connection::fromDsn('sqs://default?foo=foo', ['bar' => 'bar']); } @@ -312,4 +315,83 @@ public function testGetQueueUrlNotCalled() $connection->delete('id'); } + + public function testLoggerWithoutDebugOption() + { + $client = new MockHttpClient([$this->getMockedQueueUrlResponse(), $this->getMockedReceiveMessageResponse()]); + $logger = $this->getMockBuilder(NullLogger::class) + ->disableOriginalConstructor() + ->onlyMethods(['debug']) + ->getMock(); + $logger->expects($this->never())->method('debug'); + $connection = Connection::fromDsn('sqs://default', ['access_key' => 'foo', 'secret_key' => 'bar', 'auto_setup' => false], $client, $logger); + $connection->get(); + } + + public function testLoggerWithDebugOption() + { + $client = new MockHttpClient([$this->getMockedQueueUrlResponse(), $this->getMockedReceiveMessageResponse()]); + $logger = $this->getMockBuilder(NullLogger::class) + ->disableOriginalConstructor() + ->onlyMethods(['debug']) + ->getMock(); + $logger->expects($this->exactly(4))->method('debug'); + $connection = Connection::fromDsn('sqs://default?debug=true', ['access_key' => 'foo', 'secret_key' => 'bar', 'auto_setup' => false], $client, $logger); + $connection->get(); + } + + private function getMockedQueueUrlResponse(): MockResponse + { + return new MockResponse(<< + + https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue + + + 470a6f13-2ed9-4181-ad8a-2fdea142988e + + +XML + ); + } + + private function getMockedReceiveMessageResponse(): MockResponse + { + return new MockResponse(<< + + + 5fea7756-0ea4-451a-a703-a558b933e274 + + MbZj6wDWli+JvwwJaBV+3dcjk2YW2vA3+STFFljTM8tJJg6HRG6PYSasuWXPJB+Cw + Lj1FjgXUv1uSj1gUPAWV66FU/WeR4mq2OKpEGYWbnLmpRCJVAyeMjeU5ZBdtcQ+QE + auMZc8ZRv37sIW2iJKq3M9MFx1YvV11A2x/KSbkJ0= + + fafb00f5732ab283681e124bf8747ed1 + This is a test message + + SenderId + 195004372649 + + + SentTimestamp + 1238099229000 + + + ApproximateReceiveCount + 5 + + + ApproximateFirstReceiveTimestamp + 1250700979248 + + + + + b6633655-283d-45b4-aee4-4e84e0ae6afa + + +XML + ); + } } diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransportFactory.php index d0424d1e9555c..0673966ba0cf5 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransportFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransportFactory.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Bridge\AmazonSqs\Transport; +use Psr\Log\LoggerInterface; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Messenger\Transport\TransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; @@ -20,11 +21,18 @@ */ class AmazonSqsTransportFactory implements TransportFactoryInterface { + private $logger; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface { unset($options['transport_name']); - return new AmazonSqsTransport(Connection::fromDsn($dsn, $options), $serializer); + return new AmazonSqsTransport(Connection::fromDsn($dsn, $options, null, $this->logger), $serializer); } public function supports(string $dsn, array $options): bool diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php index d4c7053b48f7b..f2408dc0588c8 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php @@ -15,6 +15,7 @@ use AsyncAws\Sqs\Result\ReceiveMessageResult; use AsyncAws\Sqs\SqsClient; use AsyncAws\Sqs\ValueObject\MessageAttributeValue; +use Psr\Log\LoggerInterface; use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\Exception\TransportException; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -45,6 +46,7 @@ class Connection 'queue_name' => 'messages', 'account' => null, 'sslmode' => null, + 'debug' => null, ]; private $configuration; @@ -85,8 +87,9 @@ public function __destruct() * * poll_timeout: amount of seconds the transport should wait for new message * * visibility_timeout: amount of seconds the message won't be visible * * auto_setup: Whether the queue should be created automatically during send / get (Default: true) + * * debug: Log all HTTP requests and responses as LoggerInterface::DEBUG (Default: false) */ - public static function fromDsn(string $dsn, array $options = [], HttpClientInterface $client = null): self + public static function fromDsn(string $dsn, array $options = [], HttpClientInterface $client = null, LoggerInterface $logger = null): self { if (false === $parsedUrl = parse_url($dsn)) { throw new InvalidArgumentException(sprintf('The given Amazon SQS DSN "%s" is invalid.', $dsn)); @@ -124,6 +127,9 @@ public static function fromDsn(string $dsn, array $options = [], HttpClientInter 'accessKeyId' => urldecode($parsedUrl['user'] ?? '') ?: $options['access_key'] ?? self::DEFAULT_OPTIONS['access_key'], 'accessKeySecret' => urldecode($parsedUrl['pass'] ?? '') ?: $options['secret_key'] ?? self::DEFAULT_OPTIONS['secret_key'], ]; + if (isset($options['debug'])) { + $clientConfiguration['debug'] = $options['debug']; + } unset($query['region']); if ('default' !== ($parsedUrl['host'] ?? 'default')) { @@ -152,7 +158,7 @@ public static function fromDsn(string $dsn, array $options = [], HttpClientInter $queueUrl = 'https://'.$parsedUrl['host'].$parsedUrl['path']; } - return new self($configuration, new SqsClient($clientConfiguration, null, $client), $queueUrl); + return new self($configuration, new SqsClient($clientConfiguration, null, $client, $logger), $queueUrl); } public function get(): ?array diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json index 3bcee938c00e5..c9902ac1c3b23 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json @@ -17,9 +17,11 @@ ], "require": { "php": ">=7.2.5", + "async-aws/core": "^1.5", "async-aws/sqs": "^1.0", "symfony/messenger": "^4.3|^5.0", - "symfony/service-contracts": "^1.1|^2" + "symfony/service-contracts": "^1.1|^2", + "psr/log": "^1.0" }, "require-dev": { "symfony/http-client-contracts": "^1.0|^2.0", From c2fa2cb376c3db62b9056a7387cd19395d66d784 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Fri, 16 Oct 2020 11:59:17 +0200 Subject: [PATCH 004/188] [BrowserKit] Add jsonRequest function to the browser-kit client --- .../Component/BrowserKit/AbstractBrowser.php | 18 ++++++++++++++++++ src/Symfony/Component/BrowserKit/CHANGELOG.md | 5 +++++ .../BrowserKit/Tests/AbstractBrowserTest.php | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php index f8c89871149e3..a43f434c70337 100644 --- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php +++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php @@ -161,6 +161,24 @@ public function xmlHttpRequest(string $method, string $uri, array $parameters = } } + /** + * Converts the request parameters into a JSON string and uses it as request content. + */ + public function jsonRequest(string $method, string $uri, array $parameters = [], array $server = [], bool $changeHistory = true): Crawler + { + $content = json_encode($parameters); + + $this->setServerParameter('CONTENT_TYPE', 'application/json'); + $this->setServerParameter('HTTP_ACCEPT', 'application/json'); + + try { + return $this->request($method, $uri, [], [], $server, $content, $changeHistory); + } finally { + unset($this->server['CONTENT_TYPE']); + unset($this->server['HTTP_ACCEPT']); + } + } + /** * Returns the History instance. * diff --git a/src/Symfony/Component/BrowserKit/CHANGELOG.md b/src/Symfony/Component/BrowserKit/CHANGELOG.md index 323166a3d6cc5..70401267c4f32 100644 --- a/src/Symfony/Component/BrowserKit/CHANGELOG.md +++ b/src/Symfony/Component/BrowserKit/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * Added `jsonRequest` method to `AbstractBrowser` + 4.3.0 ----- diff --git a/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php b/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php index 63374c50144c0..f4394fc8f6ff4 100644 --- a/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php @@ -60,6 +60,17 @@ public function testXmlHttpRequest() $this->assertFalse($client->getServerParameter('HTTP_X_REQUESTED_WITH', false)); } + public function testJsonRequest() + { + $client = $this->getBrowser(); + $client->jsonRequest('GET', 'http://example.com/', ['param' => 1], [], true); + $this->assertSame('application/json', $client->getRequest()->getServer()['CONTENT_TYPE']); + $this->assertSame('application/json', $client->getRequest()->getServer()['HTTP_ACCEPT']); + $this->assertFalse($client->getServerParameter('CONTENT_TYPE', false)); + $this->assertFalse($client->getServerParameter('HTTP_ACCEPT', false)); + $this->assertSame('{"param":1}', $client->getRequest()->getContent()); + } + public function testGetRequestWithIpAsHttpHost() { $client = $this->getBrowser(); From 281af262e6830f3bc98a4bd430911780f0da3de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20Wilczy=C5=84ski?= Date: Mon, 26 Oct 2020 19:41:01 +0100 Subject: [PATCH 005/188] [Messenger] Make all the dependencies of AmazonSqsTransport injectable --- .../Messenger/Bridge/AmazonSqs/CHANGELOG.md | 1 + .../Transport/AmazonSqsTransportTest.php | 114 ++++++++++++++++++ .../Transport/AmazonSqsTransport.php | 6 +- 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md index 03a7df3723902..59550730887ac 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Added new `debug` option to log HTTP requests and responses. + * Allowed for receiver & sender injection into AmazonSqsTransport 5.2.0 ----- diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php index 34e0bf1502888..80e53b9164926 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php @@ -11,17 +11,55 @@ namespace Symfony\Component\Messenger\Bridge\AmazonSqs\Tests\Transport; +use AsyncAws\Core\Exception\Http\HttpException; +use AsyncAws\Core\Exception\Http\ServerException; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Bridge\AmazonSqs\Tests\Fixtures\DummyMessage; +use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsReceiver; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransport; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\Connection; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\TransportException; use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface; +use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; +use Symfony\Component\Messenger\Transport\Sender\SenderInterface; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Messenger\Transport\TransportInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; class AmazonSqsTransportTest extends TestCase { + /** + * @var MockObject|Connection + */ + private $connection; + + /** + * @var MockObject|ReceiverInterface + */ + private $receiver; + + /** + * @var MockObject|SenderInterface|MessageCountAwareInterface + */ + private $sender; + + /** + * @var AmazonSqsTransport + */ + private $transport; + + protected function setUp(): void + { + $this->connection = $this->createMock(Connection::class); + // Mocking the concrete receiver class because mocking multiple interfaces is deprecated + $this->receiver = $this->createMock(AmazonSqsReceiver::class); + $this->sender = $this->createMock(SenderInterface::class); + + $this->transport = new AmazonSqsTransport($this->connection, null, $this->receiver, $this->sender); + } + public function testItIsATransport() { $transport = $this->getTransport(); @@ -58,6 +96,77 @@ public function testTransportIsAMessageCountAware() $this->assertInstanceOf(MessageCountAwareInterface::class, $transport); } + public function testItCanGetMessagesViaTheReceiver(): void + { + $envelopes = [new Envelope(new \stdClass()), new Envelope(new \stdClass())]; + $this->receiver->expects($this->once())->method('get')->willReturn($envelopes); + $this->assertSame($envelopes, $this->transport->get()); + } + + public function testItCanAcknowledgeAMessageViaTheReceiver(): void + { + $envelope = new Envelope(new \stdClass()); + $this->receiver->expects($this->once())->method('ack')->with($envelope); + $this->transport->ack($envelope); + } + + public function testItCanRejectAMessageViaTheReceiver(): void + { + $envelope = new Envelope(new \stdClass()); + $this->receiver->expects($this->once())->method('reject')->with($envelope); + $this->transport->reject($envelope); + } + + public function testItCanGetMessageCountViaTheReceiver(): void + { + $messageCount = 15; + $this->receiver->expects($this->once())->method('getMessageCount')->willReturn($messageCount); + $this->assertSame($messageCount, $this->transport->getMessageCount()); + } + + public function testItCanSendAMessageViaTheSender(): void + { + $envelope = new Envelope(new \stdClass()); + $this->sender->expects($this->once())->method('send')->with($envelope)->willReturn($envelope); + $this->assertSame($envelope, $this->transport->send($envelope)); + } + + public function testItCanSetUpTheConnection(): void + { + $this->connection->expects($this->once())->method('setup'); + $this->transport->setup(); + } + + public function testItConvertsHttpExceptionDuringSetupIntoTransportException(): void + { + $this->connection + ->expects($this->once()) + ->method('setup') + ->willThrowException($this->createHttpException()); + + $this->expectException(TransportException::class); + + $this->transport->setup(); + } + + public function testItCanResetTheConnection(): void + { + $this->connection->expects($this->once())->method('reset'); + $this->transport->reset(); + } + + public function testItConvertsHttpExceptionDuringResetIntoTransportException(): void + { + $this->connection + ->expects($this->once()) + ->method('reset') + ->willThrowException($this->createHttpException()); + + $this->expectException(TransportException::class); + + $this->transport->reset(); + } + private function getTransport(SerializerInterface $serializer = null, Connection $connection = null) { $serializer = $serializer ?: $this->getMockBuilder(SerializerInterface::class)->getMock(); @@ -65,4 +174,9 @@ private function getTransport(SerializerInterface $serializer = null, Connection return new AmazonSqsTransport($connection, $serializer); } + + private function createHttpException(): HttpException + { + return new ServerException($this->createMock(ResponseInterface::class)); + } } diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php index cf84fce11cd9a..50c7b8ff9a7d2 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php @@ -15,6 +15,8 @@ use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\TransportException; use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface; +use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; +use Symfony\Component\Messenger\Transport\Sender\SenderInterface; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Messenger\Transport\SetupableTransportInterface; @@ -31,10 +33,12 @@ class AmazonSqsTransport implements TransportInterface, SetupableTransportInterf private $receiver; private $sender; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, SerializerInterface $serializer = null, ReceiverInterface $receiver = null, SenderInterface $sender = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); + $this->receiver = $receiver; + $this->sender = $sender; } /** From 46a8007afcdc0e83f58dcb6463124ba81e77d9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Barray?= Date: Fri, 13 Nov 2020 15:09:30 +0100 Subject: [PATCH 006/188] =?UTF-8?q?[Messenger]=C2=A0Allow=20InMemoryTransp?= =?UTF-8?q?ort=20to=20serialize=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Symfony/Component/Messenger/CHANGELOG.md | 5 ++ .../InMemoryTransportFactoryTest.php | 31 +++++++ .../Tests/Transport/InMemoryTransportTest.php | 86 +++++++++++++++++++ .../Messenger/Transport/InMemoryTransport.php | 57 ++++++++++-- .../Transport/InMemoryTransportFactory.php | 16 +++- 5 files changed, 186 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 814e1625dc6a5..3cf43252be713 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* `InMemoryTransport` can perform message serialization through dsn `in-memory://?serialize=true`. + 5.2.0 ----- diff --git a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php index 6fe95025cd583..adb089efaa533 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php @@ -49,6 +49,35 @@ public function testCreateTransport() $this->assertInstanceOf(InMemoryTransport::class, $this->factory->createTransport('in-memory://', [], $serializer)); } + public function testCreateTransportWithoutSerializer() + { + /** @var SerializerInterface $serializer */ + $serializer = $this->createMock(SerializerInterface::class); + $serializer + ->expects($this->never()) + ->method('encode') + ; + $transport = $this->factory->createTransport('in-memory://?serialize=false', [], $serializer); + $message = Envelope::wrap(new DummyMessage('Hello.')); + $transport->send($message); + + $this->assertSame([$message], $transport->get()); + } + + public function testCreateTransportWithSerializer() + { + /** @var SerializerInterface $serializer */ + $serializer = $this->createMock(SerializerInterface::class); + $message = Envelope::wrap(new DummyMessage('Hello.')); + $serializer + ->expects($this->once()) + ->method('encode') + ->with($this->equalTo($message)) + ; + $transport = $this->factory->createTransport('in-memory://?serialize=true', [], $serializer); + $transport->send($message); + } + public function testResetCreatedTransports() { $transport = $this->factory->createTransport('in-memory://', [], $this->createMock(SerializerInterface::class)); @@ -63,6 +92,8 @@ public function provideDSN(): array { return [ 'Supported' => ['in-memory://foo'], + 'Serialize enabled' => ['in-memory://?serialize=true'], + 'Serialize disabled' => ['in-memory://?serialize=false'], 'Unsupported' => ['amqp://bar', false], ]; } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php index 6fddc3fbbc3e5..733eeb97714c7 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php @@ -14,7 +14,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Tests\Fixtures\AnEnvelopeStamp; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Transport\InMemoryTransport; +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; /** * @author Gary PEGEOT @@ -26,9 +28,21 @@ class InMemoryTransportTest extends TestCase */ private $transport; + /** + * @var InMemoryTransport + */ + private $serializeTransport; + + /** + * @var SerializerInterface + */ + private $serializer; + protected function setUp(): void { + $this->serializer = $this->createMock(SerializerInterface::class); $this->transport = new InMemoryTransport(); + $this->serializeTransport = new InMemoryTransport($this->serializer); } public function testSend() @@ -38,6 +52,24 @@ public function testSend() $this->assertSame([$envelope], $this->transport->getSent()); } + public function testSendWithSerialization() + { + $envelope = new Envelope(new \stdClass()); + $envelopeDecoded = Envelope::wrap(new DummyMessage('Hello.')); + $this->serializer + ->method('encode') + ->with($this->equalTo($envelope)) + ->willReturn(['foo' => 'ba']) + ; + $this->serializer + ->method('decode') + ->with(['foo' => 'ba']) + ->willReturn($envelopeDecoded) + ; + $this->serializeTransport->send($envelope); + $this->assertSame([$envelopeDecoded], $this->serializeTransport->getSent()); + } + public function testQueue() { $envelope1 = new Envelope(new \stdClass()); @@ -51,6 +83,24 @@ public function testQueue() $this->assertSame([], $this->transport->get()); } + public function testQueueWithSerialization() + { + $envelope = new Envelope(new \stdClass()); + $envelopeDecoded = Envelope::wrap(new DummyMessage('Hello.')); + $this->serializer + ->method('encode') + ->with($this->equalTo($envelope)) + ->willReturn(['foo' => 'ba']) + ; + $this->serializer + ->method('decode') + ->with(['foo' => 'ba']) + ->willReturn($envelopeDecoded) + ; + $this->serializeTransport->send($envelope); + $this->assertSame([$envelopeDecoded], $this->serializeTransport->get()); + } + public function testAcknowledgeSameMessageWithDifferentStamps() { $envelope1 = new Envelope(new \stdClass(), [new AnEnvelopeStamp()]); @@ -71,6 +121,24 @@ public function testAck() $this->assertSame([$envelope], $this->transport->getAcknowledged()); } + public function testAckWithSerialization() + { + $envelope = new Envelope(new \stdClass()); + $envelopeDecoded = Envelope::wrap(new DummyMessage('Hello.')); + $this->serializer + ->method('encode') + ->with($this->equalTo($envelope)) + ->willReturn(['foo' => 'ba']) + ; + $this->serializer + ->method('decode') + ->with(['foo' => 'ba']) + ->willReturn($envelopeDecoded) + ; + $this->serializeTransport->ack($envelope); + $this->assertSame([$envelopeDecoded], $this->serializeTransport->getAcknowledged()); + } + public function testReject() { $envelope = new Envelope(new \stdClass()); @@ -78,6 +146,24 @@ public function testReject() $this->assertSame([$envelope], $this->transport->getRejected()); } + public function testRejectWithSerialization() + { + $envelope = new Envelope(new \stdClass()); + $envelopeDecoded = Envelope::wrap(new DummyMessage('Hello.')); + $this->serializer + ->method('encode') + ->with($this->equalTo($envelope)) + ->willReturn(['foo' => 'ba']) + ; + $this->serializer + ->method('decode') + ->with(['foo' => 'ba']) + ->willReturn($envelopeDecoded) + ; + $this->serializeTransport->reject($envelope); + $this->assertSame([$envelopeDecoded], $this->serializeTransport->getRejected()); + } + public function testReset() { $envelope = new Envelope(new \stdClass()); diff --git a/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php b/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php index 09cbb31a041fd..75a0b445e4759 100644 --- a/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php +++ b/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Transport; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Contracts\Service\ResetInterface; /** @@ -41,12 +42,22 @@ class InMemoryTransport implements TransportInterface, ResetInterface */ private $queue = []; + /** + * @var SerializerInterface|null + */ + private $serializer; + + public function __construct(SerializerInterface $serializer = null) + { + $this->serializer = $serializer; + } + /** * {@inheritdoc} */ public function get(): iterable { - return array_values($this->queue); + return array_values($this->decode($this->queue)); } /** @@ -54,7 +65,7 @@ public function get(): iterable */ public function ack(Envelope $envelope): void { - $this->acknowledged[] = $envelope; + $this->acknowledged[] = $this->encode($envelope); $id = spl_object_hash($envelope->getMessage()); unset($this->queue[$id]); } @@ -64,7 +75,7 @@ public function ack(Envelope $envelope): void */ public function reject(Envelope $envelope): void { - $this->rejected[] = $envelope; + $this->rejected[] = $this->encode($envelope); $id = spl_object_hash($envelope->getMessage()); unset($this->queue[$id]); } @@ -74,9 +85,10 @@ public function reject(Envelope $envelope): void */ public function send(Envelope $envelope): Envelope { - $this->sent[] = $envelope; + $encodedEnvelope = $this->encode($envelope); + $this->sent[] = $encodedEnvelope; $id = spl_object_hash($envelope->getMessage()); - $this->queue[$id] = $envelope; + $this->queue[$id] = $encodedEnvelope; return $envelope; } @@ -91,7 +103,7 @@ public function reset() */ public function getAcknowledged(): array { - return $this->acknowledged; + return $this->decode($this->acknowledged); } /** @@ -99,7 +111,7 @@ public function getAcknowledged(): array */ public function getRejected(): array { - return $this->rejected; + return $this->decode($this->rejected); } /** @@ -107,6 +119,35 @@ public function getRejected(): array */ public function getSent(): array { - return $this->sent; + return $this->decode($this->sent); + } + + /** + * @return Envelope|array + */ + private function encode(Envelope $envelope) + { + if (null === $this->serializer) { + return $envelope; + } + + return $this->serializer->encode($envelope); + } + + /** + * @param array $messagesEncoded + * + * @return Envelope[] + */ + private function decode(array $messagesEncoded): array + { + if (null === $this->serializer) { + return $messagesEncoded; + } + + return array_map( + [$this->serializer, 'decode'], + $messagesEncoded + ); } } diff --git a/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php b/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php index 597107341a977..5da5d5d046945 100644 --- a/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php @@ -26,7 +26,9 @@ class InMemoryTransportFactory implements TransportFactoryInterface, ResetInterf public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface { - return $this->createdTransports[] = new InMemoryTransport(); + ['serialize' => $serialize] = $this->parseDsn($dsn); + + return $this->createdTransports[] = new InMemoryTransport($serialize ? $serializer : null); } public function supports(string $dsn, array $options): bool @@ -40,4 +42,16 @@ public function reset() $transport->reset(); } } + + private function parseDsn(string $dsn): array + { + $query = []; + if ($queryAsString = strstr($dsn, '?')) { + parse_str(ltrim($queryAsString, '?'), $query); + } + + return [ + 'serialize' => filter_var($query['serialize'] ?? false, \FILTER_VALIDATE_BOOLEAN), + ]; + } } From d3b944046ad059f1aad09427cee39080f8ad6872 Mon Sep 17 00:00:00 2001 From: Karl Shea Date: Sun, 8 Nov 2020 12:53:11 -0600 Subject: [PATCH 007/188] [Ldap] Ldap Entry case-sensitive attribute key option --- src/Symfony/Component/Ldap/CHANGELOG.md | 5 ++ src/Symfony/Component/Ldap/Entry.php | 54 ++++++++++++++++--- .../Component/Ldap/Tests/EntryTest.php | 42 +++++++++++++++ 3 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Component/Ldap/Tests/EntryTest.php diff --git a/src/Symfony/Component/Ldap/CHANGELOG.md b/src/Symfony/Component/Ldap/CHANGELOG.md index f54a3e824184e..b232f657dbc78 100644 --- a/src/Symfony/Component/Ldap/CHANGELOG.md +++ b/src/Symfony/Component/Ldap/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* Added caseSensitive option for attribute keys in the Entry class. + 5.1.0 ----- diff --git a/src/Symfony/Component/Ldap/Entry.php b/src/Symfony/Component/Ldap/Entry.php index da3b8bb74ef41..60253f69a9956 100644 --- a/src/Symfony/Component/Ldap/Entry.php +++ b/src/Symfony/Component/Ldap/Entry.php @@ -13,16 +13,23 @@ /** * @author Charles Sarrazin + * @author Karl Shea */ class Entry { private $dn; private $attributes; + private $lowerMap; public function __construct(string $dn, array $attributes = []) { $this->dn = $dn; - $this->attributes = $attributes; + $this->attributes = []; + $this->lowerMap = []; + + foreach ($attributes as $key => $attribute) { + $this->setAttribute($key, $attribute); + } } /** @@ -38,13 +45,21 @@ public function getDn() /** * Returns whether an attribute exists. * - * @param string $name The name of the attribute + * @param string $name The name of the attribute + * @param bool $caseSensitive Whether the check should be case-sensitive * * @return bool */ - public function hasAttribute(string $name) + public function hasAttribute(string $name/* , bool $caseSensitive = true */) { - return isset($this->attributes[$name]); + $caseSensitive = 2 > \func_num_args() || true === func_get_arg(1); + $attributeKey = $this->getAttributeKey($name, $caseSensitive); + + if (null === $attributeKey) { + return false; + } + + return isset($this->attributes[$attributeKey]); } /** @@ -53,13 +68,21 @@ public function hasAttribute(string $name) * As LDAP can return multiple values for a single attribute, * this value is returned as an array. * - * @param string $name The name of the attribute + * @param string $name The name of the attribute + * @param bool $caseSensitive Whether the attribute name is case-sensitive * * @return array|null */ - public function getAttribute(string $name) + public function getAttribute(string $name/* , bool $caseSensitive = true */) { - return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + $caseSensitive = 2 > \func_num_args() || true === func_get_arg(1); + $attributeKey = $this->getAttributeKey($name, $caseSensitive); + + if (null === $attributeKey) { + return null; + } + + return $this->attributes[$attributeKey] ?? null; } /** @@ -78,6 +101,7 @@ public function getAttributes() public function setAttribute(string $name, array $value) { $this->attributes[$name] = $value; + $this->lowerMap[strtolower($name)] = $name; } /** @@ -86,5 +110,21 @@ public function setAttribute(string $name, array $value) public function removeAttribute(string $name) { unset($this->attributes[$name]); + unset($this->lowerMap[strtolower($name)]); + } + + /** + * Get the attribute key. + * + * @param string $name The attribute name + * @param bool $caseSensitive Whether the attribute name is case-sensitive + */ + private function getAttributeKey(string $name, bool $caseSensitive = true): ?string + { + if ($caseSensitive) { + return $name; + } + + return $this->lowerMap[strtolower($name)] ?? null; } } diff --git a/src/Symfony/Component/Ldap/Tests/EntryTest.php b/src/Symfony/Component/Ldap/Tests/EntryTest.php new file mode 100644 index 0000000000000..7185db1040281 --- /dev/null +++ b/src/Symfony/Component/Ldap/Tests/EntryTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Ldap\Entry; + +class EntryTest extends TestCase +{ + public function testCaseSensitiveAttributeAccessors() + { + $mail = 'fabpot@symfony.com'; + $givenName = 'Fabien Potencier'; + + $entry = new Entry('cn=fabpot,dc=symfony,dc=com', [ + 'mail' => [$mail], + 'givenName' => [$givenName], + ]); + + $this->assertFalse($entry->hasAttribute('givenname')); + $this->assertTrue($entry->hasAttribute('givenname', false)); + + $this->assertNull($entry->getAttribute('givenname')); + $this->assertSame($givenName, $entry->getAttribute('givenname', false)[0]); + + $firstName = 'Fabien'; + + $entry->setAttribute('firstName', [$firstName]); + $this->assertSame($firstName, $entry->getAttribute('firstname', false)[0]); + $entry->removeAttribute('firstName'); + $this->assertFalse($entry->hasAttribute('firstname', false)); + } +} From f0bbdc8d722eca3782bc43e4cb5889ce5f29c3bb Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Tue, 3 Nov 2020 17:31:16 +0100 Subject: [PATCH 008/188] [Console][Yaml] Linter: add Github annotations format for errors --- src/Symfony/Component/Console/CHANGELOG.md | 5 + .../Console/CI/GithubActionReporter.php | 99 +++++++++++++++++++ .../Tests/CI/GithubActionReporterTest.php | 81 +++++++++++++++ src/Symfony/Component/Yaml/CHANGELOG.md | 6 ++ .../Component/Yaml/Command/LintCommand.php | 25 ++++- .../Yaml/Tests/Command/LintCommandTest.php | 52 ++++++++++ 6 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Console/CI/GithubActionReporter.php create mode 100644 src/Symfony/Component/Console/Tests/CI/GithubActionReporterTest.php diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index c5a69637e1ba8..afa00450d233c 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * Added `GithubActionReporter` to render annotations in a Github Action + 5.2.0 ----- diff --git a/src/Symfony/Component/Console/CI/GithubActionReporter.php b/src/Symfony/Component/Console/CI/GithubActionReporter.php new file mode 100644 index 0000000000000..0ae18ca15e8a0 --- /dev/null +++ b/src/Symfony/Component/Console/CI/GithubActionReporter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CI; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Utility class for Github actions. + * + * @author Maxime Steinhausser + */ +class GithubActionReporter +{ + private $output; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 + */ + private const ESCAPED_DATA = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ]; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94 + */ + private const ESCAPED_PROPERTIES = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ':' => '%3A', + ',' => '%2C', + ]; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + public static function isGithubActionEnvironment(): bool + { + return false !== getenv('GITHUB_ACTIONS'); + } + + /** + * Output an error using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + */ + public function error(string $message, string $file = null, int $line = null, int $col = null): void + { + $this->log('error', $message, $file, $line, $col); + } + + /** + * Output a warning using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message + */ + public function warning(string $message, string $file = null, int $line = null, int $col = null): void + { + $this->log('warning', $message, $file, $line, $col); + } + + /** + * Output a debug log using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message + */ + public function debug(string $message, string $file = null, int $line = null, int $col = null): void + { + $this->log('debug', $message, $file, $line, $col); + } + + private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void + { + // Some values must be encoded. + $message = strtr($message, self::ESCAPED_DATA); + + if (!$file) { + // No file provided, output the message solely: + $this->output->writeln(sprintf('::%s::%s', $type, $message)); + + return; + } + + $this->output->writeln(sprintf('::%s file=%s, line=%s, col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message)); + } +} diff --git a/src/Symfony/Component/Console/Tests/CI/GithubActionReporterTest.php b/src/Symfony/Component/Console/Tests/CI/GithubActionReporterTest.php new file mode 100644 index 0000000000000..4325508399113 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/CI/GithubActionReporterTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\CI; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Output\BufferedOutput; + +class GithubActionReporterTest extends TestCase +{ + public function testIsGithubActionEnvironment() + { + $prev = getenv('GITHUB_ACTIONS'); + putenv('GITHUB_ACTIONS'); + + try { + self::assertFalse(GithubActionReporter::isGithubActionEnvironment()); + putenv('GITHUB_ACTIONS=1'); + self::assertTrue(GithubActionReporter::isGithubActionEnvironment()); + } finally { + putenv('GITHUB_ACTIONS'.($prev ? "=$prev" : '')); + } + } + + /** + * @dataProvider annotationsFormatProvider + */ + public function testAnnotationsFormat(string $type, string $message, string $file = null, int $line = null, int $col = null, string $expected) + { + $reporter = new GithubActionReporter($buffer = new BufferedOutput()); + + $reporter->{$type}($message, $file, $line, $col); + + self::assertSame($expected.\PHP_EOL, $buffer->fetch()); + } + + public function annotationsFormatProvider(): iterable + { + yield 'warning' => ['warning', 'A warning', null, null, null, '::warning::A warning']; + yield 'error' => ['error', 'An error', null, null, null, '::error::An error']; + yield 'debug' => ['debug', 'A debug log', null, null, null, '::debug::A debug log']; + + yield 'with message to escape' => [ + 'debug', + "There are 100% chances\nfor this to be escaped properly\rRight?", + null, + null, + null, + '::debug::There are 100%25 chances%0Afor this to be escaped properly%0DRight?', + ]; + + yield 'with meta' => [ + 'warning', + 'A warning', + 'foo/bar.php', + 2, + 4, + '::warning file=foo/bar.php, line=2, col=4::A warning', + ]; + + yield 'with file property to escape' => [ + 'warning', + 'A warning', + 'foo,bar:baz%quz.php', + 2, + 4, + '::warning file=foo%2Cbar%3Abaz%25quz.php, line=2, col=4::A warning', + ]; + + yield 'without file ignores col & line' => ['warning', 'A warning', null, 2, 4, '::warning::A warning']; + } +} diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index d4f2b5d781fc6..baabf8a756853 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +5.3.0 +----- + + * Added `github` format support & autodetection to render errors as annotations + when running the YAML linter command in a Github Action environment. + 5.1.0 ----- diff --git a/src/Symfony/Component/Yaml/Command/LintCommand.php b/src/Symfony/Component/Yaml/Command/LintCommand.php index 83f36a93839d2..94a84b754d213 100644 --- a/src/Symfony/Component/Yaml/Command/LintCommand.php +++ b/src/Symfony/Component/Yaml/Command/LintCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Yaml\Command; +use Symfony\Component\Console\CI\GithubActionReporter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; @@ -55,7 +56,7 @@ protected function configure() $this ->setDescription('Lints a file and outputs encountered errors') ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') ->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags') ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT @@ -84,6 +85,16 @@ protected function execute(InputInterface $input, OutputInterface $output) $io = new SymfonyStyle($input, $output); $filenames = (array) $input->getArgument('filename'); $this->format = $input->getOption('format'); + + if ('github' === $this->format && !class_exists(GithubActionReporter::class)) { + throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.'); + } + + if (null === $this->format) { + // Autodetect format according to CI environment + $this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; + } + $this->displayCorrectFiles = $output->isVerbose(); $flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0; @@ -137,17 +148,23 @@ private function display(SymfonyStyle $io, array $files): int return $this->displayTxt($io, $files); case 'json': return $this->displayJson($io, $files); + case 'github': + return $this->displayTxt($io, $files, true); default: throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); } } - private function displayTxt(SymfonyStyle $io, array $filesInfo): int + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int { $countFiles = \count($filesInfo); $erroredFiles = 0; $suggestTagOption = false; + if ($errorAsGithubAnnotations) { + $githubReporter = new GithubActionReporter($io); + } + foreach ($filesInfo as $info) { if ($info['valid'] && $this->displayCorrectFiles) { $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); @@ -159,6 +176,10 @@ private function displayTxt(SymfonyStyle $io, array $filesInfo): int if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) { $suggestTagOption = true; } + + if ($errorAsGithubAnnotations) { + $githubReporter->error($info['message'], $info['file'] ?? 'php://stdin', $info['line']); + } } } diff --git a/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php b/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php index 32dd30d495a84..6060b8fcb5518 100644 --- a/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Application; +use Symfony\Component\Console\CI\GithubActionReporter; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Yaml\Command\LintCommand; @@ -63,6 +64,57 @@ public function testLintIncorrectFile() $this->assertStringContainsString('Unable to parse at line 3 (near "bar").', trim($tester->getDisplay())); } + public function testLintIncorrectFileWithGithubFormat() + { + if (!class_exists(GithubActionReporter::class)) { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "github" format is only available since "symfony/console" >= 5.3.'); + } + + $incorrectContent = <<createCommandTester(); + $filename = $this->createFile($incorrectContent); + + $tester->execute(['filename' => $filename, '--format' => 'github'], ['decorated' => false]); + + if (!class_exists(GithubActionReporter::class)) { + return; + } + + self::assertEquals(1, $tester->getStatusCode(), 'Returns 1 in case of error'); + self::assertStringMatchesFormat('%A::error file=%s, line=2, col=0::Unable to parse at line 2 (near "bar")%A', trim($tester->getDisplay())); + } + + public function testLintAutodetectsGithubActionEnvironment() + { + if (!class_exists(GithubActionReporter::class)) { + $this->markTestSkipped('The "github" format is only available since "symfony/console" >= 5.3.'); + } + + $prev = getenv('GITHUB_ACTIONS'); + putenv('GITHUB_ACTIONS'); + + try { + putenv('GITHUB_ACTIONS=1'); + + $incorrectContent = <<createCommandTester(); + $filename = $this->createFile($incorrectContent); + + $tester->execute(['filename' => $filename], ['decorated' => false]); + + self::assertStringMatchesFormat('%A::error file=%s, line=2, col=0::Unable to parse at line 2 (near "bar")%A', trim($tester->getDisplay())); + } finally { + putenv('GITHUB_ACTIONS'.($prev ? "=$prev" : '')); + } + } + public function testConstantAsKey() { $yaml = << Date: Mon, 23 Nov 2020 16:56:49 +0100 Subject: [PATCH 009/188] Added Invalid constant into Command Class --- src/Symfony/Component/Console/Command/Command.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index e5d4cf18a1f0e..d07d8b137430b 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -31,6 +31,7 @@ class Command { public const SUCCESS = 0; public const FAILURE = 1; + public const INVALID = 2; /** * @var string|null The default command name From 3ac26fcd670beee6583957bd0b2e4727078cf3eb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 27 Nov 2020 07:41:41 +0100 Subject: [PATCH 010/188] Remove extra docblock --- src/Symfony/Component/Ldap/Entry.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Symfony/Component/Ldap/Entry.php b/src/Symfony/Component/Ldap/Entry.php index 60253f69a9956..71b675aa7006c 100644 --- a/src/Symfony/Component/Ldap/Entry.php +++ b/src/Symfony/Component/Ldap/Entry.php @@ -113,12 +113,6 @@ public function removeAttribute(string $name) unset($this->lowerMap[strtolower($name)]); } - /** - * Get the attribute key. - * - * @param string $name The attribute name - * @param bool $caseSensitive Whether the attribute name is case-sensitive - */ private function getAttributeKey(string $name, bool $caseSensitive = true): ?string { if ($caseSensitive) { From 4c74dead48752058ba4e133ac1775a52d98a63c4 Mon Sep 17 00:00:00 2001 From: Simon Berger Date: Mon, 16 Nov 2020 20:09:52 +0100 Subject: [PATCH 011/188] Cache discovered namespaces in DomCrawler --- src/Symfony/Component/DomCrawler/Crawler.php | 25 ++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 3627aad5a10c1..b6ccda13726cb 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -33,6 +33,11 @@ class Crawler implements \Countable, \IteratorAggregate */ private $namespaces = []; + /** + * @var \ArrayIterator A map of cached namespaces + */ + private $cachedNamespaces; + /** * @var string The base href value */ @@ -68,6 +73,7 @@ public function __construct($node = null, string $uri = null, string $baseHref = $this->uri = $uri; $this->baseHref = $baseHref ?: $uri; $this->html5Parser = class_exists(HTML5::class) ? new HTML5(['disable_html_ns' => true]) : null; + $this->cachedNamespaces = new \ArrayIterator(); $this->add($node); } @@ -99,6 +105,7 @@ public function clear() { $this->nodes = []; $this->document = null; + $this->cachedNamespaces = new \ArrayIterator(); } /** @@ -967,12 +974,14 @@ public static function xpathLiteral(string $s) */ private function filterRelativeXPath(string $xpath): object { - $prefixes = $this->findNamespacePrefixes($xpath); - $crawler = $this->createSubCrawler(null); + if (null === $this->document) { + return $crawler; + } + + $domxpath = $this->createDOMXPath($this->document, $this->findNamespacePrefixes($xpath)); foreach ($this->nodes as $node) { - $domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes); $crawler->add($domxpath->query($xpath, $node)); } @@ -1189,10 +1198,17 @@ private function discoverNamespace(\DOMXPath $domxpath, string $prefix): ?string return $this->namespaces[$prefix]; } + if ($this->cachedNamespaces->offsetExists($prefix)) { + return $this->cachedNamespaces->offsetGet($prefix); + } + // ask for one namespace, otherwise we'd get a collection with an item for each node $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); - return ($node = $namespaces->item(0)) ? $node->nodeValue : null; + $namespace = ($node = $namespaces->item(0)) ? $node->nodeValue : null; + $this->cachedNamespaces->offsetSet($prefix, $namespace); + + return $namespace; } private function findNamespacePrefixes(string $xpath): array @@ -1217,6 +1233,7 @@ private function createSubCrawler($nodes): object $crawler->isHtml = $this->isHtml; $crawler->document = $this->document; $crawler->namespaces = $this->namespaces; + $crawler->cachedNamespaces = $this->cachedNamespaces; $crawler->html5Parser = $this->html5Parser; return $crawler; From a8e85ecbbd52bc2aaa50d58daf02afb8b4764e9d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 27 Nov 2020 08:00:10 +0100 Subject: [PATCH 012/188] Make some CS changes --- src/Symfony/Component/DomCrawler/Crawler.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index b6ccda13726cb..2b4e1dfaaaad9 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -34,7 +34,7 @@ class Crawler implements \Countable, \IteratorAggregate private $namespaces = []; /** - * @var \ArrayIterator A map of cached namespaces + * @var \ArrayObject A map of cached namespaces */ private $cachedNamespaces; @@ -73,7 +73,7 @@ public function __construct($node = null, string $uri = null, string $baseHref = $this->uri = $uri; $this->baseHref = $baseHref ?: $uri; $this->html5Parser = class_exists(HTML5::class) ? new HTML5(['disable_html_ns' => true]) : null; - $this->cachedNamespaces = new \ArrayIterator(); + $this->cachedNamespaces = new \ArrayObject(); $this->add($node); } @@ -105,7 +105,7 @@ public function clear() { $this->nodes = []; $this->document = null; - $this->cachedNamespaces = new \ArrayIterator(); + $this->cachedNamespaces = new \ArrayObject(); } /** @@ -1198,17 +1198,14 @@ private function discoverNamespace(\DOMXPath $domxpath, string $prefix): ?string return $this->namespaces[$prefix]; } - if ($this->cachedNamespaces->offsetExists($prefix)) { - return $this->cachedNamespaces->offsetGet($prefix); + if (isset($this->cachedNamespaces[$prefix])) { + return $this->cachedNamespaces[$prefix]; } // ask for one namespace, otherwise we'd get a collection with an item for each node $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); - $namespace = ($node = $namespaces->item(0)) ? $node->nodeValue : null; - $this->cachedNamespaces->offsetSet($prefix, $namespace); - - return $namespace; + return $this->cachedNamespaces[$prefix] = ($node = $namespaces->item(0)) ? $node->nodeValue : null; } private function findNamespacePrefixes(string $xpath): array From 6687e23c7d49eb9f873e93a7bd02978913b9a86e Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 1 Dec 2020 06:41:25 +0200 Subject: [PATCH 013/188] Add ContextBlock for slack notifier --- .../Bridge/Slack/Block/SlackContextBlock.php | 66 +++++++++++++++++++ .../Tests/Block/SlackContextBlockTest.php | 66 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackContextBlockTest.php diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php new file mode 100644 index 0000000000000..8c6434321a14d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Notifier\Bridge\Slack\Block; + +final class SlackContextBlock extends AbstractSlackBlock +{ + private const ELEMENT_LIMIT = 10; + + public function __construct() + { + $this->options['type'] = 'context'; + } + + public function text(string $text, bool $markdown = true, bool $emoji = true, bool $verbatim = false): self + { + if (self::ELEMENT_LIMIT === \count($this->options['elements'] ?? [])) { + throw new \LogicException(sprintf('Maximum number of elements should not exceed %d.', self::ELEMENT_LIMIT)); + } + + $element = [ + 'type' => $markdown ? 'mrkdwn' : 'plain_text', + 'text' => $text, + ]; + if ($markdown) { + $element['verbatim'] = $verbatim; + } else { + $element['emoji'] = $emoji; + } + $this->options['elements'][] = $element; + + return $this; + } + + public function image(string $url, string $text): self + { + if (self::ELEMENT_LIMIT === \count($this->options['elements'] ?? [])) { + throw new \LogicException(sprintf('Maximum number of elements should not exceed %d.', self::ELEMENT_LIMIT)); + } + + $this->options['elements'][] = [ + 'type' => 'image', + 'image_url' => $url, + 'alt_text' => $text, + ]; + + return $this; + } + + public function id(string $id): self + { + $this->options['block_id'] = $id; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackContextBlockTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackContextBlockTest.php new file mode 100644 index 0000000000000..2df45a4907ed1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackContextBlockTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Slack\Tests\Block; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\Slack\Block\SlackContextBlock; + +final class SlackContextBlockTest extends TestCase +{ + public function testCanBeInstantiated(): void + { + $context = new SlackContextBlock(); + $context->text('context text without emoji', false, false); + $context->text('context text verbatim', true, false, true); + $context->image('https://example.com/image.jpg', 'an image'); + $context->id('context_id'); + + $this->assertSame([ + 'type' => 'context', + 'elements' => [ + [ + 'type' => 'plain_text', + 'text' => 'context text without emoji', + 'emoji' => false, + ], + [ + 'type' => 'mrkdwn', + 'text' => 'context text verbatim', + 'verbatim' => true, + ], + [ + 'type' => 'image', + 'image_url' => 'https://example.com/image.jpg', + 'alt_text' => 'an image', + ], + ], + 'block_id' => 'context_id', + ], $context->toArray()); + } + + public function testThrowsWhenElementsLimitReached(): void + { + $context = new SlackContextBlock(); + for ($i = 0; $i < 10; ++$i) { + if (0 === $i % 2) { + $context->text($i); + } else { + $context->image($i, $i); + } + } + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Maximum number of elements should not exceed 10.'); + + $context->text('fail'); + } +} From a7936d2b0e4be96392100b24aa5d2bd4de9ab5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20Schlu=CC=88ter?= Date: Thu, 3 Dec 2020 12:10:35 +0100 Subject: [PATCH 014/188] [Notifier] Check for maximum number of buttons in slack action block --- .../Bridge/Slack/Block/SlackActionsBlock.php | 4 ++ .../Notifier/Bridge/Slack/CHANGELOG.md | 5 ++ .../Tests/Block/SlackActionsBlockTest.php | 62 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackActionsBlockTest.php diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php index 85b63d2109d71..25c4c06338bea 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php @@ -26,6 +26,10 @@ public function __construct() */ public function button(string $text, string $url, string $style = null): self { + if (25 === \count($this->options['elements'] ?? [])) { + throw new \LogicException('Maximum number of buttons should not exceed 25.'); + } + $element = [ 'type' => 'button', 'text' => [ diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md index fcc1eb79f8319..b860fa08db7f4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * Check for maximum number of buttons in Slack action block + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackActionsBlockTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackActionsBlockTest.php new file mode 100644 index 0000000000000..fba5ccf7c5c71 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackActionsBlockTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Slack\Tests\Block; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\Slack\Block\SlackActionsBlock; + +final class SlackActionsBlockTest extends TestCase +{ + public function testCanBeInstantiated(): void + { + $actions = new SlackActionsBlock(); + $actions->button('first button text', 'https://example.org') + ->button('second button text', 'https://example.org/slack', 'danger') + ; + + $this->assertSame([ + 'type' => 'actions', + 'elements' => [ + [ + 'type' => 'button', + 'text' => [ + 'type' => 'plain_text', + 'text' => 'first button text', + ], + 'url' => 'https://example.org', + ], + [ + 'type' => 'button', + 'text' => [ + 'type' => 'plain_text', + 'text' => 'second button text', + ], + 'url' => 'https://example.org/slack', + 'style' => 'danger', + ], + ], + ], $actions->toArray()); + } + + public function testThrowsWhenFieldsLimitReached(): void + { + $section = new SlackActionsBlock(); + for ($i = 0; $i < 25; ++$i) { + $section->button($i, $i); + } + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Maximum number of buttons should not exceed 25.'); + + $section->button('fail', 'fail'); + } +} From 4dd16d11eb3af27a7e0d23ec0a8b2f3ae33faa2e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Dec 2020 07:31:47 +0100 Subject: [PATCH 015/188] Fix CS --- .../Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php index 8c6434321a14d..e3ece805e2421 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackContextBlock.php @@ -9,8 +9,6 @@ * file that was distributed with this source code. */ -declare(strict_types=1); - namespace Symfony\Component\Notifier\Bridge\Slack\Block; final class SlackContextBlock extends AbstractSlackBlock From b07608c0fee7cff7abef25b5f778bff4c9c89a9e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Dec 2020 08:36:41 +0100 Subject: [PATCH 016/188] Move @experimental annotations to 5.3 --- .../Security/Factory/AuthenticatorFactoryInterface.php | 2 +- .../Security/Factory/CustomAuthenticatorFactory.php | 2 +- .../DependencyInjection/Security/Factory/LoginLinkFactory.php | 2 +- .../Bundle/SecurityBundle/Security/UserAuthenticator.php | 2 +- .../HttpFoundation/RateLimiter/AbstractRequestRateLimiter.php | 2 +- .../HttpFoundation/RateLimiter/RequestRateLimiterInterface.php | 2 +- .../Component/Notifier/Bridge/Discord/DiscordOptions.php | 2 +- .../Component/Notifier/Bridge/Discord/DiscordTransport.php | 2 +- .../Notifier/Bridge/Discord/DiscordTransportFactory.php | 2 +- .../Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php | 2 +- .../Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php | 2 +- .../Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php | 2 +- .../Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php | 2 +- .../Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php | 2 +- .../Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php | 2 +- .../Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php | 2 +- .../Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php | 2 +- .../Component/Notifier/Bridge/Esendex/EsendexTransport.php | 2 +- .../Notifier/Bridge/Esendex/EsendexTransportFactory.php | 2 +- .../Component/Notifier/Bridge/Firebase/FirebaseOptions.php | 2 +- .../Component/Notifier/Bridge/Firebase/FirebaseTransport.php | 2 +- .../Notifier/Bridge/Firebase/FirebaseTransportFactory.php | 2 +- .../Bridge/Firebase/Notification/AndroidNotification.php | 2 +- .../Notifier/Bridge/Firebase/Notification/IOSNotification.php | 2 +- .../Notifier/Bridge/Firebase/Notification/WebNotification.php | 2 +- .../Notifier/Bridge/FreeMobile/FreeMobileTransport.php | 2 +- .../Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php | 2 +- .../Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php | 2 +- .../Notifier/Bridge/GoogleChat/GoogleChatTransport.php | 2 +- .../Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php | 2 +- .../Component/Notifier/Bridge/Infobip/InfobipTransport.php | 2 +- .../Notifier/Bridge/Infobip/InfobipTransportFactory.php | 2 +- .../Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php | 2 +- .../Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php | 2 +- .../Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php | 2 +- .../Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php | 2 +- .../Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php | 2 +- .../Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php | 2 +- .../Notifier/Bridge/LinkedIn/Share/ShareContentShare.php | 2 +- .../Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php | 2 +- .../Notifier/Bridge/LinkedIn/Share/VisibilityShare.php | 2 +- .../Notifier/Bridge/Mattermost/MattermostTransport.php | 2 +- .../Notifier/Bridge/Mattermost/MattermostTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php | 2 +- src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php | 2 +- .../Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php | 2 +- .../Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php | 2 +- .../Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php | 2 +- .../Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php | 2 +- .../Component/Notifier/Bridge/RocketChat/RocketChatOptions.php | 2 +- .../Notifier/Bridge/RocketChat/RocketChatTransport.php | 2 +- .../Notifier/Bridge/RocketChat/RocketChatTransportFactory.php | 2 +- .../Notifier/Bridge/Sendinblue/SendinblueTransport.php | 2 +- .../Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php | 2 +- .../Component/Notifier/Bridge/Sinch/SinchTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php | 2 +- src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php | 2 +- .../Component/Notifier/Bridge/Slack/SlackTransportFactory.php | 2 +- .../Component/Notifier/Bridge/Smsapi/SmsapiTransport.php | 2 +- .../Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php | 2 +- .../Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php | 2 +- .../Telegram/Reply/Markup/Button/AbstractKeyboardButton.php | 2 +- .../Telegram/Reply/Markup/Button/InlineKeyboardButton.php | 2 +- .../Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php | 2 +- .../Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php | 2 +- .../Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php | 2 +- .../Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php | 2 +- .../Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php | 2 +- .../Component/Notifier/Bridge/Telegram/TelegramOptions.php | 2 +- .../Component/Notifier/Bridge/Telegram/TelegramTransport.php | 2 +- .../Notifier/Bridge/Telegram/TelegramTransportFactory.php | 2 +- .../Component/Notifier/Bridge/Twilio/TwilioTransport.php | 2 +- .../Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php | 2 +- src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php | 2 +- .../Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Channel/AbstractChannel.php | 2 +- src/Symfony/Component/Notifier/Channel/BrowserChannel.php | 2 +- src/Symfony/Component/Notifier/Channel/ChannelInterface.php | 2 +- src/Symfony/Component/Notifier/Channel/ChannelPolicy.php | 2 +- .../Component/Notifier/Channel/ChannelPolicyInterface.php | 2 +- src/Symfony/Component/Notifier/Channel/ChatChannel.php | 2 +- src/Symfony/Component/Notifier/Channel/EmailChannel.php | 2 +- src/Symfony/Component/Notifier/Channel/SmsChannel.php | 2 +- src/Symfony/Component/Notifier/Chatter.php | 2 +- src/Symfony/Component/Notifier/ChatterInterface.php | 2 +- .../Notifier/DataCollector/NotificationDataCollector.php | 2 +- src/Symfony/Component/Notifier/Event/MessageEvent.php | 2 +- src/Symfony/Component/Notifier/Event/NotificationEvents.php | 2 +- .../Notifier/EventListener/NotificationLoggerListener.php | 2 +- .../EventListener/SendFailedMessageToNotifierListener.php | 2 +- src/Symfony/Component/Notifier/Exception/ExceptionInterface.php | 2 +- .../Component/Notifier/Exception/IncompleteDsnException.php | 2 +- .../Component/Notifier/Exception/InvalidArgumentException.php | 2 +- src/Symfony/Component/Notifier/Exception/LogicException.php | 2 +- src/Symfony/Component/Notifier/Exception/RuntimeException.php | 2 +- src/Symfony/Component/Notifier/Exception/TransportException.php | 2 +- .../Notifier/Exception/TransportExceptionInterface.php | 2 +- .../Component/Notifier/Exception/UnsupportedSchemeException.php | 2 +- src/Symfony/Component/Notifier/Message/ChatMessage.php | 2 +- src/Symfony/Component/Notifier/Message/EmailMessage.php | 2 +- src/Symfony/Component/Notifier/Message/MessageInterface.php | 2 +- .../Component/Notifier/Message/MessageOptionsInterface.php | 2 +- src/Symfony/Component/Notifier/Message/NullMessage.php | 2 +- src/Symfony/Component/Notifier/Message/SentMessage.php | 2 +- src/Symfony/Component/Notifier/Message/SmsMessage.php | 2 +- src/Symfony/Component/Notifier/Messenger/MessageHandler.php | 2 +- .../Notifier/Notification/ChatNotificationInterface.php | 2 +- .../Notifier/Notification/EmailNotificationInterface.php | 2 +- src/Symfony/Component/Notifier/Notification/Notification.php | 2 +- .../Notifier/Notification/SmsNotificationInterface.php | 2 +- src/Symfony/Component/Notifier/Notifier.php | 2 +- src/Symfony/Component/Notifier/NotifierInterface.php | 2 +- .../Component/Notifier/Recipient/EmailRecipientInterface.php | 2 +- .../Component/Notifier/Recipient/EmailRecipientTrait.php | 2 +- src/Symfony/Component/Notifier/Recipient/NoRecipient.php | 2 +- src/Symfony/Component/Notifier/Recipient/Recipient.php | 2 +- src/Symfony/Component/Notifier/Recipient/RecipientInterface.php | 2 +- .../Component/Notifier/Recipient/SmsRecipientInterface.php | 2 +- src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php | 2 +- src/Symfony/Component/Notifier/Texter.php | 2 +- src/Symfony/Component/Notifier/TexterInterface.php | 2 +- src/Symfony/Component/Notifier/Transport.php | 2 +- src/Symfony/Component/Notifier/Transport/AbstractTransport.php | 2 +- .../Component/Notifier/Transport/AbstractTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Transport/Dsn.php | 2 +- src/Symfony/Component/Notifier/Transport/FailoverTransport.php | 2 +- src/Symfony/Component/Notifier/Transport/NullTransport.php | 2 +- .../Component/Notifier/Transport/NullTransportFactory.php | 2 +- .../Component/Notifier/Transport/RoundRobinTransport.php | 2 +- .../Component/Notifier/Transport/TransportFactoryInterface.php | 2 +- src/Symfony/Component/Notifier/Transport/TransportInterface.php | 2 +- src/Symfony/Component/Notifier/Transport/Transports.php | 2 +- src/Symfony/Component/RateLimiter/CompoundLimiter.php | 2 +- .../RateLimiter/Exception/InvalidIntervalException.php | 2 +- .../RateLimiter/Exception/MaxWaitDurationExceededException.php | 2 +- .../RateLimiter/Exception/RateLimitExceededException.php | 2 +- .../RateLimiter/Exception/ReserveNotSupportedException.php | 2 +- src/Symfony/Component/RateLimiter/LimiterInterface.php | 2 +- src/Symfony/Component/RateLimiter/LimiterStateInterface.php | 2 +- src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php | 2 +- src/Symfony/Component/RateLimiter/Policy/NoLimiter.php | 2 +- src/Symfony/Component/RateLimiter/Policy/Rate.php | 2 +- src/Symfony/Component/RateLimiter/Policy/ResetLimiterTrait.php | 2 +- src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php | 2 +- .../Component/RateLimiter/Policy/SlidingWindowLimiter.php | 2 +- src/Symfony/Component/RateLimiter/Policy/TokenBucket.php | 2 +- src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php | 2 +- src/Symfony/Component/RateLimiter/Policy/Window.php | 2 +- src/Symfony/Component/RateLimiter/RateLimit.php | 2 +- src/Symfony/Component/RateLimiter/RateLimiterFactory.php | 2 +- src/Symfony/Component/RateLimiter/Reservation.php | 2 +- src/Symfony/Component/RateLimiter/Storage/CacheStorage.php | 2 +- src/Symfony/Component/RateLimiter/Storage/InMemoryStorage.php | 2 +- src/Symfony/Component/RateLimiter/Storage/StorageInterface.php | 2 +- .../Security/Http/Authentication/AuthenticatorManager.php | 2 +- .../Http/Authentication/AuthenticatorManagerInterface.php | 2 +- .../Security/Http/Authentication/UserAuthenticatorInterface.php | 2 +- .../Security/Http/Authenticator/AbstractAuthenticator.php | 2 +- .../Http/Authenticator/AbstractLoginFormAuthenticator.php | 2 +- .../Authenticator/AbstractPreAuthenticatedAuthenticator.php | 2 +- .../Security/Http/Authenticator/AuthenticatorInterface.php | 2 +- .../Security/Http/Authenticator/FormLoginAuthenticator.php | 2 +- .../Security/Http/Authenticator/HttpBasicAuthenticator.php | 2 +- .../Security/Http/Authenticator/JsonLoginAuthenticator.php | 2 +- .../Security/Http/Authenticator/LoginLinkAuthenticator.php | 2 +- .../Http/Authenticator/Passport/Badge/BadgeInterface.php | 2 +- .../Http/Authenticator/Passport/Badge/CsrfTokenBadge.php | 2 +- .../Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php | 2 +- .../Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php | 2 +- .../Http/Authenticator/Passport/Badge/RememberMeBadge.php | 2 +- .../Security/Http/Authenticator/Passport/Badge/UserBadge.php | 2 +- .../Authenticator/Passport/Credentials/CredentialsInterface.php | 2 +- .../Authenticator/Passport/Credentials/CustomCredentials.php | 2 +- .../Authenticator/Passport/Credentials/PasswordCredentials.php | 2 +- .../Component/Security/Http/Authenticator/Passport/Passport.php | 2 +- .../Security/Http/Authenticator/Passport/PassportInterface.php | 2 +- .../Security/Http/Authenticator/Passport/PassportTrait.php | 2 +- .../Http/Authenticator/Passport/SelfValidatingPassport.php | 2 +- .../Http/Authenticator/Passport/UserPassportInterface.php | 2 +- .../Component/Security/Http/Authenticator/X509Authenticator.php | 2 +- .../Security/Http/EventListener/CheckCredentialsListener.php | 2 +- .../Security/Http/EventListener/CsrfProtectionListener.php | 2 +- .../Security/Http/EventListener/LoginThrottlingListener.php | 2 +- .../Security/Http/EventListener/PasswordMigratingListener.php | 2 +- .../Security/Http/EventListener/RememberMeListener.php | 2 +- .../Security/Http/EventListener/UserCheckerListener.php | 2 +- .../Security/Http/EventListener/UserProviderListener.php | 2 +- .../Security/Http/Firewall/AuthenticatorManagerListener.php | 2 +- .../Http/LoginLink/Exception/ExpiredLoginLinkException.php | 2 +- .../Exception/InvalidLoginLinkAuthenticationException.php | 2 +- .../Http/LoginLink/Exception/InvalidLoginLinkException.php | 2 +- .../LoginLink/Exception/InvalidLoginLinkExceptionInterface.php | 2 +- .../Component/Security/Http/LoginLink/LoginLinkDetails.php | 2 +- .../Component/Security/Http/LoginLink/LoginLinkHandler.php | 2 +- .../Security/Http/LoginLink/LoginLinkHandlerInterface.php | 2 +- .../Component/Security/Http/LoginLink/LoginLinkNotification.php | 2 +- .../Security/Http/RateLimiter/DefaultLoginRateLimiter.php | 2 +- .../Component/Semaphore/Exception/ExceptionInterface.php | 2 +- .../Component/Semaphore/Exception/InvalidArgumentException.php | 2 +- src/Symfony/Component/Semaphore/Exception/RuntimeException.php | 2 +- .../Semaphore/Exception/SemaphoreAcquiringException.php | 2 +- .../Component/Semaphore/Exception/SemaphoreExpiredException.php | 2 +- .../Semaphore/Exception/SemaphoreReleasingException.php | 2 +- src/Symfony/Component/Semaphore/Key.php | 2 +- src/Symfony/Component/Semaphore/PersistingStoreInterface.php | 2 +- src/Symfony/Component/Semaphore/Semaphore.php | 2 +- src/Symfony/Component/Semaphore/SemaphoreFactory.php | 2 +- src/Symfony/Component/Semaphore/SemaphoreInterface.php | 2 +- src/Symfony/Component/Semaphore/Store/RedisStore.php | 2 +- src/Symfony/Component/Semaphore/Store/StoreFactory.php | 2 +- src/Symfony/Component/Uid/AbstractUid.php | 2 +- src/Symfony/Component/Uid/NilUuid.php | 2 +- src/Symfony/Component/Uid/Ulid.php | 2 +- src/Symfony/Component/Uid/Uuid.php | 2 +- src/Symfony/Component/Uid/UuidV1.php | 2 +- src/Symfony/Component/Uid/UuidV3.php | 2 +- src/Symfony/Component/Uid/UuidV4.php | 2 +- src/Symfony/Component/Uid/UuidV5.php | 2 +- src/Symfony/Component/Uid/UuidV6.php | 2 +- 222 files changed, 222 insertions(+), 222 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php index a94c988d6308e..35921dd194fa2 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php @@ -16,7 +16,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface AuthenticatorFactoryInterface { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php index 67294b3111d63..4ebd8a0344159 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php @@ -19,7 +19,7 @@ * @author Wouter de Jong * * @internal - * @experimental in 5.2 + * @experimental in 5.3 */ class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php index 3afddb3d35f3b..8071108714f76 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php @@ -24,7 +24,7 @@ /** * @internal - * @experimental in 5.2 + * @experimental in 5.3 */ class LoginLinkFactory extends AbstractFactory implements AuthenticatorFactoryInterface { diff --git a/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php index ba4af81acd603..70ac67a865d15 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php @@ -27,7 +27,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class UserAuthenticator implements UserAuthenticatorInterface { diff --git a/src/Symfony/Component/HttpFoundation/RateLimiter/AbstractRequestRateLimiter.php b/src/Symfony/Component/HttpFoundation/RateLimiter/AbstractRequestRateLimiter.php index 885ca02af5be4..ae0a7d93e80ee 100644 --- a/src/Symfony/Component/HttpFoundation/RateLimiter/AbstractRequestRateLimiter.php +++ b/src/Symfony/Component/HttpFoundation/RateLimiter/AbstractRequestRateLimiter.php @@ -22,7 +22,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface { diff --git a/src/Symfony/Component/HttpFoundation/RateLimiter/RequestRateLimiterInterface.php b/src/Symfony/Component/HttpFoundation/RateLimiter/RequestRateLimiterInterface.php index 4b95f1ef654c0..513435accaa19 100644 --- a/src/Symfony/Component/HttpFoundation/RateLimiter/RequestRateLimiterInterface.php +++ b/src/Symfony/Component/HttpFoundation/RateLimiter/RequestRateLimiterInterface.php @@ -22,7 +22,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface RequestRateLimiterInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php index ac591ea67c464..1fda563ec9619 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php @@ -18,7 +18,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DiscordOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php index 53f9cd0886522..41723f6712848 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php @@ -25,7 +25,7 @@ * * @internal * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DiscordTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php index 23bf555b69655..c2a16c5606406 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Mathieu Piot * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DiscordTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php index db4fa581dd78b..bf369caf075e2 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractDiscordEmbed implements DiscordEmbedInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php index 12c5d5ee2b0ef..09f874eaaf521 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractDiscordEmbedObject implements DiscordEmbedObjectInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php index c357e61edd5ea..c0fe90189a67a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DiscordAuthorEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php index c2565a721f678..ca2adb7635022 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DiscordEmbed extends AbstractDiscordEmbed { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php index ce1abdeb3526f..bc1f1398cc300 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ interface DiscordEmbedObjectInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php index ac8b215bb6e8b..20eb38031f96b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DiscordFieldEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php index d2dfa0b56c3b7..516f56c863acf 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DiscordFooterEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php index a2e4437783c2a..a53bb42947b52 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php @@ -14,7 +14,7 @@ /** * @author Karoly Gossler * - * @experimental in 5.2 + * @experimental in 5.3 */ class DiscordMediaEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php index a216a16f98594..bc8c7a6207e1b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php @@ -23,7 +23,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; /** - * @experimental in 5.2 + * @experimental in 5.3 */ final class EsendexTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php index f526f5a385151..368be4b66a6f9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php @@ -17,7 +17,7 @@ use Symfony\Component\Notifier\Transport\TransportInterface; /** - * @experimental in 5.2 + * @experimental in 5.3 */ final class EsendexTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php index e4a46d9007d45..3e094088762ca 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php @@ -18,7 +18,7 @@ * * @see https://firebase.google.com/docs/cloud-messaging/xmpp-server-ref.html * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class FirebaseOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php index 1c1684d53a58b..4734ad4ea0674 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php @@ -24,7 +24,7 @@ /** * @author Jeroen Spee * - * @experimental in 5.2 + * @experimental in 5.3 */ final class FirebaseTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php index 1803c412209f9..afce34cdd32b9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Jeroen Spee * - * @experimental in 5.2 + * @experimental in 5.3 */ final class FirebaseTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php index 670ca171d58b7..472bd69631649 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php @@ -14,7 +14,7 @@ use Symfony\Component\Notifier\Bridge\Firebase\FirebaseOptions; /** - * @experimental in 5.2 + * @experimental in 5.3 */ final class AndroidNotification extends FirebaseOptions { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php index a20b8cf22de3b..e8cc16cebc9ed 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php @@ -14,7 +14,7 @@ use Symfony\Component\Notifier\Bridge\Firebase\FirebaseOptions; /** - * @experimental in 5.2 + * @experimental in 5.3 */ final class IOSNotification extends FirebaseOptions { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php index 05664abcc3a75..e8c7b45956342 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php @@ -14,7 +14,7 @@ use Symfony\Component\Notifier\Bridge\Firebase\FirebaseOptions; /** - * @experimental in 5.2 + * @experimental in 5.3 */ final class WebNotification extends FirebaseOptions { diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php index 7c199c1de4c0c..2113b014d7143 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php @@ -23,7 +23,7 @@ /** * @author Antoine Makdessi * - * @experimental in 5.2 + * @experimental in 5.3 */ final class FreeMobileTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php index fca9fc2680ac6..e1cf9b407b02e 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php @@ -20,7 +20,7 @@ /** * @author Antoine Makdessi * - * @experimental in 5.2 + * @experimental in 5.3 */ final class FreeMobileTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php index a7b2b817716f7..16c9a84b7c908 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php @@ -19,7 +19,7 @@ /** * @author Jérôme Tamarelle * - * @experimental in 5.2 + * @experimental in 5.3 */ final class GoogleChatOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php index ab191a469959e..b9f4d40b7e571 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php @@ -26,7 +26,7 @@ * * @internal * - * @experimental in 5.2 + * @experimental in 5.3 */ final class GoogleChatTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php index 039b3f5e7a4ff..fb71c51280d79 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Jérôme Tamarelle * - * @experimental in 5.2 + * @experimental in 5.3 */ final class GoogleChatTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php index a480aabc6e43b..f25a8a98862a7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php @@ -24,7 +24,7 @@ * @author Fabien Potencier * @author Jérémy Romey * - * @experimental in 5.2 + * @experimental in 5.3 */ final class InfobipTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php index 3c8a7968db0c8..c2b2837b68730 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php @@ -21,7 +21,7 @@ * @author Fabien Potencier * @author Jérémy Romey * - * @experimental in 5.2 + * @experimental in 5.3 */ final class InfobipTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php index 7e915be886945..611a924c674a2 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php @@ -21,7 +21,7 @@ /** * @author Smaïne Milianni * - * @experimental in 5.2 + * @experimental in 5.3 */ final class LinkedInOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php index 4fbf958a7ee43..48ba8536c1e0e 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php @@ -24,7 +24,7 @@ /** * @author Smaïne Milianni * - * @experimental in 5.2 + * @experimental in 5.3 * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#sharecontent */ diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php index f13afa472301d..fc12f6b0a5d06 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Smaïne Milianni * - * @experimental in 5.2 + * @experimental in 5.3 */ class LinkedInTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php index 6e6d2d56920c6..ccae190e53e71 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php @@ -14,7 +14,7 @@ /** * @author Smaïne Milianni * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php index 9161eb1b4513b..4b801b8485dea 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php @@ -14,7 +14,7 @@ /** * @author Smaïne Milianni * - * @experimental in 5.2 + * @experimental in 5.3 */ final class AuthorShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php index 98fd1a83e6262..6a340bbf3ac1d 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php @@ -18,7 +18,7 @@ * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#schema lifecycleState section * - * @experimental in 5.2 + * @experimental in 5.3 */ final class LifecycleStateShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php index 2efbb08f65640..6395a88066685 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php @@ -18,7 +18,7 @@ * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#sharecontent * - * @experimental in 5.2 + * @experimental in 5.3 */ final class ShareContentShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php index f277d13b6be70..0983652e69f44 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php @@ -18,7 +18,7 @@ * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#sharemedia * - * @experimental in 5.2 + * @experimental in 5.3 */ class ShareMediaShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php index 15883c5a3a44a..633ae1fe82c24 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php @@ -16,7 +16,7 @@ /** * @author Smaïne Milianni * - * @experimental in 5.2 + * @experimental in 5.3 */ final class VisibilityShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php index 98c43e26e09e5..c27df25a3526f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php @@ -23,7 +23,7 @@ /** * @author Emanuele Panzeri * - * @experimental in 5.2 + * @experimental in 5.3 */ final class MattermostTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php index 8feb198b9f6c4..a678da252953f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Emanuele Panzeri * - * @experimental in 5.2 + * @experimental in 5.3 */ final class MattermostTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php index b63fd59e6cf23..b65dcdda53ad3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php @@ -17,7 +17,7 @@ /** * @author Bastien Durand * - * @experimental in 5.2 + * @experimental in 5.3 */ final class MobytOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php index 1f2a793dfd348..15604438576f1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php @@ -23,7 +23,7 @@ /** * @author Basien Durand * - * @experimental in 5.2 + * @experimental in 5.3 */ final class MobytTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php index d3eda1610f723..76d3b212c4741 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Bastien Durand * - * @experimental in 5.2 + * @experimental in 5.3 */ final class MobytTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php index c506d0ab96dac..5451fc8af47d4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php @@ -23,7 +23,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class NexmoTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php index c296de29d6d1c..0f3789746784a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class NexmoTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php index ad1757ad51aef..8d71d23b4a69c 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php @@ -23,7 +23,7 @@ /** * @author Thomas Ferney * - * @experimental in 5.2 + * @experimental in 5.3 */ final class OvhCloudTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php index 87327d1997167..90d70016b9201 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Thomas Ferney * - * @experimental in 5.2 + * @experimental in 5.3 */ final class OvhCloudTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php index 06ab1fc7194a6..e70f74ca8c321 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php @@ -16,7 +16,7 @@ /** * @author Jeroen Spee * - * @experimental in 5.2 + * @experimental in 5.3 * * @see https://rocket.chat/docs/administrator-guides/integrations/ */ diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php index ff81313c99bce..57ebb56ac187b 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php @@ -25,7 +25,7 @@ * * @internal * - * @experimental in 5.2 + * @experimental in 5.3 */ final class RocketChatTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php index e597045363fe2..99f9545afdfbf 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Jeroen Spee * - * @experimental in 5.2 + * @experimental in 5.3 */ final class RocketChatTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php index 889bd1c454cf1..70b62e78d7a87 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php @@ -23,7 +23,7 @@ /** * @author Pierre Tondereau * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SendinblueTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php index 7f9d1f9b4b78c..e833148173c87 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php @@ -20,7 +20,7 @@ /** * @author Pierre Tondereau * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SendinblueTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php index 0810aae702565..e14925764a491 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php @@ -23,7 +23,7 @@ /** * @author Iliya Miroslavov Iliev * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SinchTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php index 259a3397c8dbd..561588da5ffde 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Iliya Miroslavov Iliev * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SinchTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php index 383e4a2aaa92a..6b697aede59fc 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php @@ -20,7 +20,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SlackOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php index 606ad6961be53..f90b18349ec90 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php @@ -25,7 +25,7 @@ * * @internal * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SlackTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php index b2b6dda9eb9c0..e99579b78b757 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SlackTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php index 83c3b1aaabeb6..0abf6ee3fb7d0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php @@ -22,7 +22,7 @@ /** * @author Marcin Szepczynski - * @experimental in 5.2 + * @experimental in 5.3 */ final class SmsapiTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php index a573dbbb133a2..bf94824e6959d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php @@ -18,7 +18,7 @@ /** * @author Marcin Szepczynski - * @experimental in 5.2 + * @experimental in 5.3 */ class SmsapiTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php index 27de874725f2d..10a9154001bd9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php @@ -14,7 +14,7 @@ /** * @author Mihail Krasilnikov * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php index 29c2ef54b4e3c..b5b87f75ca0ea 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php @@ -14,7 +14,7 @@ /** * @author Mihail Krasilnikov * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractKeyboardButton { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php index 63e05a6772e68..b5cf6b3fd8405 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php @@ -16,7 +16,7 @@ * * @see https://core.telegram.org/bots/api#inlinekeyboardbutton * - * @experimental in 5.2 + * @experimental in 5.3 */ final class InlineKeyboardButton extends AbstractKeyboardButton { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php index c2b19ed97cce8..15359af302ad8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php @@ -16,7 +16,7 @@ * * @see https://core.telegram.org/bots/api#keyboardbutton * - * @experimental in 5.2 + * @experimental in 5.3 */ final class KeyboardButton extends AbstractKeyboardButton { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php index 43ca3bae3e9de..b6dc92317fa5e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php @@ -16,7 +16,7 @@ * * @see https://core.telegram.org/bots/api#forcereply * - * @experimental in 5.2 + * @experimental in 5.3 */ final class ForceReply extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php index cbf35d724c29c..00a80183bec53 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php @@ -18,7 +18,7 @@ * * @see https://core.telegram.org/bots/api#inlinekeyboardmarkup * - * @experimental in 5.2 + * @experimental in 5.3 */ final class InlineKeyboardMarkup extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php index 9070c67720c38..6767707624b00 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php @@ -18,7 +18,7 @@ * * @see https://core.telegram.org/bots/api#replykeyboardmarkup * - * @experimental in 5.2 + * @experimental in 5.3 */ final class ReplyKeyboardMarkup extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php index 81cbe57199730..b0619f35976b1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php @@ -16,7 +16,7 @@ * * @see https://core.telegram.org/bots/api#replykeyboardremove * - * @experimental in 5.2 + * @experimental in 5.3 */ final class ReplyKeyboardRemove extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php index 5e6d637de9efe..bb7932c1c9921 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php @@ -17,7 +17,7 @@ /** * @author Mihail Krasilnikov * - * @experimental in 5.2 + * @experimental in 5.3 */ final class TelegramOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php index de5085f5af5c4..1f34e4999d9ab 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php @@ -30,7 +30,7 @@ * * @internal * - * @experimental in 5.2 + * @experimental in 5.3 */ final class TelegramTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php index da66597762d0d..0594e79d3f3b5 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php @@ -20,7 +20,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class TelegramTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php index 1c38a8c447447..dd0a5858f3e54 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php @@ -23,7 +23,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class TwilioTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php index 6246737dd0f4a..56f881c2e6214 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class TwilioTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php index 963131d382ca5..4391544dc9198 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php @@ -16,7 +16,7 @@ /** * @author Mohammad Emran Hasan * - * @experimental in 5.2 + * @experimental in 5.3 */ final class ZulipOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php index b12581abe72b7..61cee7bb61fb2 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php @@ -23,7 +23,7 @@ /** * @author Mohammad Emran Hasan * - * @experimental in 5.2 + * @experimental in 5.3 */ class ZulipTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php index a624d3f2f7fce..5615dbad54aad 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Mohammad Emran Hasan * - * @experimental in 5.2 + * @experimental in 5.3 */ class ZulipTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Channel/AbstractChannel.php b/src/Symfony/Component/Notifier/Channel/AbstractChannel.php index c97a191636b68..b6c6d0c54ba8f 100644 --- a/src/Symfony/Component/Notifier/Channel/AbstractChannel.php +++ b/src/Symfony/Component/Notifier/Channel/AbstractChannel.php @@ -18,7 +18,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractChannel implements ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php index 6026d0fd0de40..715c87204432b 100644 --- a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php +++ b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php @@ -18,7 +18,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class BrowserChannel implements ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChannelInterface.php b/src/Symfony/Component/Notifier/Channel/ChannelInterface.php index 0ac9f44c19311..48a0228134fa2 100644 --- a/src/Symfony/Component/Notifier/Channel/ChannelInterface.php +++ b/src/Symfony/Component/Notifier/Channel/ChannelInterface.php @@ -17,7 +17,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php b/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php index 7d525850fd4ac..2d0c0eb0afa8f 100644 --- a/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php +++ b/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php @@ -16,7 +16,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class ChannelPolicy implements ChannelPolicyInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php b/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php index 5559ec59c7df2..792176853e4c9 100644 --- a/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php +++ b/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface ChannelPolicyInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChatChannel.php b/src/Symfony/Component/Notifier/Channel/ChatChannel.php index aaf2ded1175e2..61c3c91f485a4 100644 --- a/src/Symfony/Component/Notifier/Channel/ChatChannel.php +++ b/src/Symfony/Component/Notifier/Channel/ChatChannel.php @@ -19,7 +19,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class ChatChannel extends AbstractChannel { diff --git a/src/Symfony/Component/Notifier/Channel/EmailChannel.php b/src/Symfony/Component/Notifier/Channel/EmailChannel.php index 25382dea32b34..4f22b1b04d65d 100644 --- a/src/Symfony/Component/Notifier/Channel/EmailChannel.php +++ b/src/Symfony/Component/Notifier/Channel/EmailChannel.php @@ -26,7 +26,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class EmailChannel implements ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/SmsChannel.php b/src/Symfony/Component/Notifier/Channel/SmsChannel.php index 16e4ff4933839..0251c95224368 100644 --- a/src/Symfony/Component/Notifier/Channel/SmsChannel.php +++ b/src/Symfony/Component/Notifier/Channel/SmsChannel.php @@ -20,7 +20,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class SmsChannel extends AbstractChannel { diff --git a/src/Symfony/Component/Notifier/Chatter.php b/src/Symfony/Component/Notifier/Chatter.php index 81e3984715144..efbf9538df9ca 100644 --- a/src/Symfony/Component/Notifier/Chatter.php +++ b/src/Symfony/Component/Notifier/Chatter.php @@ -23,7 +23,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class Chatter implements ChatterInterface { diff --git a/src/Symfony/Component/Notifier/ChatterInterface.php b/src/Symfony/Component/Notifier/ChatterInterface.php index 7c54621b8a7e8..4519821edcc9d 100644 --- a/src/Symfony/Component/Notifier/ChatterInterface.php +++ b/src/Symfony/Component/Notifier/ChatterInterface.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface ChatterInterface extends TransportInterface { diff --git a/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php b/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php index 5b6e1bf10ad2e..a685e48694eb0 100644 --- a/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php +++ b/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php @@ -20,7 +20,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class NotificationDataCollector extends DataCollector { diff --git a/src/Symfony/Component/Notifier/Event/MessageEvent.php b/src/Symfony/Component/Notifier/Event/MessageEvent.php index cad444a9513b9..2ab2ad8f2463d 100644 --- a/src/Symfony/Component/Notifier/Event/MessageEvent.php +++ b/src/Symfony/Component/Notifier/Event/MessageEvent.php @@ -17,7 +17,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class MessageEvent extends Event { diff --git a/src/Symfony/Component/Notifier/Event/NotificationEvents.php b/src/Symfony/Component/Notifier/Event/NotificationEvents.php index 90f37933c3376..b7f60e224b6cc 100644 --- a/src/Symfony/Component/Notifier/Event/NotificationEvents.php +++ b/src/Symfony/Component/Notifier/Event/NotificationEvents.php @@ -16,7 +16,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class NotificationEvents { diff --git a/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php b/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php index 59cad7ec14fd7..2cebd9b8a7e36 100644 --- a/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php +++ b/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php @@ -19,7 +19,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class NotificationLoggerListener implements EventSubscriberInterface, ResetInterface { diff --git a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php index c4150c2b4719f..ce5a87f35cc06 100644 --- a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php +++ b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php @@ -21,7 +21,7 @@ * * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class SendFailedMessageToNotifierListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php b/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php index 218c41013c555..bef37cefc10bc 100644 --- a/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php +++ b/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php @@ -16,7 +16,7 @@ * * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface ExceptionInterface extends \Throwable { diff --git a/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php b/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php index efc42a1596404..e9fc04fe66120 100644 --- a/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php +++ b/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class IncompleteDsnException extends InvalidArgumentException { diff --git a/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php b/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php index f8229138363ee..9f82bee7db800 100644 --- a/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php +++ b/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/LogicException.php b/src/Symfony/Component/Notifier/Exception/LogicException.php index d35f2c2847243..0919d32003c10 100644 --- a/src/Symfony/Component/Notifier/Exception/LogicException.php +++ b/src/Symfony/Component/Notifier/Exception/LogicException.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class LogicException extends \LogicException implements ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/RuntimeException.php b/src/Symfony/Component/Notifier/Exception/RuntimeException.php index 87b39eeafd2c8..6b91df7819a41 100644 --- a/src/Symfony/Component/Notifier/Exception/RuntimeException.php +++ b/src/Symfony/Component/Notifier/Exception/RuntimeException.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class RuntimeException extends \RuntimeException implements ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/TransportException.php b/src/Symfony/Component/Notifier/Exception/TransportException.php index fade7275fa5fd..24ce418ad9b62 100644 --- a/src/Symfony/Component/Notifier/Exception/TransportException.php +++ b/src/Symfony/Component/Notifier/Exception/TransportException.php @@ -16,7 +16,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class TransportException extends RuntimeException implements TransportExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php b/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php index 321238c5e29a9..72023e4a963a3 100644 --- a/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php +++ b/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface TransportExceptionInterface extends ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 61be861afa616..2e3860a4bce85 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -17,7 +17,7 @@ /** * @author Konstantin Myakshin * - * @experimental in 5.2 + * @experimental in 5.3 */ class UnsupportedSchemeException extends LogicException { diff --git a/src/Symfony/Component/Notifier/Message/ChatMessage.php b/src/Symfony/Component/Notifier/Message/ChatMessage.php index 43a0c4f69cfb3..082adafdad0cc 100644 --- a/src/Symfony/Component/Notifier/Message/ChatMessage.php +++ b/src/Symfony/Component/Notifier/Message/ChatMessage.php @@ -16,7 +16,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class ChatMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/EmailMessage.php b/src/Symfony/Component/Notifier/Message/EmailMessage.php index dda322a468e21..530f19c582204 100644 --- a/src/Symfony/Component/Notifier/Message/EmailMessage.php +++ b/src/Symfony/Component/Notifier/Message/EmailMessage.php @@ -23,7 +23,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class EmailMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/MessageInterface.php b/src/Symfony/Component/Notifier/Message/MessageInterface.php index 814ec0187a447..7fff691ed3dc7 100644 --- a/src/Symfony/Component/Notifier/Message/MessageInterface.php +++ b/src/Symfony/Component/Notifier/Message/MessageInterface.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php b/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php index 22ce5606ea353..1c52a4cb9a79e 100644 --- a/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php +++ b/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Message/NullMessage.php b/src/Symfony/Component/Notifier/Message/NullMessage.php index 3fd59de62e84c..8cda1603aa7d0 100644 --- a/src/Symfony/Component/Notifier/Message/NullMessage.php +++ b/src/Symfony/Component/Notifier/Message/NullMessage.php @@ -14,7 +14,7 @@ /** * @author Jan Schädlich * - * @experimental in 5.2 + * @experimental in 5.3 */ final class NullMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/SentMessage.php b/src/Symfony/Component/Notifier/Message/SentMessage.php index d3eb75bba445e..852b70375f34b 100644 --- a/src/Symfony/Component/Notifier/Message/SentMessage.php +++ b/src/Symfony/Component/Notifier/Message/SentMessage.php @@ -14,7 +14,7 @@ /** * @author Jérémy Romey * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SentMessage { diff --git a/src/Symfony/Component/Notifier/Message/SmsMessage.php b/src/Symfony/Component/Notifier/Message/SmsMessage.php index 2a7a1b82a8acc..19cf7edd7abb8 100644 --- a/src/Symfony/Component/Notifier/Message/SmsMessage.php +++ b/src/Symfony/Component/Notifier/Message/SmsMessage.php @@ -18,7 +18,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SmsMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Messenger/MessageHandler.php b/src/Symfony/Component/Notifier/Messenger/MessageHandler.php index 88e921afd9ab2..dee0d2c06119f 100644 --- a/src/Symfony/Component/Notifier/Messenger/MessageHandler.php +++ b/src/Symfony/Component/Notifier/Messenger/MessageHandler.php @@ -18,7 +18,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class MessageHandler { diff --git a/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php index 6c605ed677292..4d3a48ff4c549 100644 --- a/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php @@ -17,7 +17,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface ChatNotificationInterface { diff --git a/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php index 8fcf874761c14..817d9a3835ceb 100644 --- a/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php @@ -17,7 +17,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface EmailNotificationInterface { diff --git a/src/Symfony/Component/Notifier/Notification/Notification.php b/src/Symfony/Component/Notifier/Notification/Notification.php index 5b85481c15123..fcbaa3c72ddab 100644 --- a/src/Symfony/Component/Notifier/Notification/Notification.php +++ b/src/Symfony/Component/Notifier/Notification/Notification.php @@ -18,7 +18,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class Notification { diff --git a/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php index a9f7bb14a0823..13744cbc41578 100644 --- a/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php @@ -17,7 +17,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface SmsNotificationInterface { diff --git a/src/Symfony/Component/Notifier/Notifier.php b/src/Symfony/Component/Notifier/Notifier.php index 481694372df07..bbcfcd5c0d1cb 100644 --- a/src/Symfony/Component/Notifier/Notifier.php +++ b/src/Symfony/Component/Notifier/Notifier.php @@ -23,7 +23,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class Notifier implements NotifierInterface { diff --git a/src/Symfony/Component/Notifier/NotifierInterface.php b/src/Symfony/Component/Notifier/NotifierInterface.php index edba2d8b37fdf..4f87ebb33933b 100644 --- a/src/Symfony/Component/Notifier/NotifierInterface.php +++ b/src/Symfony/Component/Notifier/NotifierInterface.php @@ -19,7 +19,7 @@ * * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface NotifierInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php b/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php index 5a7c3d4cb89fb..bc3781ac3f813 100644 --- a/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php +++ b/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php @@ -14,7 +14,7 @@ /** * @author Jan Schädlich * - * @experimental in 5.2 + * @experimental in 5.3 */ interface EmailRecipientInterface extends RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php b/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php index 73e5bd98e9c9c..5424ce9367eaa 100644 --- a/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php +++ b/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php @@ -14,7 +14,7 @@ /** * @author Jan Schädlich * - * @experimental in 5.2 + * @experimental in 5.3 */ trait EmailRecipientTrait { diff --git a/src/Symfony/Component/Notifier/Recipient/NoRecipient.php b/src/Symfony/Component/Notifier/Recipient/NoRecipient.php index 3419a61ec80cf..0e84cc83ab675 100644 --- a/src/Symfony/Component/Notifier/Recipient/NoRecipient.php +++ b/src/Symfony/Component/Notifier/Recipient/NoRecipient.php @@ -14,7 +14,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class NoRecipient implements RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/Recipient.php b/src/Symfony/Component/Notifier/Recipient/Recipient.php index db50550ac183c..38eca09530d41 100644 --- a/src/Symfony/Component/Notifier/Recipient/Recipient.php +++ b/src/Symfony/Component/Notifier/Recipient/Recipient.php @@ -17,7 +17,7 @@ * @author Fabien Potencier * @author Jan Schädlich * - * @experimental in 5.2 + * @experimental in 5.3 */ class Recipient implements EmailRecipientInterface, SmsRecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php b/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php index 53e4f60a3ef44..cc165d02a1755 100644 --- a/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php +++ b/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php @@ -14,7 +14,7 @@ /** * @author Jan Schädlich * - * @experimental in 5.2 + * @experimental in 5.3 */ interface RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php b/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php index 3b34c1b802c6a..81c6abeb51b8f 100644 --- a/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php +++ b/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php @@ -15,7 +15,7 @@ * @author Fabien Potencier * @author Jan Schädlich * - * @experimental in 5.2 + * @experimental in 5.3 */ interface SmsRecipientInterface extends RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php b/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php index 9f1ad1651805f..c17b6d65e148f 100644 --- a/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php +++ b/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php @@ -14,7 +14,7 @@ /** * @author Jan Schädlich * - * @experimental in 5.2 + * @experimental in 5.3 */ trait SmsRecipientTrait { diff --git a/src/Symfony/Component/Notifier/Texter.php b/src/Symfony/Component/Notifier/Texter.php index 4414b248d8f89..5c69e042a10e8 100644 --- a/src/Symfony/Component/Notifier/Texter.php +++ b/src/Symfony/Component/Notifier/Texter.php @@ -23,7 +23,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class Texter implements TexterInterface { diff --git a/src/Symfony/Component/Notifier/TexterInterface.php b/src/Symfony/Component/Notifier/TexterInterface.php index 9721c8190921d..c6bfe8e85cead 100644 --- a/src/Symfony/Component/Notifier/TexterInterface.php +++ b/src/Symfony/Component/Notifier/TexterInterface.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface TexterInterface extends TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index c3d8617554da4..a5d419367eb64 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -42,7 +42,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class Transport { diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php index 06fa4ed99b4c3..8c81a3018d26d 100644 --- a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php @@ -24,7 +24,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractTransport implements TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php index ae3167d066d2b..69cfa61138c2a 100644 --- a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php +++ b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php @@ -21,7 +21,7 @@ * @author Konstantin Myakshin * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractTransportFactory implements TransportFactoryInterface { diff --git a/src/Symfony/Component/Notifier/Transport/Dsn.php b/src/Symfony/Component/Notifier/Transport/Dsn.php index f8f6fb6f93f53..fd8b327ef3639 100644 --- a/src/Symfony/Component/Notifier/Transport/Dsn.php +++ b/src/Symfony/Component/Notifier/Transport/Dsn.php @@ -16,7 +16,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class Dsn { diff --git a/src/Symfony/Component/Notifier/Transport/FailoverTransport.php b/src/Symfony/Component/Notifier/Transport/FailoverTransport.php index 68edb9f1188fa..8714050372966 100644 --- a/src/Symfony/Component/Notifier/Transport/FailoverTransport.php +++ b/src/Symfony/Component/Notifier/Transport/FailoverTransport.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class FailoverTransport extends RoundRobinTransport { diff --git a/src/Symfony/Component/Notifier/Transport/NullTransport.php b/src/Symfony/Component/Notifier/Transport/NullTransport.php index bd9b878fdec79..94fee129d2b45 100644 --- a/src/Symfony/Component/Notifier/Transport/NullTransport.php +++ b/src/Symfony/Component/Notifier/Transport/NullTransport.php @@ -22,7 +22,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class NullTransport implements TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php b/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php index e2a6b6e854a37..231837eabbb21 100644 --- a/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php +++ b/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php @@ -16,7 +16,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class NullTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php b/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php index a67bccb95c76e..c78b8763f1ead 100644 --- a/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php +++ b/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php @@ -22,7 +22,7 @@ * * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ class RoundRobinTransport implements TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php b/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php index 027d7828e5abc..432f69d04a9d1 100644 --- a/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php +++ b/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php @@ -17,7 +17,7 @@ /** * @author Konstantin Myakshin * - * @experimental in 5.2 + * @experimental in 5.3 */ interface TransportFactoryInterface { diff --git a/src/Symfony/Component/Notifier/Transport/TransportInterface.php b/src/Symfony/Component/Notifier/Transport/TransportInterface.php index be3f3306ce8d4..1ab2ff4c7443d 100644 --- a/src/Symfony/Component/Notifier/Transport/TransportInterface.php +++ b/src/Symfony/Component/Notifier/Transport/TransportInterface.php @@ -18,7 +18,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ interface TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/Transports.php b/src/Symfony/Component/Notifier/Transport/Transports.php index cb7d5031b706b..049b64da921dc 100644 --- a/src/Symfony/Component/Notifier/Transport/Transports.php +++ b/src/Symfony/Component/Notifier/Transport/Transports.php @@ -19,7 +19,7 @@ /** * @author Fabien Potencier * - * @experimental in 5.2 + * @experimental in 5.3 */ final class Transports implements TransportInterface { diff --git a/src/Symfony/Component/RateLimiter/CompoundLimiter.php b/src/Symfony/Component/RateLimiter/CompoundLimiter.php index 286940930326e..79a2e2210ae72 100644 --- a/src/Symfony/Component/RateLimiter/CompoundLimiter.php +++ b/src/Symfony/Component/RateLimiter/CompoundLimiter.php @@ -16,7 +16,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class CompoundLimiter implements LimiterInterface { diff --git a/src/Symfony/Component/RateLimiter/Exception/InvalidIntervalException.php b/src/Symfony/Component/RateLimiter/Exception/InvalidIntervalException.php index 02b5c810e8278..0f29ac95ff894 100644 --- a/src/Symfony/Component/RateLimiter/Exception/InvalidIntervalException.php +++ b/src/Symfony/Component/RateLimiter/Exception/InvalidIntervalException.php @@ -14,7 +14,7 @@ /** * @author Tobias Nyholm * - * @experimental in 5.2 + * @experimental in 5.3 */ class InvalidIntervalException extends \LogicException { diff --git a/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php b/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php index 1eeec9de64193..5ff82b5dc953a 100644 --- a/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php +++ b/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php @@ -16,7 +16,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class MaxWaitDurationExceededException extends \RuntimeException { diff --git a/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php b/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php index 0cb10c750befe..2597a6a6fe967 100644 --- a/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php +++ b/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php @@ -16,7 +16,7 @@ /** * @author Kevin Bond * - * @experimental in 5.2 + * @experimental in 5.3 */ class RateLimitExceededException extends \RuntimeException { diff --git a/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php b/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php index 852c99ae1d698..791c19dd77978 100644 --- a/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php +++ b/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php @@ -14,7 +14,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class ReserveNotSupportedException extends \BadMethodCallException { diff --git a/src/Symfony/Component/RateLimiter/LimiterInterface.php b/src/Symfony/Component/RateLimiter/LimiterInterface.php index f190f56354e64..1884ae602342d 100644 --- a/src/Symfony/Component/RateLimiter/LimiterInterface.php +++ b/src/Symfony/Component/RateLimiter/LimiterInterface.php @@ -17,7 +17,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface LimiterInterface { diff --git a/src/Symfony/Component/RateLimiter/LimiterStateInterface.php b/src/Symfony/Component/RateLimiter/LimiterStateInterface.php index 9f70bdc1ad4bb..ad5aff0f236c4 100644 --- a/src/Symfony/Component/RateLimiter/LimiterStateInterface.php +++ b/src/Symfony/Component/RateLimiter/LimiterStateInterface.php @@ -20,7 +20,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface LimiterStateInterface { diff --git a/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php b/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php index b5dfec19311a8..d1d8863e2c6aa 100644 --- a/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php @@ -23,7 +23,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class FixedWindowLimiter implements LimiterInterface { diff --git a/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php b/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php index 03d4e54331db2..8ad41f31008a1 100644 --- a/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php @@ -23,7 +23,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class NoLimiter implements LimiterInterface { diff --git a/src/Symfony/Component/RateLimiter/Policy/Rate.php b/src/Symfony/Component/RateLimiter/Policy/Rate.php index a0e89fb5c2deb..0c91ef78e76c2 100644 --- a/src/Symfony/Component/RateLimiter/Policy/Rate.php +++ b/src/Symfony/Component/RateLimiter/Policy/Rate.php @@ -18,7 +18,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class Rate { diff --git a/src/Symfony/Component/RateLimiter/Policy/ResetLimiterTrait.php b/src/Symfony/Component/RateLimiter/Policy/ResetLimiterTrait.php index a356490488669..fe7fc10bed216 100644 --- a/src/Symfony/Component/RateLimiter/Policy/ResetLimiterTrait.php +++ b/src/Symfony/Component/RateLimiter/Policy/ResetLimiterTrait.php @@ -15,7 +15,7 @@ use Symfony\Component\RateLimiter\Storage\StorageInterface; /** - * @experimental in 5.2 + * @experimental in 5.3 */ trait ResetLimiterTrait { diff --git a/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php b/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php index 4188cf4ca3262..9ffcf8f830643 100644 --- a/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php +++ b/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php @@ -18,7 +18,7 @@ * @author Tobias Nyholm * * @internal - * @experimental in 5.2 + * @experimental in 5.3 */ final class SlidingWindow implements LimiterStateInterface { diff --git a/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php b/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php index fe6f1da8c31f3..47652256af818 100644 --- a/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php @@ -31,7 +31,7 @@ * * @author Tobias Nyholm * - * @experimental in 5.2 + * @experimental in 5.3 */ final class SlidingWindowLimiter implements LimiterInterface { diff --git a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php index 8464acf149777..227ab85037fd7 100644 --- a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php +++ b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php @@ -17,7 +17,7 @@ * @author Wouter de Jong * * @internal - * @experimental in 5.2 + * @experimental in 5.3 */ final class TokenBucket implements LimiterStateInterface { diff --git a/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php b/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php index 24f82d21ec720..5a02068f83485 100644 --- a/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php @@ -22,7 +22,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class TokenBucketLimiter implements LimiterInterface { diff --git a/src/Symfony/Component/RateLimiter/Policy/Window.php b/src/Symfony/Component/RateLimiter/Policy/Window.php index 1cfd49eb82ffa..e0970cba6d51c 100644 --- a/src/Symfony/Component/RateLimiter/Policy/Window.php +++ b/src/Symfony/Component/RateLimiter/Policy/Window.php @@ -17,7 +17,7 @@ * @author Wouter de Jong * * @internal - * @experimental in 5.2 + * @experimental in 5.3 */ final class Window implements LimiterStateInterface { diff --git a/src/Symfony/Component/RateLimiter/RateLimit.php b/src/Symfony/Component/RateLimiter/RateLimit.php index 64c706b6e6562..ac6ea882b7bc3 100644 --- a/src/Symfony/Component/RateLimiter/RateLimit.php +++ b/src/Symfony/Component/RateLimiter/RateLimit.php @@ -16,7 +16,7 @@ /** * @author Valentin Silvestre * - * @experimental in 5.2 + * @experimental in 5.3 */ class RateLimit { diff --git a/src/Symfony/Component/RateLimiter/RateLimiterFactory.php b/src/Symfony/Component/RateLimiter/RateLimiterFactory.php index 9fdbe474bf3ef..b27afd9789ed9 100644 --- a/src/Symfony/Component/RateLimiter/RateLimiterFactory.php +++ b/src/Symfony/Component/RateLimiter/RateLimiterFactory.php @@ -25,7 +25,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class RateLimiterFactory { diff --git a/src/Symfony/Component/RateLimiter/Reservation.php b/src/Symfony/Component/RateLimiter/Reservation.php index 4ea54ce5a8816..1921c1af83e20 100644 --- a/src/Symfony/Component/RateLimiter/Reservation.php +++ b/src/Symfony/Component/RateLimiter/Reservation.php @@ -14,7 +14,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class Reservation { diff --git a/src/Symfony/Component/RateLimiter/Storage/CacheStorage.php b/src/Symfony/Component/RateLimiter/Storage/CacheStorage.php index b61539d659243..ada3417b20c0a 100644 --- a/src/Symfony/Component/RateLimiter/Storage/CacheStorage.php +++ b/src/Symfony/Component/RateLimiter/Storage/CacheStorage.php @@ -17,7 +17,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class CacheStorage implements StorageInterface { diff --git a/src/Symfony/Component/RateLimiter/Storage/InMemoryStorage.php b/src/Symfony/Component/RateLimiter/Storage/InMemoryStorage.php index 9f17392b2d2ae..a39a5d42e11f1 100644 --- a/src/Symfony/Component/RateLimiter/Storage/InMemoryStorage.php +++ b/src/Symfony/Component/RateLimiter/Storage/InMemoryStorage.php @@ -16,7 +16,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class InMemoryStorage implements StorageInterface { diff --git a/src/Symfony/Component/RateLimiter/Storage/StorageInterface.php b/src/Symfony/Component/RateLimiter/Storage/StorageInterface.php index 3c5ec6b8a07eb..8191b9e7a005b 100644 --- a/src/Symfony/Component/RateLimiter/Storage/StorageInterface.php +++ b/src/Symfony/Component/RateLimiter/Storage/StorageInterface.php @@ -16,7 +16,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface StorageInterface { diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php index 318fd7bd21193..09b218c0291b6 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php @@ -39,7 +39,7 @@ * @author Ryan Weaver * @author Amaury Leroux de Lens * - * @experimental in 5.2 + * @experimental in 5.3 */ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManagerInterface.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManagerInterface.php index dfbd6dc14659a..b1abce56a9bdd 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManagerInterface.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManagerInterface.php @@ -19,7 +19,7 @@ * @author Wouter de Jong * @author Ryan Weaver * - * @experimental in 5.2 + * @experimental in 5.3 */ interface AuthenticatorManagerInterface { diff --git a/src/Symfony/Component/Security/Http/Authentication/UserAuthenticatorInterface.php b/src/Symfony/Component/Security/Http/Authentication/UserAuthenticatorInterface.php index 4e409b80b9e56..fe32a42a97392 100644 --- a/src/Symfony/Component/Security/Http/Authentication/UserAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Http/Authentication/UserAuthenticatorInterface.php @@ -19,7 +19,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface UserAuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractAuthenticator.php index dd4682ad8ef91..1476b7ce54291 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractAuthenticator.php @@ -22,7 +22,7 @@ * * @author Ryan Weaver * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractAuthenticator implements AuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php index aeaf1d17cd193..deb157784d749 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php @@ -23,7 +23,7 @@ * * @author Ryan Weaver * - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php index a11f2c000aa65..8ccd356ca1d09 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php @@ -33,7 +33,7 @@ * @author Fabien Potencier * * @internal - * @experimental in 5.2 + * @experimental in 5.3 */ abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php b/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php index e89e9c52bcaea..0d7e9cb7bb6f4 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php @@ -24,7 +24,7 @@ * @author Amaury Leroux de Lens * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface AuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php index 246e4894b1abc..234b2dc0a3d5a 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php @@ -41,7 +41,7 @@ * @author Fabien Potencier * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator { diff --git a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php index cd592b74930a1..179e1fdd1a94c 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php @@ -33,7 +33,7 @@ * @author Fabien Potencier * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php index f8524695bacab..7b1630b28ec8e 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php @@ -45,7 +45,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/LoginLinkAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/LoginLinkAuthenticator.php index 84b920ea56643..10efa444b27c5 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/LoginLinkAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/LoginLinkAuthenticator.php @@ -27,7 +27,7 @@ /** * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ final class LoginLinkAuthenticator extends AbstractAuthenticator implements InteractiveAuthenticatorInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/BadgeInterface.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/BadgeInterface.php index b47cad217f654..8e2d222089f4a 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/BadgeInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/BadgeInterface.php @@ -16,7 +16,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface BadgeInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php index 73f9ae5f177c6..dbfef17f9eaf1 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php @@ -21,7 +21,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class CsrfTokenBadge implements BadgeInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php index cfffe9d307b78..76b10f31b3864 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php @@ -22,7 +22,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class PasswordUpgradeBadge implements BadgeInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php index e4ceb6f98d361..f78dedfc75f14 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php @@ -23,7 +23,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class PreAuthenticatedUserBadge implements BadgeInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/RememberMeBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/RememberMeBadge.php index ee890e318ec74..8ce47fce278b3 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/RememberMeBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/RememberMeBadge.php @@ -26,7 +26,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class RememberMeBadge implements BadgeInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php index 10856e4bfe870..a58f86cd13e4c 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php @@ -23,7 +23,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class UserBadge implements BadgeInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CredentialsInterface.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CredentialsInterface.php index 08896cfe5e7ae..bdbbf751481c3 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CredentialsInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CredentialsInterface.php @@ -19,7 +19,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface CredentialsInterface extends BadgeInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CustomCredentials.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CustomCredentials.php index f0407107e6d4d..648e42f6d4881 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CustomCredentials.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/CustomCredentials.php @@ -20,7 +20,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class CustomCredentials implements CredentialsInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/PasswordCredentials.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/PasswordCredentials.php index 30838a836e8bf..e8b95bb8c292e 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/PasswordCredentials.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Credentials/PasswordCredentials.php @@ -22,7 +22,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class PasswordCredentials implements CredentialsInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php index d9b23cd3a79de..6ae34e7a9f239 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php @@ -21,7 +21,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class Passport implements UserPassportInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportInterface.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportInterface.php index e3cdc005ca6d5..15034b20e5e3b 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportInterface.php @@ -23,7 +23,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface PassportInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportTrait.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportTrait.php index e075c42ba8323..1846c80214b8f 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportTrait.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/PassportTrait.php @@ -17,7 +17,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ trait PassportTrait { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/SelfValidatingPassport.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/SelfValidatingPassport.php index c22d01bd4058c..ddce4cad0e8fe 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/SelfValidatingPassport.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/SelfValidatingPassport.php @@ -21,7 +21,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class SelfValidatingPassport extends Passport { diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/UserPassportInterface.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/UserPassportInterface.php index 2d8b57f22b8ba..3c4ef8e9d9f78 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/UserPassportInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/UserPassportInterface.php @@ -18,7 +18,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ interface UserPassportInterface extends PassportInterface { diff --git a/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php b/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php index 70f7d92f9d8f0..b5e5551bfd859 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php @@ -25,7 +25,7 @@ * @author Fabien Potencier * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class X509Authenticator extends AbstractPreAuthenticatedAuthenticator { diff --git a/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php b/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php index c1f649b089ce0..bd0d85fd7a2eb 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php @@ -27,7 +27,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class CheckCredentialsListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Security/Http/EventListener/CsrfProtectionListener.php b/src/Symfony/Component/Security/Http/EventListener/CsrfProtectionListener.php index 6634c51217f41..aac282bb749b1 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CsrfProtectionListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CsrfProtectionListener.php @@ -22,7 +22,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class CsrfProtectionListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php b/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php index 2b4954d71a538..d59b46e619da6 100644 --- a/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php @@ -22,7 +22,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class LoginThrottlingListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php index 81d4c04838619..fb1f229d6f106 100644 --- a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php @@ -23,7 +23,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class PasswordMigratingListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php index ea3d87cc9046d..70e15aa406d66 100644 --- a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php @@ -29,7 +29,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class RememberMeListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php b/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php index 62da75e91b84a..829ac2ff7f938 100644 --- a/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php @@ -22,7 +22,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class UserCheckerListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php index 5b862e6c0a755..715ae675c0bef 100644 --- a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php @@ -22,7 +22,7 @@ * @author Wouter de Jong * * @final - * @experimental in 5.2 + * @experimental in 5.3 */ class UserProviderListener { diff --git a/src/Symfony/Component/Security/Http/Firewall/AuthenticatorManagerListener.php b/src/Symfony/Component/Security/Http/Firewall/AuthenticatorManagerListener.php index df1c22a41aa62..dc51982a3da78 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AuthenticatorManagerListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AuthenticatorManagerListener.php @@ -20,7 +20,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class AuthenticatorManagerListener extends AbstractListener { diff --git a/src/Symfony/Component/Security/Http/LoginLink/Exception/ExpiredLoginLinkException.php b/src/Symfony/Component/Security/Http/LoginLink/Exception/ExpiredLoginLinkException.php index 999128d14ed63..adaba2c715af9 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/Exception/ExpiredLoginLinkException.php +++ b/src/Symfony/Component/Security/Http/LoginLink/Exception/ExpiredLoginLinkException.php @@ -13,7 +13,7 @@ /** * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ class ExpiredLoginLinkException extends \Exception implements InvalidLoginLinkExceptionInterface { diff --git a/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php b/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php index c72506d7f7c8a..6c5c1d869bfac 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php +++ b/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php @@ -17,7 +17,7 @@ * Thrown when a login link is invalid. * * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ class InvalidLoginLinkAuthenticationException extends AuthenticationException { diff --git a/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkException.php b/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkException.php index d4967f44f4514..46f91298fff2a 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkException.php +++ b/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkException.php @@ -13,7 +13,7 @@ /** * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ class InvalidLoginLinkException extends \Exception implements InvalidLoginLinkExceptionInterface { diff --git a/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php b/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php index 77fdd790453f0..57380f6367982 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php +++ b/src/Symfony/Component/Security/Http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php @@ -13,7 +13,7 @@ /** * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ interface InvalidLoginLinkExceptionInterface extends \Throwable { diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkDetails.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkDetails.php index b33011728cf28..a3ef5fa2adf0c 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkDetails.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkDetails.php @@ -13,7 +13,7 @@ /** * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ class LoginLinkDetails { diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php index 5110d1cbca8bb..8c8329a91e97a 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php @@ -22,7 +22,7 @@ /** * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ final class LoginLinkHandler implements LoginLinkHandlerInterface { diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php index 4c460baa1321a..4c7bd694285e1 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php @@ -18,7 +18,7 @@ * A class that is able to create and handle "magic" login links. * * @author Ryan Weaver - * @experimental in 5.2 + * @experimental in 5.3 */ interface LoginLinkHandlerInterface { diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php index 1b6d4eca17afb..5e09537dda3bf 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php @@ -26,7 +26,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ class LoginLinkNotification extends Notification implements EmailNotificationInterface, SmsNotificationInterface { diff --git a/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php b/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php index cdf7109cf3ad4..c038a434bb80b 100644 --- a/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php +++ b/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php @@ -24,7 +24,7 @@ * * @author Wouter de Jong * - * @experimental in 5.2 + * @experimental in 5.3 */ final class DefaultLoginRateLimiter extends AbstractRequestRateLimiter { diff --git a/src/Symfony/Component/Semaphore/Exception/ExceptionInterface.php b/src/Symfony/Component/Semaphore/Exception/ExceptionInterface.php index 616e0208cb8d1..c56de90ef47c4 100644 --- a/src/Symfony/Component/Semaphore/Exception/ExceptionInterface.php +++ b/src/Symfony/Component/Semaphore/Exception/ExceptionInterface.php @@ -14,7 +14,7 @@ /** * Base ExceptionInterface for the Semaphore Component. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Jérémy Derussé */ diff --git a/src/Symfony/Component/Semaphore/Exception/InvalidArgumentException.php b/src/Symfony/Component/Semaphore/Exception/InvalidArgumentException.php index 1eca46a4e3fd0..2ee5899b52f06 100644 --- a/src/Symfony/Component/Semaphore/Exception/InvalidArgumentException.php +++ b/src/Symfony/Component/Semaphore/Exception/InvalidArgumentException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Semaphore\Exception; /** - * @experimental in 5.2 + * @experimental in 5.3 * * @author Jérémy Derussé */ diff --git a/src/Symfony/Component/Semaphore/Exception/RuntimeException.php b/src/Symfony/Component/Semaphore/Exception/RuntimeException.php index 5119ae68db185..dbbeb5b4ade84 100644 --- a/src/Symfony/Component/Semaphore/Exception/RuntimeException.php +++ b/src/Symfony/Component/Semaphore/Exception/RuntimeException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Semaphore\Exception; /** - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau */ diff --git a/src/Symfony/Component/Semaphore/Exception/SemaphoreAcquiringException.php b/src/Symfony/Component/Semaphore/Exception/SemaphoreAcquiringException.php index d54c1af1ceea1..90c218508fe02 100644 --- a/src/Symfony/Component/Semaphore/Exception/SemaphoreAcquiringException.php +++ b/src/Symfony/Component/Semaphore/Exception/SemaphoreAcquiringException.php @@ -16,7 +16,7 @@ /** * SemaphoreAcquiringException is thrown when an issue happens during the acquisition of a semaphore. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Jérémy Derussé * @author Grégoire Pineau diff --git a/src/Symfony/Component/Semaphore/Exception/SemaphoreExpiredException.php b/src/Symfony/Component/Semaphore/Exception/SemaphoreExpiredException.php index 695df079bf90b..0a5d3077cda06 100644 --- a/src/Symfony/Component/Semaphore/Exception/SemaphoreExpiredException.php +++ b/src/Symfony/Component/Semaphore/Exception/SemaphoreExpiredException.php @@ -16,7 +16,7 @@ /** * SemaphoreExpiredException is thrown when a semaphore may conflict due to a TTL expiration. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Jérémy Derussé * @author Grégoire Pineau diff --git a/src/Symfony/Component/Semaphore/Exception/SemaphoreReleasingException.php b/src/Symfony/Component/Semaphore/Exception/SemaphoreReleasingException.php index a9979815d5f7c..e1316adabf3aa 100644 --- a/src/Symfony/Component/Semaphore/Exception/SemaphoreReleasingException.php +++ b/src/Symfony/Component/Semaphore/Exception/SemaphoreReleasingException.php @@ -16,7 +16,7 @@ /** * SemaphoreReleasingException is thrown when an issue happens during the release of a semaphore. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Jérémy Derussé * @author Grégoire Pineau diff --git a/src/Symfony/Component/Semaphore/Key.php b/src/Symfony/Component/Semaphore/Key.php index b2ba8db50acea..03368609640e0 100644 --- a/src/Symfony/Component/Semaphore/Key.php +++ b/src/Symfony/Component/Semaphore/Key.php @@ -16,7 +16,7 @@ /** * Key is a container for the state of the semaphores in stores. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau * @author Jérémy Derussé diff --git a/src/Symfony/Component/Semaphore/PersistingStoreInterface.php b/src/Symfony/Component/Semaphore/PersistingStoreInterface.php index df322d1355dbe..406c7dd61bf27 100644 --- a/src/Symfony/Component/Semaphore/PersistingStoreInterface.php +++ b/src/Symfony/Component/Semaphore/PersistingStoreInterface.php @@ -16,7 +16,7 @@ use Symfony\Component\Semaphore\Exception\SemaphoreReleasingException; /** - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau * @author Jérémy Derussé diff --git a/src/Symfony/Component/Semaphore/Semaphore.php b/src/Symfony/Component/Semaphore/Semaphore.php index a65e2bc289996..cc128e55f17cf 100644 --- a/src/Symfony/Component/Semaphore/Semaphore.php +++ b/src/Symfony/Component/Semaphore/Semaphore.php @@ -23,7 +23,7 @@ /** * Semaphore is the default implementation of the SemaphoreInterface. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau * @author Jérémy Derussé diff --git a/src/Symfony/Component/Semaphore/SemaphoreFactory.php b/src/Symfony/Component/Semaphore/SemaphoreFactory.php index 9194817f096e3..230f53b7cf9bc 100644 --- a/src/Symfony/Component/Semaphore/SemaphoreFactory.php +++ b/src/Symfony/Component/Semaphore/SemaphoreFactory.php @@ -18,7 +18,7 @@ /** * Factory provides method to create semaphores. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau * @author Jérémy Derussé diff --git a/src/Symfony/Component/Semaphore/SemaphoreInterface.php b/src/Symfony/Component/Semaphore/SemaphoreInterface.php index cbc1f36db4cef..b1cd4ceeca5db 100644 --- a/src/Symfony/Component/Semaphore/SemaphoreInterface.php +++ b/src/Symfony/Component/Semaphore/SemaphoreInterface.php @@ -17,7 +17,7 @@ /** * SemaphoreInterface defines an interface to manipulate the status of a semaphore. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Jérémy Derussé * @author Grégoire Pineau diff --git a/src/Symfony/Component/Semaphore/Store/RedisStore.php b/src/Symfony/Component/Semaphore/Store/RedisStore.php index 0aae297715074..0523c9ab60edc 100644 --- a/src/Symfony/Component/Semaphore/Store/RedisStore.php +++ b/src/Symfony/Component/Semaphore/Store/RedisStore.php @@ -22,7 +22,7 @@ /** * RedisStore is a PersistingStoreInterface implementation using Redis as store engine. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau * @author Jérémy Derussé diff --git a/src/Symfony/Component/Semaphore/Store/StoreFactory.php b/src/Symfony/Component/Semaphore/Store/StoreFactory.php index c42eda627e137..e3a693f8e02a5 100644 --- a/src/Symfony/Component/Semaphore/Store/StoreFactory.php +++ b/src/Symfony/Component/Semaphore/Store/StoreFactory.php @@ -21,7 +21,7 @@ /** * StoreFactory create stores and connections. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Jérémy Derussé * @author Jérémy Derussé diff --git a/src/Symfony/Component/Uid/AbstractUid.php b/src/Symfony/Component/Uid/AbstractUid.php index f9455533d3df8..54732773dfefb 100644 --- a/src/Symfony/Component/Uid/AbstractUid.php +++ b/src/Symfony/Component/Uid/AbstractUid.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Uid; /** - * @experimental in 5.2 + * @experimental in 5.3 * * @author Nicolas Grekas */ diff --git a/src/Symfony/Component/Uid/NilUuid.php b/src/Symfony/Component/Uid/NilUuid.php index 6ff45626fdcf0..52362611fd429 100644 --- a/src/Symfony/Component/Uid/NilUuid.php +++ b/src/Symfony/Component/Uid/NilUuid.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Uid; /** - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau */ diff --git a/src/Symfony/Component/Uid/Ulid.php b/src/Symfony/Component/Uid/Ulid.php index efc50a1b1f24b..0fca2459e6f57 100644 --- a/src/Symfony/Component/Uid/Ulid.php +++ b/src/Symfony/Component/Uid/Ulid.php @@ -16,7 +16,7 @@ * * @see https://github.com/ulid/spec * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Nicolas Grekas */ diff --git a/src/Symfony/Component/Uid/Uuid.php b/src/Symfony/Component/Uid/Uuid.php index 1c75262c57251..1f4e88954051f 100644 --- a/src/Symfony/Component/Uid/Uuid.php +++ b/src/Symfony/Component/Uid/Uuid.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Uid; /** - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau */ diff --git a/src/Symfony/Component/Uid/UuidV1.php b/src/Symfony/Component/Uid/UuidV1.php index e9eddc531ec34..0fb7d6ff7c028 100644 --- a/src/Symfony/Component/Uid/UuidV1.php +++ b/src/Symfony/Component/Uid/UuidV1.php @@ -14,7 +14,7 @@ /** * A v1 UUID contains a 60-bit timestamp and 62 extra unique bits. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau */ diff --git a/src/Symfony/Component/Uid/UuidV3.php b/src/Symfony/Component/Uid/UuidV3.php index 735bb81452315..1c5d197d2650c 100644 --- a/src/Symfony/Component/Uid/UuidV3.php +++ b/src/Symfony/Component/Uid/UuidV3.php @@ -16,7 +16,7 @@ * * Use Uuid::v3() to compute one. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau */ diff --git a/src/Symfony/Component/Uid/UuidV4.php b/src/Symfony/Component/Uid/UuidV4.php index 65d639fabbe3f..4e55d69bee941 100644 --- a/src/Symfony/Component/Uid/UuidV4.php +++ b/src/Symfony/Component/Uid/UuidV4.php @@ -14,7 +14,7 @@ /** * A v4 UUID contains a 122-bit random number. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau */ diff --git a/src/Symfony/Component/Uid/UuidV5.php b/src/Symfony/Component/Uid/UuidV5.php index cf98a441b7c7c..f2c3f92090823 100644 --- a/src/Symfony/Component/Uid/UuidV5.php +++ b/src/Symfony/Component/Uid/UuidV5.php @@ -16,7 +16,7 @@ * * Use Uuid::v5() to compute one. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Grégoire Pineau */ diff --git a/src/Symfony/Component/Uid/UuidV6.php b/src/Symfony/Component/Uid/UuidV6.php index 4575efcc270a3..b395063847794 100644 --- a/src/Symfony/Component/Uid/UuidV6.php +++ b/src/Symfony/Component/Uid/UuidV6.php @@ -16,7 +16,7 @@ * * Unlike UUIDv1, this implementation of UUIDv6 doesn't leak the MAC address of the host. * - * @experimental in 5.2 + * @experimental in 5.3 * * @author Nicolas Grekas */ From de0dbd88d2dc5b808570595cdf80559acc294459 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 2 Dec 2020 19:36:59 +0100 Subject: [PATCH 017/188] [DependencyInjection] Add missing parameter type declarations to loader classes. --- .../DependencyInjection/Fixtures/php/argon2i_encoder.php | 2 +- .../DependencyInjection/Fixtures/php/bcrypt_encoder.php | 2 +- .../Fixtures/php/migrating_encoder.php | 2 +- .../Component/DependencyInjection/Loader/FileLoader.php | 8 +++----- .../DependencyInjection/Loader/PhpFileLoader.php | 2 +- .../Tests/Loader/GlobFileLoaderTest.php | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php index 7276f97e6b6f3..ddac043692cf1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php @@ -1,6 +1,6 @@ load('container1.php', $container); +$this->load('container1.php'); $container->loadFromExtension('security', [ 'encoders' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_encoder.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_encoder.php index 1afad79e4fca3..d4511aeb554c7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_encoder.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_encoder.php @@ -1,6 +1,6 @@ load('container1.php', $container); +$this->load('container1.php'); $container->loadFromExtension('security', [ 'encoders' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_encoder.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_encoder.php index 14c008be9d8d0..c7ad9f02ab4f5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_encoder.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_encoder.php @@ -1,6 +1,6 @@ load('container1.php', $container); +$this->load('container1.php'); $container->loadFromExtension('security', [ 'encoders' => [ diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index d553ca7072dd6..f8f9fc4523206 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -50,7 +50,7 @@ public function __construct(ContainerBuilder $container, FileLocatorInterface $l * * @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, $type = null, $ignoreErrors = false, $sourceResource = null, $exclude = null) + public function import($resource, string $type = null, $ignoreErrors = false, string $sourceResource = null, $exclude = null) { $args = \func_get_args(); @@ -87,7 +87,7 @@ public function import($resource, $type = null, $ignoreErrors = false, $sourceRe * @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 */ - public function registerClasses(Definition $prototype, $namespace, $resource, $exclude = null) + public function registerClasses(Definition $prototype, string $namespace, string $resource, $exclude = null) { if ('\\' !== substr($namespace, -1)) { throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": "%s".', $namespace)); @@ -134,10 +134,8 @@ public function registerAliasesForSinglyImplementedInterfaces() /** * Registers a definition in the container with its instanceof-conditionals. - * - * @param string $id */ - protected function setDefinition($id, Definition $definition) + protected function setDefinition(string $id, Definition $definition) { $this->container->removeBindings($id); diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index 196baf622b75d..5c7b628eab3ad 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -28,7 +28,7 @@ class PhpFileLoader extends FileLoader /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { // the container and loader variables are exposed to the included file below $container = $this->container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php index 493e935611288..c7557ee6908f8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php @@ -38,7 +38,7 @@ public function testLoadAddsTheGlobResourceToTheContainer() class GlobFileLoaderWithoutImport extends GlobFileLoader { - public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null, $exclude = null) + public function import($resource, string $type = null, $ignoreErrors = false, string $sourceResource = null, $exclude = null) { } } From ce77be2507631cd12e4ca37510dab37f4c2b759a Mon Sep 17 00:00:00 2001 From: vudaltsov Date: Sat, 5 Dec 2020 04:31:35 +0300 Subject: [PATCH 018/188] [Form] Changed DataMapperInterface $forms parameter type to \Traversable --- UPGRADE-5.3.md | 14 +++++++ UPGRADE-6.0.md | 6 +++ src/Symfony/Component/Form/CHANGELOG.md | 12 ++++++ .../Component/Form/DataMapperInterface.php | 14 +++---- .../Core/DataMapper/CheckboxListMapper.php | 8 ++++ .../Extension/Core/DataMapper/DataMapper.php | 8 ++++ .../Core/DataMapper/RadioListMapper.php | 8 ++++ .../Core/DataMapper/DataMapperTest.php | 38 +++++++++---------- 8 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 UPGRADE-5.3.md diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md new file mode 100644 index 0000000000000..69532a9397f4d --- /dev/null +++ b/UPGRADE-5.3.md @@ -0,0 +1,14 @@ +UPGRADE FROM 5.2 to 5.3 +======================= + +Form +---- + + * Changed `$forms` parameter type of the `DataMapperInterface::mapDataToForms()` method from `iterable` to `\Traversable`. + * Changed `$forms` parameter type of the `DataMapperInterface::mapFormsToData()` method from `iterable` to `\Traversable`. + * Deprecated passing an array as the second argument of the `DataMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `DataMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Deprecated passing an array as the second argument of the `CheckboxListMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index ba4be59b39a9d..3eda1d0f68e14 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -49,6 +49,12 @@ Form * The `Symfony\Component\Form\Extension\Validator\Util\ServerParams` class has been removed, use its parent `Symfony\Component\Form\Util\ServerParams` instead. * The `NumberToLocalizedStringTransformer::ROUND_*` constants have been removed, use `\NumberFormatter::ROUND_*` instead. * Removed `PropertyPathMapper` in favor of `DataMapper` and `PropertyPathAccessor`. + * Changed `$forms` parameter type of the `DataMapper::mapDataToForms()` method from `iterable` to `\Traversable`. + * Changed `$forms` parameter type of the `DataMapper::mapFormsToData()` method from `iterable` to `\Traversable`. + * Changed `$checkboxes` parameter type of the `CheckboxListMapper::mapDataToForms()` method from `iterable` to `\Traversable`. + * Changed `$checkboxes` parameter type of the `CheckboxListMapper::mapFormsToData()` method from `iterable` to `\Traversable`. + * Changed `$radios` parameter type of the `RadioListMapper::mapDataToForms()` method from `iterable` to `\Traversable`. + * Changed `$radios` parameter type of the `RadioListMapper::mapFormsToData()` method from `iterable` to `\Traversable`. FrameworkBundle --------------- diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 53450657d1214..d95d11176a42c 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,6 +1,18 @@ CHANGELOG ========= +5.3.0 +----- + + * Changed `$forms` parameter type of the `DataMapperInterface::mapDataToForms()` method from `iterable` to `\Traversable`. + * Changed `$forms` parameter type of the `DataMapperInterface::mapFormsToData()` method from `iterable` to `\Traversable`. + * Deprecated passing an array as the second argument of the `DataMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `DataMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Deprecated passing an array as the second argument of the `CheckboxListMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. + 5.2.0 ----- diff --git a/src/Symfony/Component/Form/DataMapperInterface.php b/src/Symfony/Component/Form/DataMapperInterface.php index 1e0583aa08fff..c2cd3601e4b5b 100644 --- a/src/Symfony/Component/Form/DataMapperInterface.php +++ b/src/Symfony/Component/Form/DataMapperInterface.php @@ -22,12 +22,12 @@ interface DataMapperInterface * The method is responsible for calling {@link FormInterface::setData()} * on the children of compound forms, defining their underlying model data. * - * @param mixed $viewData View data of the compound form being initialized - * @param FormInterface[]|iterable $forms A list of {@link FormInterface} instances + * @param mixed $viewData View data of the compound form being initialized + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapDataToForms($viewData, iterable $forms); + public function mapDataToForms($viewData, \Traversable $forms); /** * Maps the model data of a list of children forms into the view data of their parent. @@ -52,11 +52,11 @@ public function mapDataToForms($viewData, iterable $forms); * The model data can be an array or an object, so this second argument is always passed * by reference. * - * @param FormInterface[]|iterable $forms A list of {@link FormInterface} instances - * @param mixed $viewData The compound form's view data that get mapped - * its children model data + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * @param mixed $viewData The compound form's view data that get mapped + * its children model data * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapFormsToData(iterable $forms, &$viewData); + public function mapFormsToData(\Traversable $forms, &$viewData); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php index f03d1ad86a006..ecfd83a066a8d 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php @@ -30,6 +30,10 @@ class CheckboxListMapper implements DataMapperInterface */ public function mapDataToForms($choices, iterable $checkboxes) { + if (\is_array($checkboxes)) { + trigger_deprecation('symfony/form', '5.3', 'Passing an array as the second argument of the "%s()" method is deprecated, pass "\Traversable" instead.', __METHOD__); + } + if (null === $choices) { $choices = []; } @@ -49,6 +53,10 @@ public function mapDataToForms($choices, iterable $checkboxes) */ public function mapFormsToData(iterable $checkboxes, &$choices) { + if (\is_array($checkboxes)) { + trigger_deprecation('symfony/form', '5.3', 'Passing an array as the first argument of the "%s()" method is deprecated, pass "\Traversable" instead.', __METHOD__); + } + if (!\is_array($choices)) { throw new UnexpectedTypeException($choices, 'array'); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php index f7cb81f34a493..5f4c498a33526 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php @@ -40,6 +40,10 @@ public function __construct(DataAccessorInterface $dataAccessor = null) */ public function mapDataToForms($data, iterable $forms): void { + if (\is_array($forms)) { + trigger_deprecation('symfony/form', '5.3', 'Passing an array as the second argument of the "%s()" method is deprecated, pass "\Traversable" instead.', __METHOD__); + } + $empty = null === $data || [] === $data; if (!$empty && !\is_array($data) && !\is_object($data)) { @@ -62,6 +66,10 @@ public function mapDataToForms($data, iterable $forms): void */ public function mapFormsToData(iterable $forms, &$data): void { + if (\is_array($forms)) { + trigger_deprecation('symfony/form', '5.3', 'Passing an array as the first argument of the "%s()" method is deprecated, pass "\Traversable" instead.', __METHOD__); + } + if (null === $data) { return; } diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php index bb34c912a2ebe..b54adfa5d1ba1 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php @@ -30,6 +30,10 @@ class RadioListMapper implements DataMapperInterface */ public function mapDataToForms($choice, iterable $radios) { + if (\is_array($radios)) { + trigger_deprecation('symfony/form', '5.3', 'Passing an array as the second argument of the "%s()" method is deprecated, pass "\Traversable" instead.', __METHOD__); + } + if (!\is_string($choice)) { throw new UnexpectedTypeException($choice, 'string'); } @@ -45,6 +49,10 @@ public function mapDataToForms($choice, iterable $radios) */ public function mapFormsToData(iterable $radios, &$choice) { + if (\is_array($radios)) { + trigger_deprecation('symfony/form', '5.3', 'Passing an array as the first argument of the "%s()" method is deprecated, pass "\Traversable" instead.', __METHOD__); + } + if (null !== $choice && !\is_string($choice)) { throw new UnexpectedTypeException($choice, 'null or string'); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php index b20b827fdbb5a..bc6efb6d3bdc5 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php @@ -50,7 +50,7 @@ public function testMapDataToFormsPassesObjectRefIfByReference() $config->setPropertyPath($propertyPath); $form = new Form($config); - $this->mapper->mapDataToForms($car, [$form]); + $this->mapper->mapDataToForms($car, new \ArrayIterator([$form])); self::assertSame($engine, $form->getData()); } @@ -68,7 +68,7 @@ public function testMapDataToFormsPassesObjectCloneIfNotByReference() $config->setPropertyPath($propertyPath); $form = new Form($config); - $this->mapper->mapDataToForms($car, [$form]); + $this->mapper->mapDataToForms($car, new \ArrayIterator([$form])); self::assertNotSame($engine, $form->getData()); self::assertEquals($engine, $form->getData()); @@ -84,7 +84,7 @@ public function testMapDataToFormsIgnoresEmptyPropertyPath() self::assertNull($form->getPropertyPath()); - $this->mapper->mapDataToForms($car, [$form]); + $this->mapper->mapDataToForms($car, new \ArrayIterator([$form])); self::assertNull($form->getData()); } @@ -101,7 +101,7 @@ public function testMapDataToFormsIgnoresUnmapped() $config->setPropertyPath($propertyPath); $form = new Form($config); - $this->mapper->mapDataToForms($car, [$form]); + $this->mapper->mapDataToForms($car, new \ArrayIterator([$form])); self::assertNull($form->getData()); } @@ -117,7 +117,7 @@ public function testMapDataToFormsIgnoresUninitializedProperties() $car = new TypehintedPropertiesCar(); $car->engine = 'BMW'; - $this->mapper->mapDataToForms($car, [$engineForm, $colorForm]); + $this->mapper->mapDataToForms($car, new \ArrayIterator([$engineForm, $colorForm])); self::assertSame($car->engine, $engineForm->getData()); self::assertNull($colorForm->getData()); @@ -135,7 +135,7 @@ public function testMapDataToFormsSetsDefaultDataIfPassedDataIsNull() $form = new Form($config); - $this->mapper->mapDataToForms(null, [$form]); + $this->mapper->mapDataToForms(null, new \ArrayIterator([$form])); self::assertSame($default, $form->getData()); } @@ -152,7 +152,7 @@ public function testMapDataToFormsSetsDefaultDataIfPassedDataIsEmptyArray() $form = new Form($config); - $this->mapper->mapDataToForms([], [$form]); + $this->mapper->mapDataToForms([], new \ArrayIterator([$form])); self::assertSame($default, $form->getData()); } @@ -171,7 +171,7 @@ public function testMapFormsToDataWritesBackIfNotByReference() $config->setData($engine); $form = new SubmittedForm($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertEquals($engine, $car->engine); self::assertNotSame($engine, $car->engine); @@ -190,7 +190,7 @@ public function testMapFormsToDataWritesBackIfByReferenceButNoReference() $config->setData($engine); $form = new SubmittedForm($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame($engine, $car->engine); } @@ -209,7 +209,7 @@ public function testMapFormsToDataWritesBackIfByReferenceAndReference() $car->engine = 'Rolls-Royce'; - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame('Rolls-Royce', $car->engine); } @@ -229,7 +229,7 @@ public function testMapFormsToDataIgnoresUnmapped() $config->setMapped(false); $form = new SubmittedForm($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame($initialEngine, $car->engine); } @@ -248,7 +248,7 @@ public function testMapFormsToDataIgnoresUnsubmittedForms() $config->setData($engine); $form = new Form($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame($initialEngine, $car->engine); } @@ -266,7 +266,7 @@ public function testMapFormsToDataIgnoresEmptyData() $config->setData(null); $form = new Form($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame($initialEngine, $car->engine); } @@ -285,7 +285,7 @@ public function testMapFormsToDataIgnoresUnsynchronized() $config->setData($engine); $form = new NotSynchronizedForm($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame($initialEngine, $car->engine); } @@ -305,7 +305,7 @@ public function testMapFormsToDataIgnoresDisabled() $config->setDisabled(true); $form = new SubmittedForm($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame($initialEngine, $car->engine); } @@ -320,7 +320,7 @@ public function testMapFormsToUninitializedProperties() $config->setData('BMW'); $form = new SubmittedForm($config); - $this->mapper->mapFormsToData([$form], $car); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $car); self::assertSame('BMW', $car->engine); } @@ -342,7 +342,7 @@ public function testMapFormsToDataDoesNotChangeEqualDateTimeInstance($date) $config->setData($publishedAt); $form = new SubmittedForm($config); - $this->mapper->mapFormsToData([$form], $article); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $article); self::assertSame($publishedAtValue, $article['publishedAt']); } @@ -367,7 +367,7 @@ public function testMapDataToFormsUsingGetCallbackOption() ]); $form = new Form($config); - $this->mapper->mapDataToForms($person, [$form]); + $this->mapper->mapDataToForms($person, new \ArrayIterator([$form])); self::assertSame($initialName, $form->getData()); } @@ -384,7 +384,7 @@ public function testMapFormsToDataUsingSetCallbackOption() $config->setData('Jane Doe'); $form = new SubmittedForm($config); - $this->mapper->mapFormsToData([$form], $person); + $this->mapper->mapFormsToData(new \ArrayIterator([$form]), $person); self::assertSame('Jane Doe', $person->myName()); } From ab31ee9966356fefef5287d6a525e26544b52cb6 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sat, 5 Dec 2020 12:39:38 +0100 Subject: [PATCH 019/188] Search for pattern on debug:event-dispatcher --- .../Command/EventDispatcherDebugCommand.php | 35 +++++++++++++++---- .../Console/Descriptor/JsonDescriptor.php | 9 ++--- .../Console/Descriptor/MarkdownDescriptor.php | 5 ++- .../Console/Descriptor/TextDescriptor.php | 5 +-- .../Console/Descriptor/XmlDescriptor.php | 9 +++-- 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index ad49cdeeaa87f..5ff002d3c13e0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -46,7 +46,7 @@ protected function configure() { $this ->setDefinition([ - new InputArgument('event', InputArgument::OPTIONAL, 'An event name'), + new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), ]) @@ -75,13 +75,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int $options = []; if ($event = $input->getArgument('event')) { - if (!$this->dispatcher->hasListeners($event)) { - $io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); - - return 0; + if ($this->dispatcher->hasListeners($event)) { + $options = ['event' => $event]; + } else { + // if there is no direct match, try find partial matches + $events = $this->searchForEvent($this->dispatcher, $event); + if (0 === \count($events)) { + $io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); + + return 0; + } elseif (1 === \count($events)) { + $options = ['event' => $events[array_key_first($events)]]; + } else { + $options = ['events' => $events]; + } } - - $options = ['event' => $event]; } $helper = new DescriptorHelper(); @@ -92,4 +100,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + + private function searchForEvent(EventDispatcherInterface $dispatcher, $needle): array + { + $output = []; + $allEvents = array_keys($dispatcher->getListeners()); + foreach ($allEvents as $event) { + if (str_contains($event, $needle)) { + $output[] = $event; + } + } + + return $output; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 3eb98fc921d68..401cd2d407b2c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -135,7 +135,7 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { - $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null), $options); + $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options); } protected function describeCallable($callable, array $options = []) @@ -274,18 +274,19 @@ private function getContainerAliasData(Alias $alias): array ]; } - private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, string $event = null): array + private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, array $options): array { $data = []; + $event = \array_key_exists('event', $options) ? $options['event'] : null; - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { - foreach ($registeredListeners as $listener) { + foreach ($eventDispatcher->getListeners($event) as $listener) { $l = $this->getCallableData($listener); $l['priority'] = $eventDispatcher->getListenerPriority($event, $listener); $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(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 7bbdf48e17d45..5b97dab46218c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -291,11 +291,14 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev $title = 'Registered listeners'; if (null !== $event) { $title .= sprintf(' for event `%s` ordered by descending priority', $event); + $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(); } $this->write(sprintf('# %s', $title)."\n"); - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { foreach ($registeredListeners as $order => $listener) { $this->write("\n".sprintf('## Listener %d', $order + 1)."\n"); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index c43a7a6e60deb..d68b4c42827b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -477,13 +477,14 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev if (null !== $event) { $title = sprintf('Registered Listeners for "%s" Event', $event); + $registeredListeners = $eventDispatcher->getListeners($event); } else { $title = 'Registered Listeners 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(); } $options['output']->title($title); - - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { $this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']); } else { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 279c52c4eb278..b7b61731203c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -89,7 +89,7 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { - $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null)); + $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options)); } protected function describeCallable($callable, array $options = []) @@ -454,15 +454,18 @@ private function getContainerParameterDocument($parameter, array $options = []): return $dom; } - private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, string $event = null): \DOMDocument + private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, array $options): \DOMDocument { + $event = \array_key_exists('event', $options) ? $options['event'] : null; $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher')); - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { + $registeredListeners = $eventDispatcher->getListeners($event); $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(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { From a4b26061a98fc7cbf08cca218e75646114b53895 Mon Sep 17 00:00:00 2001 From: cesar Date: Mon, 9 Nov 2020 18:39:08 -0300 Subject: [PATCH 020/188] Extracting ProgressBar's format's magic strings into const --- .../Component/Console/Helper/ProgressBar.php | 34 ++++++++++++------- .../Console/Tests/Helper/ProgressBarTest.php | 8 ++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 7b6b99e431859..35f733be34a5f 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -26,6 +26,16 @@ */ final class ProgressBar { + public const FORMAT_VERBOSE = 'verbose'; + public const FORMAT_VERY_VERBOSE = 'very_verbose'; + public const FORMAT_DEBUG = 'debug'; + public const FORMAT_NORMAL = 'normal'; + + private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax'; + private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax'; + private const FORMAT_DEBUG_NOMAX = 'debug_nomax'; + private const FORMAT_NORMAL_NOMAX = 'normal_nomax'; + private $barWidth = 28; private $barChar; private $emptyBarChar = '-'; @@ -489,13 +499,13 @@ private function determineBestFormat(): string switch ($this->output->getVerbosity()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway case OutputInterface::VERBOSITY_VERBOSE: - return $this->max ? 'verbose' : 'verbose_nomax'; + return $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX; case OutputInterface::VERBOSITY_VERY_VERBOSE: - return $this->max ? 'very_verbose' : 'very_verbose_nomax'; + return $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX; case OutputInterface::VERBOSITY_DEBUG: - return $this->max ? 'debug' : 'debug_nomax'; + return $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX; default: - return $this->max ? 'normal' : 'normal_nomax'; + return $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX; } } @@ -547,17 +557,17 @@ private static function initPlaceholderFormatters(): array private static function initFormats(): array { return [ - 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', - 'normal_nomax' => ' %current% [%bar%]', + self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%', + self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]', - 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', - 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', - 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', - 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', - 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', - 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%', ]; } diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index b5d2b5f6e2645..7a1e9ee87f1bd 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -210,7 +210,7 @@ public function testFormat() // max in construct, explicit format before $bar = new ProgressBar($output = $this->getOutputStream(), 10, 0); - $bar->setFormat('normal'); + $bar->setFormat(ProgressBar::FORMAT_NORMAL); $bar->start(); $bar->advance(10); $bar->finish(); @@ -220,7 +220,7 @@ public function testFormat() // max in start, explicit format before $bar = new ProgressBar($output = $this->getOutputStream(), 0, 0); - $bar->setFormat('normal'); + $bar->setFormat(ProgressBar::FORMAT_NORMAL); $bar->start(10); $bar->advance(10); $bar->finish(); @@ -828,7 +828,7 @@ public function testAnsiColorsAndEmojis() public function testSetFormat() { $bar = new ProgressBar($output = $this->getOutputStream(), 0, 0); - $bar->setFormat('normal'); + $bar->setFormat(ProgressBar::FORMAT_NORMAL); $bar->start(); rewind($output->getStream()); $this->assertEquals( @@ -837,7 +837,7 @@ public function testSetFormat() ); $bar = new ProgressBar($output = $this->getOutputStream(), 10, 0); - $bar->setFormat('normal'); + $bar->setFormat(ProgressBar::FORMAT_NORMAL); $bar->start(); rewind($output->getStream()); $this->assertEquals( From 45519f2e9eb9393980fe1cc7136138a791c7bcc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 6 Nov 2020 15:54:42 +0100 Subject: [PATCH 021/188] [FrameworkBundle] Added support for configuring PHP error level to log level This commit allow the following configuration ```yaml php_errors: log: !php/const \E_DEPRECATED: !php/const Psr\Log\LogLevel::ERROR !php/const \E_USER_DEPRECATED: !php/const Psr\Log\LogLevel::ERROR !php/const \E_NOTICE: !php/const Psr\Log\LogLevel::ERROR !php/const \E_USER_NOTICE: !php/const Psr\Log\LogLevel::ERROR !php/const \E_STRICT: !php/const Psr\Log\LogLevel::ERROR !php/const \E_WARNING: !php/const Psr\Log\LogLevel::ERROR !php/const \E_USER_WARNING: !php/const Psr\Log\LogLevel::ERROR !php/const \E_COMPILE_WARNING: !php/const Psr\Log\LogLevel::ERROR !php/const \E_CORE_WARNING: !php/const Psr\Log\LogLevel::ERROR !php/const \E_USER_ERROR: !php/const Psr\Log\LogLevel::CRITICAL !php/const \E_RECOVERABLE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL !php/const \E_COMPILE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL !php/const \E_PARSE: !php/const Psr\Log\LogLevel::CRITICAL !php/const \E_ERROR: !php/const Psr\Log\LogLevel::CRITICAL !php/const \E_CORE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL ``` --- .../Bundle/FrameworkBundle/CHANGELOG.md | 5 ++++ .../DependencyInjection/Configuration.php | 25 ++++++++++++++++--- .../Resources/config/schema/symfony-1.0.xsd | 8 ++++++ .../Fixtures/php/php_errors_log_levels.php | 10 ++++++++ .../Fixtures/xml/php_errors_log_levels.xml | 14 +++++++++++ .../Fixtures/yml/php_errors_log_levels.yml | 5 ++++ .../FrameworkExtensionTest.php | 12 +++++++++ 7 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/php_errors_log_levels.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/php_errors_log_levels.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/php_errors_log_levels.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index c4b5786744fb3..26519c7cc2943 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * Added support for configuring PHP error level to log levels + 5.2.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 346047062201b..e0a073c88f6f1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1070,14 +1070,31 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) ->info('PHP errors handling configuration') ->addDefaultsIfNotSet() ->children() - ->scalarNode('log') + ->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.') + ->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() + ->then(function (array $v): array { + if (!($v[0]['type'] ?? false)) { + return $v; + } + + // Fix XML normalization + + $ret = []; + foreach ($v as ['type' => $type, 'logLevel' => $logLevel]) { + $ret[$type] = $logLevel; + } + + return $ret; + }) + ->end() ->validate() - ->ifTrue(function ($v) { return !(\is_int($v) || \is_bool($v)); }) - ->thenInvalid('The "php_errors.log" parameter should be either an integer or a boolean.') + ->ifTrue(function ($v) { return !(\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() ->booleanNode('throw') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 61410d874cd05..c92fd3cd3b2b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -313,10 +313,18 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/php_errors_log_levels.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/php_errors_log_levels.php new file mode 100644 index 0000000000000..e4560ac45f690 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/php_errors_log_levels.php @@ -0,0 +1,10 @@ +loadFromExtension('framework', [ + 'php_errors' => [ + 'log' => [ + \E_NOTICE => \Psr\Log\LogLevel::ERROR, + \E_WARNING => \Psr\Log\LogLevel::ERROR, + ] + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/php_errors_log_levels.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/php_errors_log_levels.xml new file mode 100644 index 0000000000000..1b6642a575c4c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/php_errors_log_levels.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/php_errors_log_levels.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/php_errors_log_levels.yml new file mode 100644 index 0000000000000..ad9fd30667de2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/php_errors_log_levels.yml @@ -0,0 +1,5 @@ +framework: + php_errors: + log: + !php/const \E_NOTICE: !php/const Psr\Log\LogLevel::ERROR + !php/const \E_WARNING: !php/const Psr\Log\LogLevel::ERROR diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 0fc7b47d561d4..9efb52f327efb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -498,6 +498,18 @@ public function testPhpErrorsWithLogLevel() $this->assertSame(8, $definition->getArgument(2)); } + public function testPhpErrorsWithLogLevels() + { + $container = $this->createContainerFromFile('php_errors_log_levels'); + + $definition = $container->getDefinition('debug.debug_handlers_listener'); + $this->assertEquals(new Reference('monolog.logger.php', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(1)); + $this->assertSame([ + \E_NOTICE => \Psr\Log\LogLevel::ERROR, + \E_WARNING => \Psr\Log\LogLevel::ERROR, + ], $definition->getArgument(2)); + } + public function testRouter() { $container = $this->createContainerFromFile('full'); From 62398b525e34d0c23558af334f66f9b344fd58ef Mon Sep 17 00:00:00 2001 From: Timo Bakx Date: Tue, 1 Dec 2020 21:35:43 +0100 Subject: [PATCH 022/188] [FrameworkBundle] Added option to specify the event dispatcher in debug:event-dispatcher --- .../Bundle/FrameworkBundle/CHANGELOG.md | 2 ++ .../Command/EventDispatcherDebugCommand.php | 30 +++++++++++++++---- .../Console/Descriptor/MarkdownDescriptor.php | 6 ++++ .../Console/Descriptor/TextDescriptor.php | 11 +++++-- .../Compiler/UnusedTagsPass.php | 3 +- .../FrameworkExtension.php | 3 ++ .../Resources/config/console.php | 2 +- .../Resources/config/services.php | 1 + .../DependencyInjection/SecurityExtension.php | 3 +- 9 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 26519c7cc2943..d1d26696e73a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG ----- * Added support for configuring PHP error level to log levels +* Added the `dispatcher` option to `debug:event-dispatcher` +* Added the `event_dispatcher.dispatcher` tag 5.2.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index 5ff002d3c13e0..a053d96dd8fdb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -29,14 +30,16 @@ */ class EventDispatcherDebugCommand extends Command { + private const DEFAULT_DISPATCHER = 'event_dispatcher'; + protected static $defaultName = 'debug:event-dispatcher'; - private $dispatcher; + private $dispatchers; - public function __construct(EventDispatcherInterface $dispatcher) + public function __construct(ContainerInterface $dispatchers) { parent::__construct(); - $this->dispatcher = $dispatcher; + $this->dispatchers = $dispatchers; } /** @@ -47,6 +50,7 @@ protected function configure() $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('raw', null, InputOption::VALUE_NONE, 'To output raw description'), ]) @@ -74,12 +78,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $options = []; + $dispatcherServiceName = $input->getOption('dispatcher'); + if (!$this->dispatchers->has($dispatcherServiceName)) { + $io->getErrorStyle()->error(sprintf('Event dispatcher "%s" is not available.', $dispatcherServiceName)); + + return 1; + } + + $dispatcher = $this->dispatchers->get($dispatcherServiceName); + if ($event = $input->getArgument('event')) { - if ($this->dispatcher->hasListeners($event)) { + if ($dispatcher->hasListeners($event)) { $options = ['event' => $event]; } else { // if there is no direct match, try find partial matches - $events = $this->searchForEvent($this->dispatcher, $event); + $events = $this->searchForEvent($dispatcher, $event); if (0 === \count($events)) { $io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); @@ -93,10 +106,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $helper = new DescriptorHelper(); + + if (self::DEFAULT_DISPATCHER !== $dispatcherServiceName) { + $options['dispatcher_service_name'] = $dispatcherServiceName; + } + $options['format'] = $input->getOption('format'); $options['raw_text'] = $input->getOption('raw'); $options['output'] = $io; - $helper->describe($io, $this->dispatcher, $options); + $helper->describe($io, $dispatcher, $options); return 0; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 5b97dab46218c..634fbb4750edc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -287,8 +287,14 @@ protected function describeContainerEnvVars(array $envs, array $options = []) protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $event = \array_key_exists('event', $options) ? $options['event'] : null; + $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; $title = 'Registered listeners'; + + if (null !== $dispatcherServiceName) { + $title .= sprintf(' of event dispatcher "%s"', $dispatcherServiceName); + } + if (null !== $event) { $title .= sprintf(' for event `%s` ordered by descending priority', $event); $registeredListeners = $eventDispatcher->getListeners($event); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index d68b4c42827b9..e83f15c65d3da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -474,12 +474,19 @@ protected function describeContainerEnvVars(array $envs, array $options = []) protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $event = \array_key_exists('event', $options) ? $options['event'] : null; + $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; + + $title = 'Registered Listeners'; + + if (null !== $dispatcherServiceName) { + $title .= sprintf(' of Event Dispatcher "%s"', $dispatcherServiceName); + } if (null !== $event) { - $title = sprintf('Registered Listeners for "%s" Event', $event); + $title .= sprintf(' for "%s" Event', $event); $registeredListeners = $eventDispatcher->getListeners($event); } else { - $title = 'Registered Listeners Grouped by Event'; + $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(); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 37f545468d813..a38cde35d1163 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -43,6 +43,7 @@ class UnusedTagsPass implements CompilerPassInterface 'controller.argument_value_resolver', 'controller.service_arguments', 'data_collector', + 'event_dispatcher.dispatcher', 'form.type', 'form.type_extension', 'form.type_guesser', @@ -72,9 +73,9 @@ class UnusedTagsPass implements CompilerPassInterface 'routing.expression_language_provider', 'routing.loader', 'routing.route_loader', + 'security.authenticator.login_linker', 'security.expression_language_provider', 'security.remember_me_aware', - 'security.authenticator.login_linker', 'security.voter', 'serializer.encoder', 'serializer.normalizer', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a45de43954e52..bcf4f05c5d33a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -160,6 +160,7 @@ use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\CallbackInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -479,6 +480,8 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('kernel.cache_clearer'); $container->registerForAutoconfiguration(CacheWarmerInterface::class) ->addTag('kernel.cache_warmer'); + $container->registerForAutoconfiguration(EventDispatcherInterface::class) + ->addTag('event_dispatcher.dispatcher'); $container->registerForAutoconfiguration(EventSubscriberInterface::class) ->addTag('kernel.event_subscriber'); $container->registerForAutoconfiguration(LocaleAwareInterface::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php index e9b3d2e36a855..81531599f1f6c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php @@ -129,7 +129,7 @@ ->set('console.command.event_dispatcher_debug', EventDispatcherDebugCommand::class) ->args([ - service('event_dispatcher'), + tagged_locator('event_dispatcher.dispatcher'), ]) ->tag('console.command', ['command' => 'debug:event-dispatcher']) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index 4df97ed19c531..a0603b2cf34f7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -65,6 +65,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->set('event_dispatcher', EventDispatcher::class) ->public() ->tag('container.hot_path') + ->tag('event_dispatcher.dispatcher') ->alias(EventDispatcherInterfaceComponentAlias::class, 'event_dispatcher') ->alias(EventDispatcherInterface::class, 'event_dispatcher') diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 3c84bf34072d0..e297aeeb99df4 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -358,7 +358,8 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ // Register Firewall-specific event dispatcher $firewallEventDispatcherId = 'security.event_dispatcher.'.$id; - $container->register($firewallEventDispatcherId, EventDispatcher::class); + $container->register($firewallEventDispatcherId, EventDispatcher::class) + ->addTag('event_dispatcher.dispatcher'); // Register listeners $listeners = []; From 8143f8a83b086d457a60b81f14a19cc3981112b0 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 5 Dec 2020 20:58:01 +0100 Subject: [PATCH 023/188] Fix CS. --- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index d1d26696e73a0..bc5ca7f27dbc2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -5,8 +5,8 @@ CHANGELOG ----- * Added support for configuring PHP error level to log levels -* Added the `dispatcher` option to `debug:event-dispatcher` -* Added the `event_dispatcher.dispatcher` tag + * Added the `dispatcher` option to `debug:event-dispatcher` + * Added the `event_dispatcher.dispatcher` tag 5.2.0 ----- From d907d3ba83479cdbbdd29aea40b86420706a5965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronny=20L=C3=B3pez?= Date: Sun, 6 Dec 2020 09:31:27 +0100 Subject: [PATCH 024/188] Fix broken link to documentation in Lock and Semaphore components --- src/Symfony/Component/Lock/README.md | 2 +- src/Symfony/Component/Semaphore/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Lock/README.md b/src/Symfony/Component/Lock/README.md index 0be0bfd49dfda..9936634221952 100644 --- a/src/Symfony/Component/Lock/README.md +++ b/src/Symfony/Component/Lock/README.md @@ -4,7 +4,7 @@ Lock Component Resources --------- - * [Documentation](https://symfony.com/doc/master/components/lock.html) + * [Documentation](https://symfony.com/doc/current/components/lock.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) diff --git a/src/Symfony/Component/Semaphore/README.md b/src/Symfony/Component/Semaphore/README.md index 4a56641ae26b9..490833cde826a 100644 --- a/src/Symfony/Component/Semaphore/README.md +++ b/src/Symfony/Component/Semaphore/README.md @@ -13,7 +13,7 @@ are not covered by Symfony's Resources --------- - * [Documentation](https://symfony.com/doc/master/components/semaphore.html) + * [Documentation](https://symfony.com/doc/current/components/semaphore.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) From ffdfb4fb53b4a3919dd0d17feb65ead3d3d0ae8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 5 Dec 2020 21:52:04 +0100 Subject: [PATCH 025/188] Assert voter returns valid decision --- UPGRADE-5.3.md | 5 ++++ UPGRADE-6.0.md | 1 + src/Symfony/Component/Security/CHANGELOG.md | 5 ++++ .../Authorization/AccessDecisionManager.php | 10 ++++++++ .../AccessDecisionManagerTest.php | 25 +++++++++++++++++++ 5 files changed, 46 insertions(+) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 69532a9397f4d..1685d36ac272a 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -12,3 +12,8 @@ Form * Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead. * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead. * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. + +Security +-------- + + * Deprecated voters that do not return a valid decision when calling the `vote` method. diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 3eda1d0f68e14..450a3a0a1048e 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -159,6 +159,7 @@ Security in `PreAuthenticatedToken`, `RememberMeToken`, `SwitchUserToken`, `UsernamePasswordToken`, `DefaultAuthenticationSuccessHandler`. * Removed the `AbstractRememberMeServices::$providerKey` property in favor of `AbstractRememberMeServices::$firewallName` + * `AccessDecisionManager` now throw an exception when a voter does not return a valid decision. TwigBundle ---------- diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 566625a7c1f7c..2f7c94deed1bc 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* Deprecated voters that do not return a valid decision when calling the `vote` method. + 5.2.0 ----- diff --git a/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php index 8356c38bb93fc..7c4cbd728866b 100644 --- a/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php @@ -89,6 +89,8 @@ private function decideAffirmative(TokenInterface $token, array $attributes, $ob if (VoterInterface::ACCESS_DENIED === $result) { ++$deny; + } elseif (VoterInterface::ACCESS_ABSTAIN !== $result) { + trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class); } } @@ -124,6 +126,8 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje ++$grant; } elseif (VoterInterface::ACCESS_DENIED === $result) { ++$deny; + } elseif (VoterInterface::ACCESS_ABSTAIN !== $result) { + trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class); } } @@ -161,6 +165,8 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje if (VoterInterface::ACCESS_GRANTED === $result) { ++$grant; + } elseif (VoterInterface::ACCESS_ABSTAIN !== $result) { + trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class); } } } @@ -192,6 +198,10 @@ private function decidePriority(TokenInterface $token, array $attributes, $objec if (VoterInterface::ACCESS_DENIED === $result) { return false; } + + if (VoterInterface::ACCESS_ABSTAIN !== $result) { + trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class); + } } return $this->allowIfAllAbstainDecisions; diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php index 0e3c62c5bd861..2f7ce5e9f6578 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php @@ -12,11 +12,14 @@ namespace Symfony\Component\Security\Core\Tests\Authorization; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; class AccessDecisionManagerTest extends TestCase { + use ExpectDeprecationTrait; + public function testSetUnsupportedStrategy() { $this->expectException('InvalidArgumentException'); @@ -34,6 +37,20 @@ public function testStrategies($strategy, $voters, $allowIfAllAbstainDecisions, $this->assertSame($expected, $manager->decide($token, ['ROLE_FOO'])); } + /** + * @dataProvider provideStrategies + * @group legacy + */ + public function testDeprecatedVoter($strategy) + { + $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); + $manager = new AccessDecisionManager([$this->getVoter(3)], $strategy); + + $this->expectDeprecation('Since symfony/security-core 5.3: Returning "3" in "%s::vote()" is deprecated, return one of "Symfony\Component\Security\Core\Authorization\Voter\VoterInterface" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".'); + + $manager->decide($token, ['ROLE_FOO']); + } + public function getStrategyTests() { return [ @@ -94,6 +111,14 @@ public function getStrategyTests() ]; } + public function provideStrategies() + { + yield [AccessDecisionManager::STRATEGY_AFFIRMATIVE]; + yield [AccessDecisionManager::STRATEGY_CONSENSUS]; + yield [AccessDecisionManager::STRATEGY_UNANIMOUS]; + yield [AccessDecisionManager::STRATEGY_PRIORITY]; + } + protected function getVoters($grants, $denies, $abstains) { $voters = []; From 41c59010477bba2b96021bff7e15d9b55b6fbeb4 Mon Sep 17 00:00:00 2001 From: Fabien Bourigault Date: Sat, 5 Dec 2020 14:38:59 +0100 Subject: [PATCH 026/188] [FrameworkBundle] Add validator.expression_language service --- .../FrameworkBundle/Resources/config/validator.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php index 5dcb427b565bc..75166bd810a9e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php @@ -13,6 +13,7 @@ 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\ExpressionValidator; use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; @@ -66,10 +67,18 @@ ]) ->set('validator.expression', ExpressionValidator::class) + ->args([service('validator.expression_language')->nullOnInvalid()]) ->tag('validator.constraint_validator', [ 'alias' => 'validator.expression', ]) + ->set('validator.expression_language', ExpressionLanguage::class) + ->args([service('cache.validator_expression_language')->nullOnInvalid()]) + + ->set('cache.validator_expression_language') + ->parent('cache.system') + ->tag('cache.pool') + ->set('validator.email', EmailValidator::class) ->args([ abstract_arg('Default mode'), From 814ffabbd820dbc58b6913e285cb6ff85feddb11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Renan=20Gon=C3=A7alves?= Date: Mon, 7 Dec 2020 15:08:24 +0100 Subject: [PATCH 027/188] [Cache] Support Redis Sentinel mode when using phpredis/phpredis extension --- src/Symfony/Component/Cache/CHANGELOG.md | 5 +++ .../Adapter/PredisAdapterSentinelTest.php | 35 +++++++++++++++++++ .../Adapter/RedisAdapterSentinelTest.php | 4 +-- .../Component/Cache/Traits/RedisTrait.php | 28 ++++++++++----- 4 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterSentinelTest.php diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 73965c2058903..2b6c4b177cee9 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* added support for connecting to Redis Sentinel clusters when using the Redis PHP extension + 5.2.0 ----- diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterSentinelTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterSentinelTest.php new file mode 100644 index 0000000000000..e6de9b3ee6bbe --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterSentinelTest.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\Cache\Tests\Adapter; + +use Symfony\Component\Cache\Adapter\AbstractAdapter; + +/** + * @group integration + */ +class PredisAdapterSentinelTest extends AbstractRedisAdapterTest +{ + public static function setUpBeforeClass(): void + { + if (!class_exists(\Predis\Client::class)) { + self::markTestSkipped('The Predis\Client class is required.'); + } + if (!$hosts = getenv('REDIS_SENTINEL_HOSTS')) { + self::markTestSkipped('REDIS_SENTINEL_HOSTS env var is not defined.'); + } + if (!$service = getenv('REDIS_SENTINEL_SERVICE')) { + self::markTestSkipped('REDIS_SENTINEL_SERVICE env var is not defined.'); + } + + self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['redis_sentinel' => $service, 'class' => \Predis\Client::class]); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php index 82b9f08b65474..84ea7969ad962 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php @@ -21,8 +21,8 @@ class RedisAdapterSentinelTest extends AbstractRedisAdapterTest { public static function setUpBeforeClass(): void { - if (!class_exists('Predis\Client')) { - self::markTestSkipped('The Predis\Client class is required.'); + if (!class_exists(\RedisSentinel::class)) { + self::markTestSkipped('The RedisSentinel class is required.'); } if (!$hosts = getenv('REDIS_SENTINEL_HOSTS')) { self::markTestSkipped('REDIS_SENTINEL_HOSTS env var is not defined.'); diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 317879aa494e2..6e6b6d0e4f6c6 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -159,13 +159,17 @@ public static function createConnection($dsn, array $options = []) throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); } - if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class)) { - throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package: "%s".', $dsn)); + $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)); } - $params += $query + $options + self::$defaultConnectionOptions; + 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)); + } - if (null === $params['class'] && !isset($params['redis_sentinel']) && \extension_loaded('redis')) { + if (null === $params['class'] && \extension_loaded('redis')) { $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class); } else { $class = null === $params['class'] ? \Predis\Client::class : $params['class']; @@ -176,8 +180,19 @@ public static function createConnection($dsn, array $options = []) $redis = new $class(); $initializer = static function ($redis) use ($connect, $params, $dsn, $auth, $hosts) { + $host = $hosts[0]['host'] ?? $hosts[0]['path']; + $port = $hosts[0]['port'] ?? null; + + if (isset($params['redis_sentinel'])) { + $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']); + + if (![$host, $port] = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { + throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $params['redis_sentinel'], $host, $port)); + } + } + try { - @$redis->{$connect}($hosts[0]['host'] ?? $hosts[0]['path'], $hosts[0]['port'] ?? null, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']); + @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); $isConnected = $redis->isConnected(); @@ -254,9 +269,6 @@ public static function createConnection($dsn, array $options = []) } elseif (is_a($class, \Predis\ClientInterface::class, true)) { if ($params['redis_cluster']) { $params['cluster'] = 'redis'; - if (isset($params['redis_sentinel'])) { - throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".', $dsn)); - } } elseif (isset($params['redis_sentinel'])) { $params['replication'] = 'sentinel'; $params['service'] = $params['redis_sentinel']; From a885ba844dc9a4ec2d08cf1ef33f499a9b5d3ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 8 Dec 2020 10:59:12 +0100 Subject: [PATCH 028/188] [Messenger] Use "warning" intead of "error" log level for RecoverableException --- .../SendFailedMessageForRetryListener.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php index ff330cd38c783..bd42cebcc5f14 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php @@ -12,6 +12,7 @@ use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; @@ -70,7 +71,18 @@ public function onMessageFailed(WorkerMessageFailedEvent $event) $delay = $retryStrategy->getWaitingTime($envelope, $throwable); if (null !== $this->logger) { - $this->logger->error('Error thrown while handling message {class}. Sending for retry #{retryCount} using {delay} ms delay. Error: "{error}"', $context + ['retryCount' => $retryCount, 'delay' => $delay, 'error' => $throwable->getMessage(), 'exception' => $throwable]); + $logLevel = LogLevel::ERROR; + if ($throwable instanceof RecoverableExceptionInterface) { + $logLevel = LogLevel::WARNING; + } elseif ($throwable instanceof HandlerFailedException) { + foreach ($throwable->getNestedExceptions() as $nestedException) { + if ($nestedException instanceof RecoverableExceptionInterface) { + $logLevel = LogLevel::WARNING; + break; + } + } + } + $this->logger->log($logLevel, 'Error thrown while handling message {class}. Sending for retry #{retryCount} using {delay} ms delay. Error: "{error}"', $context + ['retryCount' => $retryCount, 'delay' => $delay, 'error' => $throwable->getMessage(), 'exception' => $throwable]); } // add the delay and retry stamp info From 620ee34b90474f5a51bfae6bacf91975d2f42815 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 8 Dec 2020 12:16:55 +0100 Subject: [PATCH 029/188] [Cache] hotfix --- src/Symfony/Component/Cache/Traits/RedisTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 6e6b6d0e4f6c6..7db5bbaad833d 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -161,7 +161,7 @@ public static function createConnection($dsn, array $options = []) $params += $query + $options + self::$defaultConnectionOptions; - if (isset($params['redis_sentinel']) && (!class_exists(\Predis\Client::class) || !class_exists(\RedisSentinel::class))) { + 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)); } From 1ee7e4c94c41498e13c13fa20ce8b5585d4181f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 30 Nov 2020 20:36:42 +0100 Subject: [PATCH 030/188] [HttpKernel] Marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal --- UPGRADE-5.3.md | 5 +++++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 5 +++++ .../HttpKernel/EventListener/DebugHandlersListener.php | 2 ++ 3 files changed, 12 insertions(+) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 1685d36ac272a..4d991a30805c2 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -13,6 +13,11 @@ Form * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead. * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. +HttpKernel +---------- + + * Marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal + Security -------- diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index db2821334323d..a5444039b0c8a 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal + 5.2.0 ----- diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index 6b677edd7ca6c..ee711798e987d 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -27,6 +27,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 5.3 */ class DebugHandlersListener implements EventSubscriberInterface { From 93c2f5e891e7210a9a5a94ea19a10f0a00d7934c Mon Sep 17 00:00:00 2001 From: Thiago Melo Date: Sat, 17 Oct 2020 22:37:28 -0400 Subject: [PATCH 031/188] [BrowserKit] Allowing body content from GET with a content-type --- src/Symfony/Component/BrowserKit/CHANGELOG.md | 1 + src/Symfony/Component/BrowserKit/HttpBrowser.php | 2 +- .../Component/BrowserKit/Tests/HttpBrowserTest.php | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/BrowserKit/CHANGELOG.md b/src/Symfony/Component/BrowserKit/CHANGELOG.md index 69a1689f0e3ea..5409def7e1192 100644 --- a/src/Symfony/Component/BrowserKit/CHANGELOG.md +++ b/src/Symfony/Component/BrowserKit/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Added `jsonRequest` method to `AbstractBrowser` + * Allowed sending a body with GET requests when a content-type is defined 5.2.0 ----- diff --git a/src/Symfony/Component/BrowserKit/HttpBrowser.php b/src/Symfony/Component/BrowserKit/HttpBrowser.php index 0ad87b5c33a62..eba038ec6e734 100644 --- a/src/Symfony/Component/BrowserKit/HttpBrowser.php +++ b/src/Symfony/Component/BrowserKit/HttpBrowser.php @@ -61,7 +61,7 @@ protected function doRequest($request): Response */ private function getBodyAndExtraHeaders(Request $request, array $headers): array { - if (\in_array($request->getMethod(), ['GET', 'HEAD'])) { + if (\in_array($request->getMethod(), ['GET', 'HEAD']) && !isset($headers['content-type'])) { return ['', []]; } diff --git a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php index 1397d9b10c387..28555e8904330 100644 --- a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php @@ -63,6 +63,14 @@ public function validContentTypes() ['POST', 'http://example.com/', [], [], ['CONTENT_TYPE' => 'application/json'], '["content"]'], ['POST', 'http://example.com/', ['headers' => $defaultHeaders + ['content-type' => 'application/json'], 'body' => '["content"]', 'max_redirects' => 0]], ]; + yield 'GET JSON' => [ + ['GET', 'http://example.com/jsonrpc', [], [], ['CONTENT_TYPE' => 'application/json'], '["content"]'], + ['GET', 'http://example.com/jsonrpc', ['headers' => $defaultHeaders + ['content-type' => 'application/json'], 'body' => '["content"]', 'max_redirects' => 0]], + ]; + yield 'HEAD JSON' => [ + ['HEAD', 'http://example.com/jsonrpc', [], [], ['CONTENT_TYPE' => 'application/json'], '["content"]'], + ['HEAD', 'http://example.com/jsonrpc', ['headers' => $defaultHeaders + ['content-type' => 'application/json'], 'body' => '["content"]', 'max_redirects' => 0]], + ]; } public function testMultiPartRequestWithSingleFile() From 7f9237e88c8d11543b7f5c6c848a02daab27a32e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arthur=20Woimb=C3=A9e?= Date: Thu, 8 Oct 2020 20:32:55 +0200 Subject: [PATCH 032/188] [ErrorHandler] fix html W3C compliance --- .../Bridge/Twig/Extension/CodeExtension.php | 2 +- .../ErrorRenderer/HtmlErrorRenderer.php | 8 ++--- .../Resources/assets/css/exception.css | 2 +- .../Resources/views/logs.html.php | 2 +- .../Resources/views/traces.html.php | 31 ++++++++++++------- .../Resources/views/traces_text.html.php | 4 +-- 6 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index 7acf75fb9c17a..5ecc09060b4cd 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -137,7 +137,7 @@ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?stri } for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) { - $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; + $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; } return '
    '.implode("\n", $lines).'
'; diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php index 429f0dfdbb4ad..806ef6aad7ca8 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php @@ -283,7 +283,7 @@ private function fileExcerpt(string $file, int $line, int $srcContext = 3): stri } for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) { - $lines[] = ''.$this->fixCodeMarkup($content[$i - 1]).''; + $lines[] = ''.$this->fixCodeMarkup($content[$i - 1]).''; } return '
    '.implode("\n", $lines).'
'; @@ -302,9 +302,9 @@ private function fixCodeMarkup(string $line) } // missing tag at the end of line - $opening = strpos($line, ''); - if (false !== $opening && (false === $closing || $closing > $opening)) { + $opening = strrpos($line, ''); + if (false !== $opening && (false === $closing || $closing < $opening)) { $line .= ''; } diff --git a/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css index e873c7366f84d..5822ea2043eb4 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css +++ b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css @@ -124,7 +124,7 @@ tr.status-error td, tr.status-warning td { border-bottom: 1px solid var(--base-2 .status-warning .colored { color: #A46A1F; } .status-error .colored { color: var(--color-error); } -.sf-toggle { cursor: pointer; } +.sf-toggle { cursor: pointer; position: relative; } .sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; } .sf-toggle-content.sf-toggle-hidden { display: none; } .sf-toggle-content.sf-toggle-visible { display: block; } diff --git a/src/Symfony/Component/ErrorHandler/Resources/views/logs.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/logs.html.php index f886200fb3b5b..9757e1a61151f 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/views/logs.html.php +++ b/src/Symfony/Component/ErrorHandler/Resources/views/logs.html.php @@ -23,7 +23,7 @@ $status = \E_DEPRECATED === $severity || \E_USER_DEPRECATED === $severity ? 'warning' : 'normal'; } ?> data-filter-channel="escape($log['channel']); ?>"> - + escape($log['priorityName']); ?> diff --git a/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php index d587b058e26bf..f64d917138258 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php +++ b/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php @@ -1,21 +1,30 @@
- -

- include('assets/images/icon-minus-square-o.svg'); ?> - include('assets/images/icon-plus-square-o.svg'); ?> - - - 1 ? '\\' : ''; ?> - - -

+
+ include('assets/images/icon-minus-square-o.svg'); ?> + include('assets/images/icon-plus-square-o.svg'); ?> + + +
+ +

+ + + + +

+ 1) { ?>

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

- +
diff --git a/src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php index a7090fbe8909e..6b478402c85a7 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php +++ b/src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php @@ -2,14 +2,14 @@ -

+
1) { ?> [/] include('assets/images/icon-minus-square-o.svg'); ?> include('assets/images/icon-plus-square-o.svg'); ?> -

+
From fe24f73ec0f8f706584f7dc5bb3cc0b6e85fe408 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 9 Dec 2020 20:07:39 +0100 Subject: [PATCH 033/188] [PhpUnitBridge] bump "php" to 7.1+ and "phpunit" to 7.5+ --- src/Symfony/Bridge/PhpUnit/CHANGELOG.md | 6 + .../Bridge/PhpUnit/ClassExistsMock.php | 2 +- src/Symfony/Bridge/PhpUnit/ClockMock.php | 2 +- .../Bridge/PhpUnit/ConstraintTrait.php | 7 +- .../Bridge/PhpUnit/CoverageListener.php | 8 +- .../PhpUnit/DeprecationErrorHandler.php | 14 +- .../DeprecationErrorHandler/Configuration.php | 2 +- .../DeprecationErrorHandler/Deprecation.php | 26 ++-- .../DeprecationGroup.php | 6 +- src/Symfony/Bridge/PhpUnit/DnsMock.php | 4 +- .../Bridge/PhpUnit/Legacy/CommandForV5.php | 57 -------- .../{CommandForV6.php => CommandForV7.php} | 4 +- .../Bridge/PhpUnit/Legacy/CommandForV9.php | 2 +- .../PhpUnit/Legacy/ConstraintTraitForV6.php | 130 ------------------ .../PhpUnit/Legacy/CoverageListenerForV5.php | 35 ----- .../PhpUnit/Legacy/CoverageListenerForV6.php | 41 ------ .../Legacy/SetUpTearDownTraitForV5.php | 70 ---------- .../Legacy/SymfonyTestsListenerForV5.php | 54 -------- .../Legacy/SymfonyTestsListenerForV6.php | 58 -------- .../Legacy/SymfonyTestsListenerTrait.php | 14 +- .../Bridge/PhpUnit/SymfonyTestsListener.php | 8 +- .../Bridge/PhpUnit/Tests/BootstrapTest.php | 40 ------ .../DeprecationTest.php | 6 +- .../Fixtures/coverage/tests/bootstrap.php | 10 +- src/Symfony/Bridge/PhpUnit/TextUI/Command.php | 6 +- .../Bridge/PhpUnit/bin/simple-phpunit.php | 24 +--- src/Symfony/Bridge/PhpUnit/bootstrap.php | 92 +------------ src/Symfony/Bridge/PhpUnit/composer.json | 6 +- 28 files changed, 59 insertions(+), 675 deletions(-) delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php rename src/Symfony/Bridge/PhpUnit/Legacy/{CommandForV6.php => CommandForV7.php} (91%) delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/ConstraintTraitForV6.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV5.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Tests/BootstrapTest.php diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index 2808ad0c50903..a8a4e2df43408 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +5.3.0 +----- + + * bumped the minimum PHP version to 7.1.3 + * bumped the minimum PHPUnit version to 7.5 + 5.1.0 ----- diff --git a/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php b/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php index e8ca4ac9402a8..824ef33688d84 100644 --- a/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php @@ -45,7 +45,7 @@ public static function trait_exists($name, $autoload = true) public static function register($class) { - $self = \get_called_class(); + $self = static::class; $mockedNs = [substr($class, 0, strrpos($class, '\\'))]; if (0 < strpos($class, '\\Tests\\')) { diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index 2cc834cd4f679..7280d44dc16f8 100644 --- a/src/Symfony/Bridge/PhpUnit/ClockMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -92,7 +92,7 @@ public static function gmdate($format, $timestamp = null) public static function register($class) { - $self = \get_called_class(); + $self = static::class; $mockedNs = [substr($class, 0, strrpos($class, '\\'))]; if (0 < strpos($class, '\\Tests\\')) { diff --git a/src/Symfony/Bridge/PhpUnit/ConstraintTrait.php b/src/Symfony/Bridge/PhpUnit/ConstraintTrait.php index 446dbf2f4fe03..478eee187c67e 100644 --- a/src/Symfony/Bridge/PhpUnit/ConstraintTrait.php +++ b/src/Symfony/Bridge/PhpUnit/ConstraintTrait.php @@ -15,12 +15,7 @@ use ReflectionClass; $r = new ReflectionClass(Constraint::class); -if (\PHP_VERSION_ID < 70000 || !$r->getMethod('matches')->hasReturnType()) { - trait ConstraintTrait - { - use Legacy\ConstraintTraitForV6; - } -} elseif ($r->getProperty('exporter')->isProtected()) { +if ($r->getProperty('exporter')->isProtected()) { trait ConstraintTrait { use Legacy\ConstraintTraitForV7; diff --git a/src/Symfony/Bridge/PhpUnit/CoverageListener.php b/src/Symfony/Bridge/PhpUnit/CoverageListener.php index 805f9222a50d9..5dd29a9cbb718 100644 --- a/src/Symfony/Bridge/PhpUnit/CoverageListener.php +++ b/src/Symfony/Bridge/PhpUnit/CoverageListener.php @@ -11,13 +11,7 @@ namespace Symfony\Bridge\PhpUnit; -if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV5', 'Symfony\Bridge\PhpUnit\CoverageListener'); -} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV6', 'Symfony\Bridge\PhpUnit\CoverageListener'); -} else { - class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV7', 'Symfony\Bridge\PhpUnit\CoverageListener'); -} +class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV7', 'Symfony\Bridge\PhpUnit\CoverageListener'); if (false) { class CoverageListener diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 66378170903ce..7b7b038fc5dc0 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -25,9 +25,9 @@ */ class DeprecationErrorHandler { - const MODE_DISABLED = 'disabled'; - const MODE_WEAK = 'max[total]=999999&verbose=0'; - const MODE_STRICT = 'max[total]=0'; + public const MODE_DISABLED = 'disabled'; + public const MODE_WEAK = 'max[total]=999999&verbose=0'; + public const MODE_STRICT = 'max[total]=0'; private $mode; private $configuration; @@ -238,13 +238,7 @@ private function getConfiguration() return $this->configuration; } if (false === $mode = $this->mode) { - if (isset($_SERVER['SYMFONY_DEPRECATIONS_HELPER'])) { - $mode = $_SERVER['SYMFONY_DEPRECATIONS_HELPER']; - } elseif (isset($_ENV['SYMFONY_DEPRECATIONS_HELPER'])) { - $mode = $_ENV['SYMFONY_DEPRECATIONS_HELPER']; - } else { - $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); - } + $mode = $_SERVER['SYMFONY_DEPRECATIONS_HELPER'] ?? $_ENV['SYMFONY_DEPRECATIONS_HELPER'] ?? getenv('SYMFONY_DEPRECATIONS_HELPER'); } if ('strict' === $mode) { return $this->configuration = Configuration::inStrictMode(); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php index 20ffd9651b8ed..95b64369714bb 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php @@ -278,7 +278,7 @@ public static function fromUrlEncodedString($serializedConfiguration) } return new self( - isset($normalizedConfiguration['max']) ? $normalizedConfiguration['max'] : [], + $normalizedConfiguration['max'] ?? [], '', $verboseOutput, filter_var($normalizedConfiguration['generateBaseline'], \FILTER_VALIDATE_BOOLEAN), diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index 72991c75ea504..2eed1c3305415 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -21,14 +21,14 @@ */ class Deprecation { - const PATH_TYPE_VENDOR = 'path_type_vendor'; - const PATH_TYPE_SELF = 'path_type_internal'; - const PATH_TYPE_UNDETERMINED = 'path_type_undetermined'; + public const PATH_TYPE_VENDOR = 'path_type_vendor'; + public const PATH_TYPE_SELF = 'path_type_internal'; + public const PATH_TYPE_UNDETERMINED = 'path_type_undetermined'; - const TYPE_SELF = 'type_self'; - const TYPE_DIRECT = 'type_direct'; - const TYPE_INDIRECT = 'type_indirect'; - const TYPE_UNDETERMINED = 'type_undetermined'; + public const TYPE_SELF = 'type_self'; + public const TYPE_DIRECT = 'type_direct'; + public const TYPE_INDIRECT = 'type_indirect'; + public const TYPE_UNDETERMINED = 'type_undetermined'; private $trace = []; private $message; @@ -61,10 +61,10 @@ public function __construct($message, array $trace, $file) $this->trace = $trace; - if ('trigger_error' === (isset($trace[1]['function']) ? $trace[1]['function'] : null) - && (DebugClassLoader::class === ($class = (isset($trace[2]['class']) ? $trace[2]['class'] : null)) || LegacyDebugClassLoader::class === $class) - && 'checkClass' === (isset($trace[2]['function']) ? $trace[2]['function'] : null) - && null !== ($extraFile = (isset($trace[2]['args'][1]) ? $trace[2]['args'][1] : null)) + if ('trigger_error' === ($trace[1]['function'] ?? null) + && (DebugClassLoader::class === ($class = $trace[2]['class'] ?? null) || LegacyDebugClassLoader::class === $class) + && 'checkClass' === ($trace[2]['function'] ?? null) + && null !== ($extraFile = $trace[2]['args'][1] ?? null) && '' !== $extraFile && false !== $extraFile = realpath($extraFile) ) { @@ -118,7 +118,7 @@ private function lineShouldBeSkipped(array $line) } $class = $line['class']; - return 'ReflectionMethod' === $class || 0 === strpos($class, 'PHPUnit_') || 0 === strpos($class, 'PHPUnit\\'); + return 'ReflectionMethod' === $class || 0 === strpos($class, 'PHPUnit\\'); } /** @@ -290,7 +290,7 @@ private static function getVendors() foreach (get_declared_classes() as $class) { if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); - $v = \dirname(\dirname($r->getFileName())); + $v = \dirname($r->getFileName(), 2); if (file_exists($v.'/composer/installed.json')) { self::$vendors[] = $v; $loader = require $v.'/autoload.php'; diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php index f2b0323135dd4..6ad2b84ea3fd6 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php @@ -55,11 +55,7 @@ public function addNotice() */ private function deprecationNotice($message) { - if (!isset($this->deprecationNotices[$message])) { - $this->deprecationNotices[$message] = new DeprecationNotice(); - } - - return $this->deprecationNotices[$message]; + return $this->deprecationNotices[$message] ?? $this->deprecationNotices[$message] = new DeprecationNotice(); } public function count() diff --git a/src/Symfony/Bridge/PhpUnit/DnsMock.php b/src/Symfony/Bridge/PhpUnit/DnsMock.php index 1e2f55b371be3..642da0a6dfcde 100644 --- a/src/Symfony/Bridge/PhpUnit/DnsMock.php +++ b/src/Symfony/Bridge/PhpUnit/DnsMock.php @@ -152,7 +152,7 @@ public static function dns_get_record($hostname, $type = \DNS_ANY, &$authns = nu $records = []; foreach (self::$hosts[$hostname] as $record) { - if (isset(self::$dnsTypes[$record['type']]) && (self::$dnsTypes[$record['type']] & $type)) { + if ((self::$dnsTypes[$record['type']] ?? 0) & $type) { $records[] = array_merge(['host' => $hostname, 'class' => 'IN', 'ttl' => 1, 'type' => $record['type']], $record); } } @@ -163,7 +163,7 @@ public static function dns_get_record($hostname, $type = \DNS_ANY, &$authns = nu public static function register($class) { - $self = \get_called_class(); + $self = static::class; $mockedNs = [substr($class, 0, strrpos($class, '\\'))]; if (0 < strpos($class, '\\Tests\\')) { diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php deleted file mode 100644 index 2ce390df38609..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -/** - * {@inheritdoc} - * - * @internal - */ -class CommandForV5 extends \PHPUnit_TextUI_Command -{ - /** - * {@inheritdoc} - */ - protected function createRunner() - { - $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : []; - - $registeredLocally = false; - - foreach ($this->arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListenerForV5) { - $registeredListener->globalListenerDisabled(); - $registeredLocally = true; - break; - } - } - - if (isset($this->arguments['configuration'])) { - $configuration = $this->arguments['configuration']; - if (!$configuration instanceof \PHPUnit_Util_Configuration) { - $configuration = \PHPUnit_Util_Configuration::getInstance($this->arguments['configuration']); - } - foreach ($configuration->getListenerConfiguration() as $registeredListener) { - if ('Symfony\Bridge\PhpUnit\SymfonyTestsListener' === ltrim($registeredListener['class'], '\\')) { - $registeredLocally = true; - break; - } - } - } - - if (!$registeredLocally) { - $this->arguments['listeners'][] = new SymfonyTestsListenerForV5(); - } - - return parent::createRunner(); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV7.php similarity index 91% rename from src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php rename to src/Symfony/Bridge/PhpUnit/Legacy/CommandForV7.php index 93e1ad975b7e4..fcf5c4505d3da 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV7.php @@ -21,14 +21,14 @@ * * @internal */ -class CommandForV6 extends BaseCommand +class CommandForV7 extends BaseCommand { /** * {@inheritdoc} */ protected function createRunner(): BaseRunner { - $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : []; + $this->arguments['listeners'] ?? $this->arguments['listeners'] = []; $registeredLocally = false; diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php index 2511380257fd8..351f02f2230ec 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php @@ -31,7 +31,7 @@ class CommandForV9 extends BaseCommand */ protected function createRunner(): BaseRunner { - $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : []; + $this->arguments['listeners'] ?? $this->arguments['listeners'] = []; $registeredLocally = false; diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/ConstraintTraitForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/ConstraintTraitForV6.php deleted file mode 100644 index 53819e4b3c4d7..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/ConstraintTraitForV6.php +++ /dev/null @@ -1,130 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal - */ -trait ConstraintTraitForV6 -{ - /** - * @return bool|null - */ - public function evaluate($other, $description = '', $returnResult = false) - { - return $this->doEvaluate($other, $description, $returnResult); - } - - /** - * @return int - */ - public function count() - { - return $this->doCount(); - } - - /** - * @return string - */ - public function toString() - { - return $this->doToString(); - } - - /** - * @param mixed $other - * - * @return string - */ - protected function additionalFailureDescription($other) - { - return $this->doAdditionalFailureDescription($other); - } - - /** - * @return Exporter - */ - protected function exporter() - { - if (null === $this->exporter) { - $this->exporter = new Exporter(); - } - - return $this->exporter; - } - - /** - * @param mixed $other - * - * @return string - */ - protected function failureDescription($other) - { - return $this->doFailureDescription($other); - } - - /** - * @param mixed $other - * - * @return bool - */ - protected function matches($other) - { - return $this->doMatches($other); - } - - private function doAdditionalFailureDescription($other) - { - return ''; - } - - private function doCount() - { - return 1; - } - - private function doEvaluate($other, $description, $returnResult) - { - $success = false; - - if ($this->matches($other)) { - $success = true; - } - - if ($returnResult) { - return $success; - } - - if (!$success) { - $this->fail($other, $description); - } - - return null; - } - - private function doFailureDescription($other) - { - return $this->exporter()->export($other).' '.$this->toString(); - } - - private function doMatches($other) - { - return false; - } - - private function doToString() - { - return ''; - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php deleted file mode 100644 index 9d754eebc85df..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -/** - * CoverageListener adds `@covers ` on each test when possible to - * make the code coverage more accurate. - * - * @author Grégoire Pineau - * - * @internal - */ -class CoverageListenerForV5 extends \PHPUnit_Framework_BaseTestListener -{ - private $trait; - - public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) - { - $this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound); - } - - public function startTest(\PHPUnit_Framework_Test $test) - { - $this->trait->startTest($test); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php deleted file mode 100644 index 1b3ceec161f8a..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.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\Bridge\PhpUnit\Legacy; - -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestListenerDefaultImplementation; - -/** - * CoverageListener adds `@covers ` on each test when possible to - * make the code coverage more accurate. - * - * @author Grégoire Pineau - * - * @internal - */ -class CoverageListenerForV6 implements TestListener -{ - use TestListenerDefaultImplementation; - - private $trait; - - public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) - { - $this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound); - } - - public function startTest(Test $test) - { - $this->trait->startTest($test); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV5.php deleted file mode 100644 index ca29c2ae49ab8..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV5.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -/** - * @internal - */ -trait SetUpTearDownTraitForV5 -{ - /** - * @return void - */ - public static function setUpBeforeClass() - { - self::doSetUpBeforeClass(); - } - - /** - * @return void - */ - public static function tearDownAfterClass() - { - self::doTearDownAfterClass(); - } - - /** - * @return void - */ - protected function setUp() - { - self::doSetUp(); - } - - /** - * @return void - */ - protected function tearDown() - { - self::doTearDown(); - } - - private static function doSetUpBeforeClass() - { - parent::setUpBeforeClass(); - } - - private static function doTearDownAfterClass() - { - parent::tearDownAfterClass(); - } - - private function doSetUp() - { - parent::setUp(); - } - - private function doTearDown() - { - parent::tearDown(); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php deleted file mode 100644 index 9b646dca8dfab..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -/** - * Collects and replays skipped tests. - * - * @author Nicolas Grekas - * - * @internal - */ -class SymfonyTestsListenerForV5 extends \PHPUnit_Framework_BaseTestListener -{ - private $trait; - - public function __construct(array $mockedNamespaces = []) - { - $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); - } - - public function globalListenerDisabled() - { - $this->trait->globalListenerDisabled(); - } - - public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) - { - $this->trait->startTestSuite($suite); - } - - public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) - { - $this->trait->addSkippedTest($test, $e, $time); - } - - public function startTest(\PHPUnit_Framework_Test $test) - { - $this->trait->startTest($test); - } - - public function endTest(\PHPUnit_Framework_Test $test, $time) - { - $this->trait->endTest($test, $time); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php deleted file mode 100644 index 8f2f6b5a7ed54..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -use PHPUnit\Framework\BaseTestListener; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestSuite; - -/** - * Collects and replays skipped tests. - * - * @author Nicolas Grekas - * - * @internal - */ -class SymfonyTestsListenerForV6 extends BaseTestListener -{ - private $trait; - - public function __construct(array $mockedNamespaces = []) - { - $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); - } - - public function globalListenerDisabled() - { - $this->trait->globalListenerDisabled(); - } - - public function startTestSuite(TestSuite $suite) - { - $this->trait->startTestSuite($suite); - } - - public function addSkippedTest(Test $test, \Exception $e, $time) - { - $this->trait->addSkippedTest($test, $e, $time); - } - - public function startTest(Test $test) - { - $this->trait->startTest($test); - } - - public function endTest(Test $test, $time) - { - $this->trait->endTest($test, $time); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 07046cb9fe457..6c2dcb9b1ef32 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -123,7 +123,7 @@ public function startTestSuite($suite) $suiteName = $suite->getName(); foreach ($suite->tests() as $test) { - if (!($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) { + if (!$test instanceof TestCase) { continue; } if (null === Test::getPreserveGlobalStateSettings(\get_class($test), $test->getName(false))) { @@ -158,7 +158,7 @@ public function startTestSuite($suite) $testSuites = [$suite]; for ($i = 0; isset($testSuites[$i]); ++$i) { foreach ($testSuites[$i]->tests() as $test) { - if ($test instanceof \PHPUnit_Framework_TestSuite || $test instanceof TestSuite) { + if ($test instanceof TestSuite) { if (!class_exists($test->getName(), false)) { $testSuites[] = $test; continue; @@ -178,11 +178,11 @@ public function startTestSuite($suite) $skipped = []; while ($s = array_shift($suites)) { foreach ($s->tests() as $test) { - if ($test instanceof \PHPUnit_Framework_TestSuite || $test instanceof TestSuite) { + if ($test instanceof TestSuite) { $suites[] = $test; continue; } - if (($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase) + if ($test instanceof TestCase && isset($this->wasSkipped[\get_class($test)][$test->getName()]) ) { $skipped[] = $test; @@ -202,7 +202,7 @@ public function addSkippedTest($test, \Exception $e, $time) public function startTest($test) { - if (-2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) { + if (-2 < $this->state && $test instanceof TestCase) { // This event is triggered before the test is re-run in isolation if ($this->willBeIsolated($test)) { $this->runsInSeparateProcess = tempnam(sys_get_temp_dir(), 'deprec'); @@ -280,7 +280,7 @@ public function endTest($test, $time) unlink($this->runsInSeparateProcess); putenv('SYMFONY_DEPRECATIONS_SERIALIZE'); foreach ($deprecations ? unserialize($deprecations) : [] as $deprecation) { - $error = serialize(['deprecation' => $deprecation[1], 'class' => $className, 'method' => $test->getName(false), 'triggering_file' => isset($deprecation[2]) ? $deprecation[2] : null]); + $error = serialize(['deprecation' => $deprecation[1], 'class' => $className, 'method' => $test->getName(false), 'triggering_file' => $deprecation[2] ?? null]); if ($deprecation[0]) { // unsilenced on purpose trigger_error($error, \E_USER_DEPRECATED); @@ -312,7 +312,7 @@ public function endTest($test, $time) self::$expectedDeprecations = self::$gatheredDeprecations = []; self::$previousErrorHandler = null; } - if (!$this->runsInSeparateProcess && -2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) { + if (!$this->runsInSeparateProcess && -2 < $this->state && $test instanceof TestCase) { if (\in_array('time-sensitive', $groups, true)) { ClockMock::withClockMock(false); } diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php index d3cd7563bd41f..47f0f42afc8fd 100644 --- a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php +++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php @@ -11,13 +11,7 @@ namespace Symfony\Bridge\PhpUnit; -if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); -} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); -} else { - class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); -} +class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); if (false) { class SymfonyTestsListener diff --git a/src/Symfony/Bridge/PhpUnit/Tests/BootstrapTest.php b/src/Symfony/Bridge/PhpUnit/Tests/BootstrapTest.php deleted file mode 100644 index d1811575087df..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Tests/BootstrapTest.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Tests; - -use PHPUnit\Framework\TestCase; - -class BootstrapTest extends TestCase -{ - /** - * @requires PHPUnit < 6.0 - */ - public function testAliasingOfErrorClasses() - { - $this->assertInstanceOf( - \PHPUnit_Framework_Error::class, - new \PHPUnit\Framework\Error\Error('message', 0, __FILE__, __LINE__) - ); - $this->assertInstanceOf( - \PHPUnit_Framework_Error_Deprecated::class, - new \PHPUnit\Framework\Error\Deprecated('message', 0, __FILE__, __LINE__) - ); - $this->assertInstanceOf( - \PHPUnit_Framework_Error_Notice::class, - new \PHPUnit\Framework\Error\Notice('message', 0, __FILE__, __LINE__) - ); - $this->assertInstanceOf( - \PHPUnit_Framework_Error_Warning::class, - new \PHPUnit\Framework\Error\Warning('message', 0, __FILE__, __LINE__) - ); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index 5c2f28264037b..c78b821e6d9bc 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -33,7 +33,7 @@ private static function getVendorDir() foreach (get_declared_classes() as $class) { if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); - $vendorDir = \dirname(\dirname($r->getFileName())); + $vendorDir = \dirname($r->getFileName(), 2); if (file_exists($vendorDir.'/composer/installed.json') && @mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true)) { break; } @@ -61,7 +61,7 @@ public function testItCanTellWhetherItIsInternal() { $r = new \ReflectionClass(Deprecation::class); - if (\dirname(\dirname($r->getFileName())) !== \dirname(\dirname(__DIR__))) { + if (\dirname($r->getFileName(), 2) !== \dirname(__DIR__, 2)) { $this->markTestSkipped('Test case is not compatible with having the bridge in vendor/'); } @@ -266,7 +266,7 @@ private static function doSetupBeforeClass() foreach (get_declared_classes() as $class) { if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); - $v = \dirname(\dirname($r->getFileName())); + $v = \dirname($r->getFileName(), 2); if (file_exists($v.'/composer/installed.json')) { $loader = require $v.'/autoload.php'; $reflection = new \ReflectionClass($loader); diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php index 3e45381dce2a0..6d78d06f6b3a5 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php @@ -13,13 +13,5 @@ require __DIR__.'/../src/FooCov.php'; require __DIR__.'/../../../../Legacy/CoverageListenerTrait.php'; - -if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) { - require_once __DIR__.'/../../../../Legacy/CoverageListenerForV5.php'; -} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { - require_once __DIR__.'/../../../../Legacy/CoverageListenerForV6.php'; -} else { - require_once __DIR__.'/../../../../Legacy/CoverageListenerForV7.php'; -} - +require_once __DIR__.'/../../../../Legacy/CoverageListenerForV7.php'; require __DIR__.'/../../../../CoverageListener.php'; diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php index 8690812b56b57..3cc158f6b8e72 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php @@ -11,10 +11,8 @@ namespace Symfony\Bridge\PhpUnit\TextUI; -if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV5', 'Symfony\Bridge\PhpUnit\TextUI\Command'); -} elseif (version_compare(\PHPUnit\Runner\Version::id(), '9.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV6', 'Symfony\Bridge\PhpUnit\TextUI\Command'); +if (version_compare(\PHPUnit\Runner\Version::id(), '9.0.0', '<')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV7', 'Symfony\Bridge\PhpUnit\TextUI\Command'); } else { class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV9', 'Symfony\Bridge\PhpUnit\TextUI\Command'); } diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index b8940368308a3..76c1291fe7a84 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -15,8 +15,8 @@ error_reporting(-1); global $argv, $argc; -$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : []; -$argc = isset($_SERVER['argc']) ? $_SERVER['argc'] : 0; +$argv = $_SERVER['argv'] ?? []; +$argc = $_SERVER['argc'] ?? 0; $getEnvVar = function ($name, $default = false) use ($argv) { if (false !== $value = getenv($name)) { return $value; @@ -98,19 +98,9 @@ $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '9.4'); } elseif (\PHP_VERSION_ID >= 70200) { // PHPUnit 8 requires PHP 7.2+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '8.3'); -} elseif (\PHP_VERSION_ID >= 70100) { - // PHPUnit 7 requires PHP 7.1+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.5'); -} elseif (\PHP_VERSION_ID >= 70000) { - // PHPUnit 6 requires PHP 7.0+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '6.5'); -} elseif (\PHP_VERSION_ID >= 50600) { - // PHPUnit 4 does not support PHP 7 - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '5.7'); + $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '8.5'); } else { - // PHPUnit 5.1 requires PHP 5.6+ - $PHPUNIT_VERSION = '4.8'; + $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.5'); } $MAX_PHPUNIT_VERSION = $getEnvVar('SYMFONY_MAX_PHPUNIT_VERSION', false); @@ -255,12 +245,12 @@ if ($PHPUNIT_REMOVE_RETURN_TYPEHINT) { $alteredCode = preg_replace('/^ ((?:protected|public)(?: static)? function \w+\(\)): void/m', ' $1', $alteredCode); } - $alteredCode = preg_replace('/abstract class (?:TestCase|PHPUnit_Framework_TestCase)[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillTestCaseTrait;", $alteredCode, 1); + $alteredCode = preg_replace('/abstract class TestCase[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillTestCaseTrait;", $alteredCode, 1); file_put_contents($alteredFile, $alteredCode); // Mutate Assert code $alteredCode = file_get_contents($alteredFile = './src/Framework/Assert.php'); - $alteredCode = preg_replace('/abstract class (?:Assert|PHPUnit_Framework_Assert)[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillAssertTrait;", $alteredCode, 1); + $alteredCode = preg_replace('/abstract class Assert[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillAssertTrait;", $alteredCode, 1); file_put_contents($alteredFile, $alteredCode); file_put_contents('phpunit', <<<'EOPHP' @@ -352,7 +342,7 @@ class_exists('SymfonyExcludeListSimplePhpunit', false) && PHPUnit\Util\Blacklist } if ($components) { - $skippedTests = isset($_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS']) ? $_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS'] : false; + $skippedTests = $_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS'] ?? false; $runningProcs = []; foreach ($components as $component) { diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index 2b6f2bac6cdf5..9d425b741a5a2 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -12,96 +12,6 @@ use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; -if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - $classes = [ - 'PHPUnit_Framework_Assert', // override PhpUnit's ForwardCompat child class - 'PHPUnit_Framework_AssertionFailedError', // override PhpUnit's ForwardCompat child class - 'PHPUnit_Framework_BaseTestListener', // override PhpUnit's ForwardCompat child class - - 'PHPUnit_Framework_Constraint', - 'PHPUnit_Framework_Constraint_ArrayHasKey', - 'PHPUnit_Framework_Constraint_ArraySubset', - 'PHPUnit_Framework_Constraint_Attribute', - 'PHPUnit_Framework_Constraint_Callback', - 'PHPUnit_Framework_Constraint_ClassHasAttribute', - 'PHPUnit_Framework_Constraint_ClassHasStaticAttribute', - 'PHPUnit_Framework_Constraint_Composite', - 'PHPUnit_Framework_Constraint_Count', - 'PHPUnit_Framework_Constraint_Exception', - 'PHPUnit_Framework_Constraint_ExceptionCode', - 'PHPUnit_Framework_Constraint_ExceptionMessage', - 'PHPUnit_Framework_Constraint_ExceptionMessageRegExp', - 'PHPUnit_Framework_Constraint_FileExists', - 'PHPUnit_Framework_Constraint_GreaterThan', - 'PHPUnit_Framework_Constraint_IsAnything', - 'PHPUnit_Framework_Constraint_IsEmpty', - 'PHPUnit_Framework_Constraint_IsEqual', - 'PHPUnit_Framework_Constraint_IsFalse', - 'PHPUnit_Framework_Constraint_IsIdentical', - 'PHPUnit_Framework_Constraint_IsInstanceOf', - 'PHPUnit_Framework_Constraint_IsJson', - 'PHPUnit_Framework_Constraint_IsNull', - 'PHPUnit_Framework_Constraint_IsTrue', - 'PHPUnit_Framework_Constraint_IsType', - 'PHPUnit_Framework_Constraint_JsonMatches', - 'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider', - 'PHPUnit_Framework_Constraint_LessThan', - 'PHPUnit_Framework_Constraint_ObjectHasAttribute', - 'PHPUnit_Framework_Constraint_PCREMatch', - 'PHPUnit_Framework_Constraint_SameSize', - 'PHPUnit_Framework_Constraint_StringContains', - 'PHPUnit_Framework_Constraint_StringEndsWith', - 'PHPUnit_Framework_Constraint_StringMatches', - 'PHPUnit_Framework_Constraint_StringStartsWith', - 'PHPUnit_Framework_Constraint_TraversableContains', - 'PHPUnit_Framework_Constraint_TraversableContainsOnly', - - 'PHPUnit_Framework_Error_Deprecated', - 'PHPUnit_Framework_Error_Notice', - 'PHPUnit_Framework_Error_Warning', - 'PHPUnit_Framework_Exception', - 'PHPUnit_Framework_ExpectationFailedException', - - 'PHPUnit_Framework_MockObject_MockObject', - - 'PHPUnit_Framework_IncompleteTest', - 'PHPUnit_Framework_IncompleteTestCase', - 'PHPUnit_Framework_IncompleteTestError', - 'PHPUnit_Framework_RiskyTest', - 'PHPUnit_Framework_RiskyTestError', - 'PHPUnit_Framework_SkippedTest', - 'PHPUnit_Framework_SkippedTestCase', - 'PHPUnit_Framework_SkippedTestError', - 'PHPUnit_Framework_SkippedTestSuiteError', - - 'PHPUnit_Framework_SyntheticError', - - 'PHPUnit_Framework_Test', - 'PHPUnit_Framework_TestCase', // override PhpUnit's ForwardCompat child class - 'PHPUnit_Framework_TestFailure', - 'PHPUnit_Framework_TestListener', - 'PHPUnit_Framework_TestResult', - 'PHPUnit_Framework_TestSuite', // override PhpUnit's ForwardCompat child class - - 'PHPUnit_Runner_BaseTestRunner', - 'PHPUnit_Runner_Version', - - 'PHPUnit_Util_Blacklist', - 'PHPUnit_Util_ErrorHandler', - 'PHPUnit_Util_Test', - 'PHPUnit_Util_XML', - ]; - foreach ($classes as $class) { - class_alias($class, '\\'.strtr($class, '_', '\\')); - } - - class_alias('PHPUnit_Framework_Constraint_And', 'PHPUnit\Framework\Constraint\LogicalAnd'); - class_alias('PHPUnit_Framework_Constraint_Not', 'PHPUnit\Framework\Constraint\LogicalNot'); - class_alias('PHPUnit_Framework_Constraint_Or', 'PHPUnit\Framework\Constraint\LogicalOr'); - class_alias('PHPUnit_Framework_Constraint_Xor', 'PHPUnit\Framework\Constraint\LogicalXor'); - class_alias('PHPUnit_Framework_Error', 'PHPUnit\Framework\Error\Error'); -} - // Detect if we need to serialize deprecations to a file. if ($file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) { DeprecationErrorHandler::collectDeprecations($file); @@ -110,7 +20,7 @@ class_alias('PHPUnit_Framework_Error', 'PHPUnit\Framework\Error\Error'); } // Detect if we're loaded by an actual run of phpunit -if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists('PHPUnit_TextUI_Command', false) && !class_exists('PHPUnit\TextUI\Command', false)) { +if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists('PHPUnit\TextUI\Command', false)) { return; } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 9f710b2dddf2a..9095e1a46cba5 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -16,9 +16,9 @@ } ], "require": { - "php": ">=5.5.9 EVEN ON LATEST SYMFONY VERSIONS TO ALLOW USING", + "php": ">=7.1.3 EVEN ON LATEST SYMFONY VERSIONS TO ALLOW USING", "php": "THIS BRIDGE WHEN TESTING LOWEST SYMFONY VERSIONS.", - "php": ">=5.5.9" + "php": ">=7.1.3" }, "require-dev": { "symfony/deprecation-contracts": "^2.1", @@ -28,7 +28,7 @@ "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0|9.1.2" + "phpunit/phpunit": "<7.5|9.1.2" }, "autoload": { "files": [ "bootstrap.php" ], From 122eaba746a4f77be6ec2798a697fc2aed54045e Mon Sep 17 00:00:00 2001 From: Stephen Date: Sun, 6 Dec 2020 15:28:21 +0100 Subject: [PATCH 034/188] [TwigBridge] export concatenated translations --- .../NodeVisitor/TranslationNodeVisitor.php | 35 +++++++++++++++++++ .../Tests/Translation/TwigExtractorTest.php | 13 +++++++ 2 files changed, 48 insertions(+) diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index 23c244e4248f7..e5b0fc4ea1b73 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -13,6 +13,7 @@ use Symfony\Bridge\Twig\Node\TransNode; use Twig\Environment; +use Twig\Node\Expression\Binary\ConcatBinary; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\FunctionExpression; @@ -87,6 +88,16 @@ protected function doEnterNode(Node $node, Environment $env): Node $node->getNode('body')->getAttribute('data'), $node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null, ]; + } elseif ( + $node instanceof FilterExpression && + 'trans' === $node->getNode('filter')->getAttribute('value') && + $node->getNode('node') instanceof ConcatBinary && + $message = $this->getConcatValueFromNode($node->getNode('node'), null) + ) { + $this->messages[] = [ + $message, + $this->getReadDomainFromArguments($node->getNode('arguments'), 1), + ]; } return $node; @@ -151,4 +162,28 @@ private function getReadDomainFromNode(Node $node): ?string return self::UNDEFINED_DOMAIN; } + + private function getConcatValueFromNode(Node $node, ?string $value): ?string + { + if ($node instanceof ConcatBinary) { + foreach ($node as $nextNode) { + if ($nextNode instanceof ConcatBinary) { + $nextValue = $this->getConcatValueFromNode($nextNode, $value); + if (null === $nextValue) { + return null; + } + $value .= $nextValue; + } elseif ($nextNode instanceof ConstantExpression) { + $value .= $nextNode->getAttribute('value'); + } else { + // this is a node we cannot process (variable, or translation in translation) + return null; + } + } + } elseif ($node instanceof ConstantExpression) { + $value .= $node->getAttribute('value'); + } + + return $value; + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index 03e2936d42ca2..66a9515c2d7d7 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -43,6 +43,10 @@ public function testExtract($template, $messages) $m->setAccessible(true); $m->invoke($extractor, $template, $catalogue); + if (0 === \count($messages)) { + $this->assertSame($catalogue->all(), $messages); + } + foreach ($messages as $key => $domain) { $this->assertTrue($catalogue->has($key, $domain)); $this->assertEquals('prefix'.$key, $catalogue->get($key, $domain)); @@ -70,6 +74,15 @@ public function getExtractData() // make sure this works with twig's named arguments ['{{ "new key" | trans(domain="domain") }}', ['new key' => 'domain']], + + // concat translations + ['{{ ("new" ~ " key") | trans() }}', ['new key' => 'messages']], + ['{{ ("another " ~ "new " ~ "key") | trans() }}', ['another new key' => 'messages']], + ['{{ ("new" ~ " key") | trans(domain="domain") }}', ['new key' => 'domain']], + ['{{ ("another " ~ "new " ~ "key") | trans(domain="domain") }}', ['another new key' => 'domain']], + // if it has a variable or other expression, we can not extract it + ['{% set foo = "new" %} {{ ("new " ~ foo ~ "key") | trans() }}', []], + ['{{ ("foo " ~ "new"|trans ~ "key") | trans() }}', ['new' => 'messages']], ]; } From 1ce5b03c2a50dff89ad03aa92e153c65706968f3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 18 May 2020 11:39:39 +0200 Subject: [PATCH 035/188] [Form] Add "choice_translation_parameters" option --- .../views/Form/form_div_layout.html.twig | 2 +- src/Symfony/Bridge/Twig/composer.json | 4 +- src/Symfony/Component/Form/CHANGELOG.md | 1 + .../Component/Form/ChoiceList/ChoiceList.php | 13 ++ .../Cache/ChoiceTranslationParameters.php | 27 ++++ .../Factory/CachingFactoryDecorator.php | 35 +++-- .../Factory/ChoiceListFactoryInterface.php | 11 +- .../Factory/DefaultChoiceListFactory.php | 22 ++- .../Factory/PropertyAccessDecorator.php | 34 ++++- .../Form/ChoiceList/View/ChoiceView.php | 17 ++- .../Form/Extension/Core/Type/ChoiceType.php | 8 +- .../Factory/DefaultChoiceListFactoryTest.php | 132 +++++++++++++++++- .../Extension/Core/Type/ChoiceTypeTest.php | 20 ++- .../Descriptor/resolved_form_type_1.json | 1 + .../Descriptor/resolved_form_type_1.txt | 76 +++++----- 15 files changed, 322 insertions(+), 81 deletions(-) create mode 100644 src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 3f31c5f31c8c6..94f87dc165ec6 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -85,7 +85,7 @@ {{- block('choice_widget_options') -}} {%- else -%} - + {%- endif -%} {% endfor %} {%- endblock choice_widget_options -%} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index f9caf53de6cd0..db9ed7ca07b6d 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -27,7 +27,7 @@ "symfony/asset": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", - "symfony/form": "^5.1.9", + "symfony/form": "^5.3", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", "symfony/mime": "^5.2", @@ -52,7 +52,7 @@ }, "conflict": { "symfony/console": "<4.4", - "symfony/form": "<5.1", + "symfony/form": "<5.3", "symfony/http-foundation": "<4.4", "symfony/http-kernel": "<4.4", "symfony/translation": "<5.2", diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index d95d11176a42c..f8a60ddffe990 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead. * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead. * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Added a `choice_translation_parameters` option to `ChoiceType` 5.2.0 ----- diff --git a/src/Symfony/Component/Form/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ChoiceList.php index 045ded01e2e05..df63c83d89dee 100644 --- a/src/Symfony/Component/Form/ChoiceList/ChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ChoiceList.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFilter; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceTranslationParameters; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue; use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy; use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice; @@ -113,6 +114,18 @@ public static function attr($formType, $attr, $vary = null): ChoiceAttr return new ChoiceAttr($formType, $attr, $vary); } + /** + * Decorates a "choice_translation_parameters" option to make it cacheable. + * + * @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list + * @param callable|array $translationParameters Any pseudo callable or array to create translation parameters from a choice + * @param mixed|null $vary Dynamic data used to compute a unique hash when caching the option + */ + public static function translationParameters($formType, $translationParameters, $vary = null): ChoiceTranslationParameters + { + return new ChoiceTranslationParameters($formType, $translationParameters, $vary); + } + /** * Decorates a "group_by" callback to make it cacheable. * diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php b/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php new file mode 100644 index 0000000000000..e9ab5c7119922 --- /dev/null +++ b/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.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\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_translation_parameters" option. + * + * @internal + * + * @author Vincent Langlet + */ +final class ChoiceTranslationParameters extends AbstractStaticOption +{ +} diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php index 2e1dc9a317654..c376f1a2d1c54 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -174,14 +174,16 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, $value = nul /** * {@inheritdoc} * - * @param array|callable|Cache\PreferredChoice|null $preferredChoices The preferred choices - * @param callable|false|Cache\ChoiceLabel|null $label The option or static option generating the choice labels - * @param callable|Cache\ChoiceFieldName|null $index The option or static option generating the view indices - * @param callable|Cache\GroupBy|null $groupBy The option or static option generating the group names - * @param array|callable|Cache\ChoiceAttr|null $attr The option or static option generating the HTML attributes + * @param array|callable|Cache\PreferredChoice|null $preferredChoices The preferred choices + * @param callable|false|Cache\ChoiceLabel|null $label The option or static option generating the choice labels + * @param callable|Cache\ChoiceFieldName|null $index The option or static option generating the view indices + * @param callable|Cache\GroupBy|null $groupBy The option or static option generating the group names + * @param array|callable|Cache\ChoiceAttr|null $attr The option or static option generating the HTML attributes + * @param array|callable|Cache\ChoiceTranslationParameters $labelTranslationParameters The parameters used to translate the choice labels */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null) + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/) { + $labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : []; $cache = true; if ($preferredChoices instanceof Cache\PreferredChoice) { @@ -214,11 +216,25 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, $cache = false; } + if ($labelTranslationParameters instanceof Cache\ChoiceTranslationParameters) { + $labelTranslationParameters = $labelTranslationParameters->getOption(); + } elseif ([] !== $labelTranslationParameters) { + $cache = false; + } + if (!$cache) { - return $this->decoratedFactory->createView($list, $preferredChoices, $label, $index, $groupBy, $attr); + return $this->decoratedFactory->createView( + $list, + $preferredChoices, + $label, + $index, + $groupBy, + $attr, + $labelTranslationParameters + ); } - $hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr]); + $hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters]); if (!isset($this->views[$hash])) { $this->views[$hash] = $this->decoratedFactory->createView( @@ -227,7 +243,8 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, $label, $index, $groupBy, - $attr + $attr, + $labelTranslationParameters ); } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php index 82b1e4dc7de6b..6834009190f81 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -76,12 +76,13 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va * match the keys of the choices. The values should be arrays of HTML * attributes that should be added to the respective choice. * - * @param array|callable|null $preferredChoices The preferred choices - * @param callable|false|null $label The callable generating the choice labels; - * pass false to discard the label - * @param array|callable|null $attr The callable generating the HTML attributes + * @param array|callable|null $preferredChoices The preferred choices + * @param callable|false|null $label The callable generating the choice labels; + * pass false to discard the label + * @param array|callable|null $attr The callable generating the HTML attributes + * @param array|callable $labelTranslationParameters The parameters used to translate the choice labels * * @return ChoiceListView The choice list view */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null); + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/); } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index 45d3d046bd36e..6545f60998aff 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -68,9 +68,12 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va /** * {@inheritdoc} + * + * @param array|callable $labelTranslationParameters The parameters used to translate the choice labels */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null) + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/) { + $labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : []; $preferredViews = []; $preferredViewsOrder = []; $otherViews = []; @@ -109,6 +112,7 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, $keys, $index, $attr, + $labelTranslationParameters, $preferredChoices, $preferredViews, $preferredViewsOrder, @@ -146,6 +150,7 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, $keys, $index, $attr, + $labelTranslationParameters, $preferredChoices, $preferredViews, $preferredViewsOrder, @@ -162,7 +167,7 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, return new ChoiceListView($otherViews, $preferredViews); } - private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) { // $value may be an integer or a string, since it's stored in the array // keys. We want to guarantee it's a string though. @@ -186,7 +191,10 @@ private static function addChoiceView($choice, string $value, $label, array $key $label, // The attributes may be a callable or a mapping from choice indices // to nested arrays - \is_callable($attr) ? $attr($choice, $key, $value) : (isset($attr[$key]) ? $attr[$key] : []) + \is_callable($attr) ? $attr($choice, $key, $value) : ($attr[$key] ?? []), + // The label translation parameters may be a callable or a mapping from choice indices + // to nested arrays + \is_callable($labelTranslationParameters) ? $labelTranslationParameters($choice, $key, $value) : ($labelTranslationParameters[$key] ?? []) ); // $isPreferred may be null if no choices are preferred @@ -198,7 +206,7 @@ private static function addChoiceView($choice, string $value, $label, array $key $otherViews[$nextIndex] = $view; } - private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) { foreach ($values as $key => $value) { if (null === $value) { @@ -217,6 +225,7 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViewsForGroup, $preferredViewsOrder, @@ -242,6 +251,7 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViews, $preferredViewsOrder, @@ -250,7 +260,7 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label } } - private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) { $groupLabels = $groupBy($choice, $keys[$value], $value); @@ -263,6 +273,7 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViews, $preferredViewsOrder, @@ -292,6 +303,7 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViews[$groupLabel]->choices, $preferredViewsOrder[$groupLabel], diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index bfa37973a565e..6d323fbb08777 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -145,16 +145,18 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, $value = nul /** * {@inheritdoc} * - * @param array|callable|string|PropertyPath|null $preferredChoices The preferred choices - * @param callable|string|PropertyPath|null $label The callable or path generating the choice labels - * @param callable|string|PropertyPath|null $index The callable or path generating the view indices - * @param callable|string|PropertyPath|null $groupBy The callable or path generating the group names - * @param array|callable|string|PropertyPath|null $attr The callable or path generating the HTML attributes + * @param array|callable|string|PropertyPath|null $preferredChoices The preferred choices + * @param callable|string|PropertyPath|null $label The callable or path generating the choice labels + * @param callable|string|PropertyPath|null $index The callable or path generating the view indices + * @param callable|string|PropertyPath|null $groupBy The callable or path generating the group names + * @param array|callable|string|PropertyPath|null $attr The callable or path generating the HTML attributes + * @param array|callable|string|PropertyPath $labelTranslationParameters The callable or path generating the parameters used to translate the choice labels * * @return ChoiceListView The choice list view */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null) + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/) { + $labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : []; $accessor = $this->propertyAccessor; if (\is_string($label)) { @@ -217,6 +219,24 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, }; } - return $this->decoratedFactory->createView($list, $preferredChoices, $label, $index, $groupBy, $attr); + if (\is_string($labelTranslationParameters)) { + $labelTranslationParameters = new PropertyPath($labelTranslationParameters); + } + + if ($labelTranslationParameters instanceof PropertyPath) { + $labelTranslationParameters = static function ($choice) use ($accessor, $labelTranslationParameters) { + return $accessor->getValue($choice, $labelTranslationParameters); + }; + } + + return $this->decoratedFactory->createView( + $list, + $preferredChoices, + $label, + $index, + $groupBy, + $attr, + $labelTranslationParameters + ); } } diff --git a/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php b/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php index 2b5636b17eb1d..cbb9ed540e9a9 100644 --- a/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php +++ b/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php @@ -27,19 +27,26 @@ class ChoiceView */ public $attr; + /** + * Additional parameters used to translate the label. + */ + public $labelTranslationParameters; + /** * Creates a new choice view. * - * @param mixed $data The original choice - * @param string $value The view representation of the choice - * @param string|false $label The label displayed to humans; pass false to discard the label - * @param array $attr Additional attributes for the HTML tag + * @param mixed $data The original choice + * @param string $value The view representation of the choice + * @param string|false $label The label displayed to humans; pass false to discard the label + * @param array $attr Additional attributes for the HTML tag + * @param array $labelTranslationParameters Additional parameters used to translate the label */ - public function __construct($data, string $value, $label, array $attr = []) + public function __construct($data, string $value, $label, array $attr = [], array $labelTranslationParameters = []) { $this->data = $data; $this->value = $value; $this->label = $label; $this->attr = $attr; + $this->labelTranslationParameters = $labelTranslationParameters; } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index dbdc8744da5c1..513e0e5b78ce8 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -18,6 +18,7 @@ use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFilter; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceTranslationParameters; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue; use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy; use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice; @@ -212,6 +213,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) 'separator' => '-------------------', 'placeholder' => null, 'choice_translation_domain' => $choiceTranslationDomain, + 'choice_translation_parameters' => $options['choice_translation_parameters'], ]); // The decision, whether a choice is selected, is potentially done @@ -326,6 +328,7 @@ public function configureOptions(OptionsResolver $resolver) 'choice_name' => null, 'choice_value' => null, 'choice_attr' => null, + 'choice_translation_parameters' => [], 'preferred_choices' => [], 'group_by' => null, 'empty_data' => $emptyData, @@ -356,6 +359,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('choice_name', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceFieldName::class]); $resolver->setAllowedTypes('choice_value', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceValue::class]); $resolver->setAllowedTypes('choice_attr', ['null', 'array', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceAttr::class]); + $resolver->setAllowedTypes('choice_translation_parameters', ['null', 'array', 'callable', ChoiceTranslationParameters::class]); $resolver->setAllowedTypes('preferred_choices', ['array', '\Traversable', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', PreferredChoice::class]); $resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', GroupBy::class]); } @@ -395,6 +399,7 @@ private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceV 'value' => $choiceView->value, 'label' => $choiceView->label, 'attr' => $choiceView->attr, + 'label_translation_parameters' => $choiceView->labelTranslationParameters, 'translation_domain' => $options['choice_translation_domain'], 'block_name' => 'entry', ]; @@ -439,7 +444,8 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op $options['choice_label'], $options['choice_name'], $options['group_by'], - $options['choice_attr'] + $options['choice_attr'], + $options['choice_translation_parameters'] ); } } diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index a124b48ffda31..c59b3840a5948 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -77,6 +77,11 @@ public function getAttr($object) return $object->attr; } + public function getLabelTranslationParameters($object) + { + return $object->labelTranslationParameters; + } + public function getGroup($object) { return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2'; @@ -96,10 +101,10 @@ public function getGroupAsObject($object) protected function setUp(): void { - $this->obj1 = (object) ['label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => []]; - $this->obj2 = (object) ['label' => 'B', 'index' => 'x', 'value' => 'b', 'preferred' => true, 'group' => 'Group 1', 'attr' => ['attr1' => 'value1']]; - $this->obj3 = (object) ['label' => 'C', 'index' => 'y', 'value' => 1, 'preferred' => true, 'group' => 'Group 2', 'attr' => ['attr2' => 'value2']]; - $this->obj4 = (object) ['label' => 'D', 'index' => 'z', 'value' => 2, 'preferred' => false, 'group' => 'Group 2', 'attr' => []]; + $this->obj1 = (object) ['label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => [], 'labelTranslationParameters' => []]; + $this->obj2 = (object) ['label' => 'B', 'index' => 'x', 'value' => 'b', 'preferred' => true, 'group' => 'Group 1', 'attr' => ['attr1' => 'value1'], 'labelTranslationParameters' => []]; + $this->obj3 = (object) ['label' => 'C', 'index' => 'y', 'value' => 1, 'preferred' => true, 'group' => 'Group 2', 'attr' => ['attr2' => 'value2'], 'labelTranslationParameters' => []]; + $this->obj4 = (object) ['label' => 'D', 'index' => 'z', 'value' => 2, 'preferred' => false, 'group' => 'Group 2', 'attr' => [], 'labelTranslationParameters' => ['%placeholder1%' => 'value1']]; $this->list = new ArrayChoiceList(['A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4]); $this->factory = new DefaultChoiceListFactory(); } @@ -753,6 +758,110 @@ function ($object, $key, $value) { $this->assertFlatViewWithAttr($view); } + public function testCreateViewFlatLabelTranslationParametersAsArray() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + [ + 'D' => ['%placeholder1%' => 'value1'], + ] + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersEmpty() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + [] + ); + + $this->assertFlatView($view); + } + + public function testCreateViewFlatlabelTranslationParametersAsCallable() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + [$this, 'getlabelTranslationParameters'] + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersAsClosure() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + function ($object) { + return $object->labelTranslationParameters; + } + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersClosureReceivesKey() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + function ($object, $key) { + switch ($key) { + case 'D': return ['%placeholder1%' => 'value1']; + default: return []; + } + } + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersClosureReceivesValue() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + function ($object, $key, $value) { + switch ($value) { + case '3': return ['%placeholder1%' => 'value1']; + default: return []; + } + } + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + private function assertScalarListWithChoiceValues(ChoiceListInterface $list) { $this->assertSame(['a', 'b', 'c', 'd'], $list->getValues()); @@ -894,6 +1003,21 @@ private function assertFlatViewWithAttr($view) ), $view); } + private function assertFlatViewWithlabelTranslationParameters($view) + { + $this->assertEquals(new ChoiceListView( + [ + 0 => new ChoiceView($this->obj1, '0', 'A'), + 1 => new ChoiceView($this->obj2, '1', 'B'), + 2 => new ChoiceView($this->obj3, '2', 'C'), + 3 => new ChoiceView($this->obj4, '3', 'D', [], ['%placeholder1%' => 'value1']), + ], [ + 1 => new ChoiceView($this->obj2, '1', 'B'), + 2 => new ChoiceView($this->obj3, '2', 'C'), + ] + ), $view); + } + private function assertGroupedView($view) { $this->assertEquals(new ChoiceListView( diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 16ede19493d5d..663aac3dcc456 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -1782,14 +1782,26 @@ public function testPassChoiceDataToView() 'choices' => [$obj1, $obj2, $obj3, $obj4], 'choice_label' => 'label', 'choice_value' => 'value', + 'choice_attr' => [ + ['attr1' => 'value1'], + ['attr2' => 'value2'], + ['attr3' => 'value3'], + ['attr4' => 'value4'], + ], + 'choice_translation_parameters' => [ + ['%placeholder1%' => 'value1'], + ['%placeholder2%' => 'value2'], + ['%placeholder3%' => 'value3'], + ['%placeholder4%' => 'value4'], + ], ]) ->createView(); $this->assertEquals([ - new ChoiceView($obj1, 'a', 'A'), - new ChoiceView($obj2, 'b', 'B'), - new ChoiceView($obj3, 'c', 'C'), - new ChoiceView($obj4, 'd', 'D'), + new ChoiceView($obj1, 'a', 'A', ['attr1' => 'value1'], ['%placeholder1%' => 'value1']), + new ChoiceView($obj2, 'b', 'B', ['attr2' => 'value2'], ['%placeholder2%' => 'value2']), + new ChoiceView($obj3, 'c', 'C', ['attr3' => 'value3'], ['%placeholder3%' => 'value3']), + new ChoiceView($obj4, 'd', 'D', ['attr4' => 'value4'], ['%placeholder4%' => 'value4']), ], $view->vars['choices']); } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json index cc7d5544a95eb..3cd6ca5ca40ca 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json @@ -9,6 +9,7 @@ "choice_loader", "choice_name", "choice_translation_domain", + "choice_translation_parameters", "choice_value", "choices", "expanded", diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt index 74603552e0d1d..0952ccc3e54ec 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt @@ -2,44 +2,44 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice") ============================================================================== - --------------------------- -------------------- ------------------------------ ----------------------- - Options Overridden options Parent options Extension options - --------------------------- -------------------- ------------------------------ ----------------------- - choice_attr FormType FormType FormTypeCsrfExtension - choice_filter -------------------- ------------------------------ ----------------------- - choice_label compound action csrf_field_name - choice_loader data_class allow_file_upload csrf_message - choice_name empty_data attr csrf_protection - choice_translation_domain error_bubbling attr_translation_parameters csrf_token_id - choice_value invalid_message auto_initialize csrf_token_manager - choices trim block_name - expanded block_prefix - group_by by_reference - multiple data - placeholder disabled - preferred_choices getter - help - help_attr - help_html - help_translation_parameters - inherit_data - invalid_message_parameters - is_empty_callback - label - label_attr - label_format - label_html - label_translation_parameters - mapped - method - post_max_size_message - property_path - required - row_attr - setter - translation_domain - upload_max_size_message - --------------------------- -------------------- ------------------------------ ----------------------- + ------------------------------- -------------------- ------------------------------ ----------------------- + Options Overridden options Parent options Extension options + ------------------------------- -------------------- ------------------------------ ----------------------- + choice_attr FormType FormType FormTypeCsrfExtension + choice_filter -------------------- ------------------------------ ----------------------- + choice_label compound action csrf_field_name + choice_loader data_class allow_file_upload csrf_message + choice_name empty_data attr csrf_protection + choice_translation_domain error_bubbling attr_translation_parameters csrf_token_id + choice_translation_parameters invalid_message auto_initialize csrf_token_manager + choice_value trim block_name + choices block_prefix + expanded by_reference + group_by data + multiple disabled + placeholder getter + preferred_choices help + help_attr + help_html + help_translation_parameters + inherit_data + invalid_message_parameters + is_empty_callback + label + label_attr + label_format + label_html + label_translation_parameters + mapped + method + post_max_size_message + property_path + required + row_attr + setter + translation_domain + upload_max_size_message + ------------------------------- -------------------- ------------------------------ ----------------------- Parent types ------------ From 14e36a22d69ab9e10b5335d8169cffab3d9de789 Mon Sep 17 00:00:00 2001 From: Ferran Vidal Date: Thu, 10 Dec 2020 16:38:46 +0100 Subject: [PATCH 036/188] [Cache] Make use of `read_timeout` in `\RedisSentinel` and `\Redis` Both classes have an optional argument `$readTimeout` that can be set during initialization for `\RedisSentinel` and during `connect`/`pconnect` respectively. --- src/Symfony/Component/Cache/Traits/RedisTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 55750f8feb64d..851ff9832a68f 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -184,7 +184,7 @@ public static function createConnection($dsn, array $options = []) $port = $hosts[0]['port'] ?? null; if (isset($params['redis_sentinel'])) { - $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']); + $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout']); if (![$host, $port] = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $params['redis_sentinel'], $host, $port)); @@ -192,7 +192,7 @@ public static function createConnection($dsn, array $options = []) } try { - @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']); + @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout']); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); $isConnected = $redis->isConnected(); From 276bbb5e85694ccdbd3d38140571facd1a3f6299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Renan=20Gon=C3=A7alves?= Date: Thu, 10 Dec 2020 18:12:52 +0100 Subject: [PATCH 037/188] [Cache] Bugfix provide the correct host and port when throwing the exception --- .../Cache/Tests/Adapter/RedisAdapterSentinelTest.php | 9 +++++++++ src/Symfony/Component/Cache/Traits/RedisTrait.php | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php index 84ea7969ad962..ad69293b88efd 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php @@ -41,4 +41,13 @@ public function testInvalidDSNHasBothClusterAndSentinel() $dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&redis_cluster=1&redis_sentinel=mymaster'; RedisAdapter::createConnection($dsn); } + + public function testExceptionMessageWhenFailingToRetrieveMasterInformation() + { + $hosts = getenv('REDIS_SENTINEL_HOSTS'); + $firstHost = explode(' ', $hosts)[0]; + $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Failed to retrieve master information from master name "invalid-masterset-name" and address "'.$firstHost.'".'); + AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['redis_sentinel' => 'invalid-masterset-name']); + } } diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 851ff9832a68f..24d075b58df93 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -186,9 +186,11 @@ public static function createConnection($dsn, array $options = []) if (isset($params['redis_sentinel'])) { $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout']); - if (![$host, $port] = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { + if (!$address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $params['redis_sentinel'], $host, $port)); } + + [$host, $port] = $address; } try { From d985ca9a6e6f805e88814ae38efd67ae340dfce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joachim=20L=C3=B8vgaard?= Date: Thu, 10 Dec 2020 11:00:14 +0100 Subject: [PATCH 038/188] [Messenger] Added more descriptive exception message when handling of a message failed --- .../Messenger/Exception/HandlerFailedException.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php b/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php index 50172c38bdfbb..74443e9dcd2e6 100644 --- a/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php +++ b/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php @@ -25,10 +25,13 @@ public function __construct(Envelope $envelope, array $exceptions) { $firstFailure = current($exceptions); + $message = sprintf('Handling "%s" failed: ', \get_class($envelope->getMessage())); + parent::__construct( - 1 === \count($exceptions) + $message.(1 === \count($exceptions) ? $firstFailure->getMessage() - : sprintf('%d handlers failed. First failure is: "%s"', \count($exceptions), $firstFailure->getMessage()), + : sprintf('%d handlers failed. First failure is: %s', \count($exceptions), $firstFailure->getMessage()) + ), (int) $firstFailure->getCode(), $firstFailure ); From fb22eece5e58bc695520ccc117b1981b979d3012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Thu, 10 Dec 2020 16:40:38 +0100 Subject: [PATCH 039/188] Fix CS in changelogs --- src/Symfony/Component/Cache/CHANGELOG.md | 2 +- src/Symfony/Component/Ldap/CHANGELOG.md | 2 +- src/Symfony/Component/Messenger/CHANGELOG.md | 2 +- src/Symfony/Component/Security/CHANGELOG.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 2b6c4b177cee9..38d60a390d94b 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 5.3.0 ----- -* added support for connecting to Redis Sentinel clusters when using the Redis PHP extension + * added support for connecting to Redis Sentinel clusters when using the Redis PHP extension 5.2.0 ----- diff --git a/src/Symfony/Component/Ldap/CHANGELOG.md b/src/Symfony/Component/Ldap/CHANGELOG.md index b232f657dbc78..b9aac6fb0590e 100644 --- a/src/Symfony/Component/Ldap/CHANGELOG.md +++ b/src/Symfony/Component/Ldap/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 5.3.0 ----- -* Added caseSensitive option for attribute keys in the Entry class. + * Added caseSensitive option for attribute keys in the Entry class. 5.1.0 ----- diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 034fe96ed0086..a8b317952ea5a 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 5.3.0 ----- -* `InMemoryTransport` can perform message serialization through dsn `in-memory://?serialize=true`. + * `InMemoryTransport` can perform message serialization through dsn `in-memory://?serialize=true`. 5.2.0 ----- diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 2f7c94deed1bc..aeacb4cc4b561 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 5.3.0 ----- -* Deprecated voters that do not return a valid decision when calling the `vote` method. + * Deprecated voters that do not return a valid decision when calling the `vote` method. 5.2.0 ----- From 421d01b87244d502aee65e4f4fa464b71f0c7d3f Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 9 Dec 2020 15:46:29 +0200 Subject: [PATCH 040/188] Add HeaderBlock for slack notifier --- .../Bridge/Slack/Block/SlackHeaderBlock.php | 51 +++++++++++++++++++ .../Notifier/Bridge/Slack/CHANGELOG.md | 1 + .../Tests/Block/SlackHeaderBlockTest.php | 51 +++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php new file mode 100644 index 0000000000000..c958962f1b6a4 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Slack\Block; + +use Symfony\Component\Notifier\Exception\LogicException; + +/** + * @author Tomas Norkūnas + * + * @experimental in 5.3 + */ +final class SlackHeaderBlock extends AbstractSlackBlock +{ + private const TEXT_LIMIT = 150; + private const ID_LIMIT = 255; + + public function __construct(string $text) + { + if (\strlen($text) > self::TEXT_LIMIT) { + throw new LogicException(sprintf('Maximum length for the text is %d characters.', self::TEXT_LIMIT)); + } + + $this->options = [ + 'type' => 'header', + 'text' => [ + 'type' => 'plain_text', + 'text' => $text, + ], + ]; + } + + public function id(string $id): self + { + if (\strlen($id) > self::ID_LIMIT) { + throw new LogicException(sprintf('Maximum length for the block id is %d characters.', self::ID_LIMIT)); + } + + $this->options['block_id'] = $id; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md index b860fa08db7f4..650a09bc1eec8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Check for maximum number of buttons in Slack action block + * Add HeaderBlock 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php new file mode 100644 index 0000000000000..c899b1822c091 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Slack\Tests\Block; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\Slack\Block\SlackHeaderBlock; +use Symfony\Component\Notifier\Exception\LogicException; + +final class SlackHeaderBlockTest extends TestCase +{ + public function testCanBeInstantiated(): void + { + $header = new SlackHeaderBlock('header text'); + $header->id('header_id'); + + $this->assertSame([ + 'type' => 'header', + 'text' => [ + 'type' => 'plain_text', + 'text' => 'header text', + ], + 'block_id' => 'header_id', + ], $header->toArray()); + } + + public function testThrowsWhenTextExceedsCharacterLimit(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Maximum length for the text is 150 characters.'); + + new SlackHeaderBlock(str_repeat('h', 151)); + } + + public function testThrowsWhenBlockIdExceedsCharacterLimit(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Maximum length for the block id is 255 characters.'); + + $header = new SlackHeaderBlock('header'); + $header->id(str_repeat('h', 256)); + } +} From cf1d352eac0f8ac16c704544d9fc8116c1c12e4d Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 7 Dec 2020 15:44:34 +0100 Subject: [PATCH 041/188] [Notifier] [DX] UnsupportedMessageTypeException for notifier transports --- .../Bridge/Discord/DiscordTransport.php | 3 +- .../Discord/Tests/DiscordTransportTest.php | 5 +-- .../Notifier/Bridge/Discord/composer.json | 2 +- .../Bridge/Esendex/EsendexTransport.php | 4 +-- .../Esendex/Tests/EsendexTransportTest.php | 5 +-- .../Notifier/Bridge/Esendex/composer.json | 2 +- .../Bridge/Firebase/FirebaseTransport.php | 4 +-- .../Notifier/Bridge/Firebase/composer.json | 2 +- .../Bridge/FreeMobile/FreeMobileTransport.php | 4 +-- .../Tests/FreeMobileTransportTest.php | 6 ++-- .../Notifier/Bridge/FreeMobile/composer.json | 2 +- .../Bridge/GoogleChat/GoogleChatTransport.php | 3 +- .../Tests/GoogleChatTransportTest.php | 5 +-- .../Notifier/Bridge/GoogleChat/composer.json | 2 +- .../Bridge/Infobip/InfobipTransport.php | 4 +-- .../Notifier/Bridge/Infobip/composer.json | 2 +- .../Bridge/LinkedIn/LinkedInTransport.php | 3 +- .../LinkedIn/Tests/LinkedInTransportTest.php | 5 +-- .../Notifier/Bridge/LinkedIn/composer.json | 2 +- .../Bridge/Mattermost/MattermostTransport.php | 4 +-- .../Notifier/Bridge/Mattermost/composer.json | 2 +- .../Notifier/Bridge/Mobyt/MobytTransport.php | 3 +- .../Notifier/Bridge/Mobyt/composer.json | 2 +- .../Notifier/Bridge/Nexmo/NexmoTransport.php | 4 +-- .../Notifier/Bridge/Nexmo/composer.json | 2 +- .../Bridge/OvhCloud/OvhCloudTransport.php | 4 +-- .../Notifier/Bridge/OvhCloud/composer.json | 2 +- .../Bridge/RocketChat/RocketChatTransport.php | 3 +- .../Notifier/Bridge/RocketChat/composer.json | 2 +- .../Bridge/Sendinblue/SendinblueTransport.php | 4 +-- .../Tests/SendinblueTransportTest.php | 5 +-- .../Notifier/Bridge/Sendinblue/composer.json | 2 +- .../Notifier/Bridge/Sinch/SinchTransport.php | 4 +-- .../Notifier/Bridge/Sinch/composer.json | 2 +- .../Notifier/Bridge/Slack/SlackTransport.php | 3 +- .../Bridge/Slack/Tests/SlackTransportTest.php | 5 +-- .../Notifier/Bridge/Slack/composer.json | 2 +- .../Bridge/Smsapi/SmsapiTransport.php | 4 +-- .../Notifier/Bridge/Smsapi/composer.json | 2 +- .../Bridge/Telegram/TelegramTransport.php | 3 +- .../Telegram/Tests/TelegramTransportTest.php | 5 +-- .../Notifier/Bridge/Telegram/composer.json | 2 +- .../Bridge/Twilio/TwilioTransport.php | 4 +-- .../Notifier/Bridge/Twilio/composer.json | 2 +- .../Notifier/Bridge/Zulip/ZulipTransport.php | 3 +- .../Notifier/Bridge/Zulip/composer.json | 2 +- .../UnsupportedMessageTypeException.php | 34 +++++++++++++++++++ .../Notifier/Transport/TransportInterface.php | 1 + 48 files changed, 116 insertions(+), 66 deletions(-) create mode 100644 src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.php diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php index 1ad292c17c136..4a89e6819878d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -57,7 +58,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } $messageOptions = $message->getOptions(); diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php index 097b1ad25caf8..c42a3c6d205a9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php @@ -14,8 +14,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransport; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -43,9 +43,10 @@ public function testSupportsChatMessage() public function testSendNonChatMessageThrows() { - $this->expectException(LogicException::class); $transport = new DiscordTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class)); + $this->expectException(UnsupportedMessageTypeException::class); + $transport->send($this->createMock(MessageInterface::class)); } diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json index a47bc90ac9620..72987e5a9fd42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "require-dev": { "symfony/event-dispatcher": "^4.3|^5.0" diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php index bc8c7a6207e1b..521bbe3a3ceda 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php @@ -13,8 +13,8 @@ use Symfony\Component\HttpClient\Exception\JsonException; use Symfony\Component\HttpClient\Exception\TransportException as HttpClientTransportException; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -55,7 +55,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $messageData = [ diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php index 09bf2844acc7b..3242739119be3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php @@ -14,8 +14,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransport; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -43,7 +43,8 @@ public function testSendNonSmsMessageThrows(): void { $transport = new EsendexTransport('testToken', 'accountReference', 'from', $this->createMock(HttpClientInterface::class)); - $this->expectException(LogicException::class); + $this->expectException(UnsupportedMessageTypeException::class); + $transport->send($this->createMock(MessageInterface::class)); } diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json b/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json index cb0404171cc17..6358037b60394 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.4|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Esendex\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php index 4734ad4ea0674..77d4baef0f2d3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Notifier\Bridge\Firebase; use Symfony\Component\Notifier\Exception\InvalidArgumentException; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -54,7 +54,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } $endpoint = sprintf('https://%s', $this->getEndpoint()); diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json b/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json index a8d4c83afe912..782c5239be47b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Firebase\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php index 2113b014d7143..4da92c2fc4a6a 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\FreeMobile; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -55,7 +55,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$this->supports($message)) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given) and configured with your phone number.', __CLASS__, SmsMessage::class, \get_class($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $response = $this->client->request('POST', $this->getEndpoint(), [ diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php index e893de743408b..9b05e02f02daf 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransport; -use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -46,7 +46,7 @@ public function testSendNonSmsMessageThrowsException(): void { $transport = $this->getTransport('0611223344'); - $this->expectException(LogicException::class); + $this->expectException(UnsupportedMessageTypeException::class); $transport->send($this->createMock(MessageInterface::class)); } @@ -55,7 +55,7 @@ public function testSendSmsMessageButInvalidPhoneThrowsException(): void { $transport = $this->getTransport('0611223344'); - $this->expectException(LogicException::class); + $this->expectException(UnsupportedMessageTypeException::class); $transport->send(new SmsMessage('0699887766', 'Hello!')); } diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json b/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json index cfc329d93446b..36d7277c44f80 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json @@ -19,7 +19,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.1", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\FreeMobile\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php index 9037644d60345..38a7910122216 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpClient\Exception\JsonException; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -87,7 +88,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, \get_class($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } if ($message->getOptions() && !$message->getOptions() instanceof GoogleChatOptions) { throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, GoogleChatOptions::class)); diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php index cf6e65d19077f..9a959a8625ae6 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransport; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\MessageOptionsInterface; @@ -44,10 +45,10 @@ public function testSupportsChatMessage(): void public function testSendNonChatMessageThrows(): void { - $this->expectException(LogicException::class); - $transport = new GoogleChatTransport('My-Space', 'theAccessKey', 'theAccessToken=', $this->createMock(HttpClientInterface::class)); + $this->expectException(UnsupportedMessageTypeException::class); + $transport->send($this->createMock(MessageInterface::class)); } diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json b/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json index cc6bb5de4402e..cb24cfc52fd6a 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\GoogleChat\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php index f25a8a98862a7..82880eaf83cf0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\Infobip; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -52,7 +52,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $endpoint = sprintf('https://%s/sms/2/text/advanced', $this->getEndpoint()); diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json b/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json index 579f059b77906..00d7e4386b7ed 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json @@ -22,7 +22,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Infobip\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php index ae078b25e64c3..970c28addfd24 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php @@ -14,6 +14,7 @@ use Symfony\Component\Notifier\Bridge\LinkedIn\Share\AuthorShare; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -61,7 +62,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, \get_class($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } if ($message->getOptions() && !$message->getOptions() instanceof LinkedInOptions) { throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, LinkedInOptions::class)); diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php index 82ff0cde5df45..4f0bdc058a45d 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php @@ -7,6 +7,7 @@ use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransport; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\MessageOptionsInterface; @@ -31,10 +32,10 @@ public function testSupportsChatMessage(): void public function testSendNonChatMessageThrows(): void { - $this->expectException(LogicException::class); - $transport = $this->getTransport(); + $this->expectException(UnsupportedMessageTypeException::class); + $transport->send($this->createMock(MessageInterface::class)); } diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json b/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json index 3210cae14e56d..36e10ccf5699b 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\LinkedIn\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php index c27df25a3526f..1ceba9a8ef105 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\Mattermost; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -54,7 +54,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } $endpoint = sprintf('https://%s/api/v4/posts', $this->getEndpoint()); diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json b/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json index b35bb906ae5aa..9f94864b3bc12 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mattermost\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php index 15604438576f1..320f83535c89e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -57,7 +58,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, \get_class($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } if ($message->getOptions() && !$message->getOptions() instanceof MobytOptions) { diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json b/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json index 96a59c8a53174..6bfe03089afa7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json @@ -19,7 +19,7 @@ "ext-json": "*", "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mobyt\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php index 5451fc8af47d4..528da637ae362 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\Nexmo; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -55,7 +55,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/sms/json', [ diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json b/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json index f6f795fd5c538..74cf98d8cc1fb 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Nexmo\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php index 8d71d23b4a69c..1611d3c76ddb6 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\OvhCloud; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -57,7 +57,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $endpoint = sprintf('https://%s/1.0/sms/%s/jobs', $this->getEndpoint(), $this->serviceName); diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json b/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json index 14149d6eec834..7030cb30143d0 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\OvhCloud\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php index 737aaa1b99c9f..f78b1cbcd9c06 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -57,7 +58,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } if ($message->getOptions() && !$message->getOptions() instanceof RocketChatOptions) { throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, RocketChatOptions::class)); diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json b/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json index 1eb4a83e0d2ce..823f51d02cef6 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\RocketChat\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php index 70b62e78d7a87..46f35e825f5d8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\Sendinblue; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -53,7 +53,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v3/transactionalSMS/sms', [ diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/Tests/SendinblueTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Sendinblue/Tests/SendinblueTransportTest.php index 7c418edef8745..b283b1fa20552 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/Tests/SendinblueTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/Tests/SendinblueTransportTest.php @@ -14,8 +14,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransport; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -42,7 +42,8 @@ public function testSendNonSmsMessageThrowsException(): void { $transport = $this->initTransport(); - $this->expectException(LogicException::class); + $this->expectException(UnsupportedMessageTypeException::class); + $transport->send($this->createMock(MessageInterface::class)); } diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json b/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json index ed0ff3147025a..64b4668669e16 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json @@ -19,7 +19,7 @@ "ext-json": "*", "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Sendinblue\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php index e14925764a491..9f4ecd03f730f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\Sinch; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -55,7 +55,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $endpoint = sprintf('https://%s/xms/v1/%s/batches', $this->getEndpoint(), $this->accountSid); diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json b/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json index ba07c5a4e8817..6ff9d8e282ea0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-json": "*", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Sinch\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php index 71f67139ec784..bdebd2a3fda26 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -57,7 +58,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } if ($message->getOptions() && !$message->getOptions() instanceof SlackOptions) { throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, SlackOptions::class)); diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php index 3c9f92c87c164..12db5d609766f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Notifier\Bridge\Slack\SlackTransport; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\MessageOptionsInterface; @@ -47,10 +48,10 @@ public function testSupportsChatMessage(): void public function testSendNonChatMessageThrows(): void { - $this->expectException(LogicException::class); - $transport = new SlackTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class)); + $this->expectException(UnsupportedMessageTypeException::class); + $transport->send($this->createMock(MessageInterface::class)); } diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json index 72acfe811be6d..24f0287d41bbe 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "require-dev": { "symfony/event-dispatcher": "^4.3|^5.0" diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php index 0abf6ee3fb7d0..0451fe768e981 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\Smsapi; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -52,7 +52,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $endpoint = sprintf('https://%s/sms.do', $this->getEndpoint()); diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json b/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json index 4df836c7ec00a..28564eaa91c96 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Smsapi\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php index a0ca53232e54d..ba953cbd1c81d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -62,7 +63,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } if ($message->getOptions() && !$message->getOptions() instanceof TelegramOptions) { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php index c9e759a52d5c1..f856df6c38083 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php @@ -15,8 +15,8 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransport; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -44,9 +44,10 @@ public function testSupportsChatMessage(): void public function testSendNonChatMessageThrows(): void { - $this->expectException(LogicException::class); $transport = new TelegramTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class)); + $this->expectException(UnsupportedMessageTypeException::class); + $transport->send($this->createMock(MessageInterface::class)); } diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json index 404aec345055d..ffca90186448a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "require-dev": { "symfony/event-dispatcher": "^4.3|^5.0" diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php index dd0a5858f3e54..1a0869de211a0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Notifier\Bridge\Twilio; -use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -55,7 +55,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof SmsMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, SmsMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } $endpoint = sprintf('https://%s/2010-04-01/Accounts/%s/Messages.json', $this->getEndpoint(), $this->accountSid); diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json b/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json index c28ae88b91877..04bc30f7306ca 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Twilio\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php index 61cee7bb61fb2..c43a1660aa03f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; @@ -56,7 +57,7 @@ public function supports(MessageInterface $message): bool protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { - throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } if (null !== $message->getOptions() && !($message->getOptions() instanceof ZulipOptions)) { diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json b/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json index 77fe8b5257508..b96a349288591 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "^5.2" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Zulip\\": "" }, diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.php new file mode 100644 index 0000000000000..d52ce21417c50 --- /dev/null +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.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\Component\Notifier\Exception; + +use Symfony\Component\Notifier\Message\MessageInterface; + +/** + * @author Oskar Stark + * + * @experimental in 5.3 + */ +class UnsupportedMessageTypeException extends LogicException +{ + public function __construct(string $transport, string $supported, MessageInterface $given) + { + $message = sprintf( + 'The "%s" transport only supports instances of "%s" (instance of "%s" given).', + $transport, + $supported, + get_debug_type($given) + ); + + parent::__construct($message); + } +} diff --git a/src/Symfony/Component/Notifier/Transport/TransportInterface.php b/src/Symfony/Component/Notifier/Transport/TransportInterface.php index 1ab2ff4c7443d..812b03919ed87 100644 --- a/src/Symfony/Component/Notifier/Transport/TransportInterface.php +++ b/src/Symfony/Component/Notifier/Transport/TransportInterface.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Notifier\Transport; use Symfony\Component\Notifier\Exception\TransportExceptionInterface; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; From 7b0a1839203af6691bb3af9de4c78689e966dda2 Mon Sep 17 00:00:00 2001 From: prosalov Date: Fri, 11 Dec 2020 21:51:28 +0000 Subject: [PATCH 042/188] [DomCrawler] Fix null namespace issue in Crawler --- src/Symfony/Component/DomCrawler/Crawler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 2b4e1dfaaaad9..fe00fa3fb8238 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -1194,11 +1194,11 @@ private function createDOMXPath(\DOMDocument $document, array $prefixes = []): \ */ private function discoverNamespace(\DOMXPath $domxpath, string $prefix): ?string { - if (isset($this->namespaces[$prefix])) { + if (\array_key_exists($prefix, $this->namespaces)) { return $this->namespaces[$prefix]; } - if (isset($this->cachedNamespaces[$prefix])) { + if ($this->cachedNamespaces->offsetExists($prefix)) { return $this->cachedNamespaces[$prefix]; } From 59db08b1a5847f3952e1724c43899287e6c60334 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sun, 13 Dec 2020 14:11:58 +0100 Subject: [PATCH 043/188] [FrameworkBundle] Add mailer monolog channel on mailer transport definitions --- .../FrameworkBundle/Resources/config/mailer_transports.php | 1 + src/Symfony/Component/Mailer/CHANGELOG.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php index ec6dd28aafbc8..9c330ab2c7333 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php @@ -34,6 +34,7 @@ service('http_client')->ignoreOnInvalid(), service('logger')->ignoreOnInvalid(), ]) + ->tag('monolog.logger', ['channel' => 'mailer']) ->set('mailer.transport_factory.amazon', SesTransportFactory::class) ->parent('mailer.transport_factory.abstract') diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index cca04f11aa78e..b5a1800502c0c 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * added the `mailer` monolog channel and set it on all transport definitions + 5.2.0 ----- From e2198a892fe752f50400398e7bb23b8e30ab219f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sun, 13 Dec 2020 23:45:00 +0100 Subject: [PATCH 044/188] [PhpUnitBridge] Restore SetUpTearDownTraitForV5 --- UPGRADE-5.3.md | 5 ++ UPGRADE-6.0.md | 1 + src/Symfony/Bridge/PhpUnit/CHANGELOG.md | 1 + .../Legacy/SetUpTearDownTraitForV7.php | 70 +++++++++++++++++++ .../Bridge/PhpUnit/SetUpTearDownTrait.php | 6 +- .../PhpUnit/Tests/CoverageListenerTest.php | 2 +- .../DeprecationTest.php | 15 ++-- 7 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV7.php diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 4d991a30805c2..922a1b0ad858e 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -18,6 +18,11 @@ HttpKernel * Marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal +PhpunitBridge +------------- + + * Deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint. + Security -------- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 2d9090a3c58c2..0413478ff7f19 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -129,6 +129,7 @@ PhpUnitBridge ------------- * Removed support for `@expectedDeprecation` annotations, use the `ExpectDeprecationTrait::expectDeprecation()` method instead. + * Removed the `SetUpTearDownTrait` trait, use original methods with "void" return typehint. PropertyAccess -------------- diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index a8a4e2df43408..35f65cd492fa2 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * bumped the minimum PHP version to 7.1.3 * bumped the minimum PHPUnit version to 7.5 + * deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint. 5.1.0 ----- diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV7.php new file mode 100644 index 0000000000000..599ffcad9f19f --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV7.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\Bridge\PhpUnit\Legacy; + +/** + * @internal + */ +trait SetUpTearDownTraitForV7 +{ + /** + * @return void + */ + public static function setUpBeforeClass() + { + self::doSetUpBeforeClass(); + } + + /** + * @return void + */ + public static function tearDownAfterClass() + { + self::doTearDownAfterClass(); + } + + /** + * @return void + */ + protected function setUp() + { + self::doSetUp(); + } + + /** + * @return void + */ + protected function tearDown() + { + self::doTearDown(); + } + + private static function doSetUpBeforeClass() + { + parent::setUpBeforeClass(); + } + + private static function doTearDownAfterClass() + { + parent::tearDownAfterClass(); + } + + private function doSetUp() + { + parent::setUp(); + } + + private function doTearDown() + { + parent::tearDown(); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php b/src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php index e27c3a4fb0934..04eee45be13a3 100644 --- a/src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php +++ b/src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php @@ -13,12 +13,14 @@ use PHPUnit\Framework\TestCase; +trigger_deprecation('symfony/phpunit-bridge', '5.3', 'The "%s" trait is deprecated, use original methods with "void" return typehint.', SetUpTearDownTrait::class); + // A trait to provide forward compatibility with newest PHPUnit versions $r = new \ReflectionClass(TestCase::class); -if (\PHP_VERSION_ID < 70000 || !$r->getMethod('setUp')->hasReturnType()) { +if (!$r->getMethod('setUp')->hasReturnType()) { trait SetUpTearDownTrait { - use Legacy\SetUpTearDownTraitForV5; + use Legacy\SetUpTearDownTraitForV7; } } else { trait SetUpTearDownTrait diff --git a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php index 53b2bb8d6cdff..b309606d5bd4e 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php @@ -14,7 +14,7 @@ public function test() exec('type phpdbg 2> /dev/null', $output, $returnCode); - if (\PHP_VERSION_ID >= 70000 && 0 === $returnCode) { + if (0 === $returnCode) { $php = 'phpdbg -qrr'; } else { exec('php --ri xdebug -d zend_extension=xdebug.so 2> /dev/null', $output, $returnCode); diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index c78b821e6d9bc..5e33613659ea2 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -14,13 +14,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation; -use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5; -use Symfony\Bridge\PhpUnit\SetUpTearDownTrait; +use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7; class DeprecationTest extends TestCase { - use SetUpTearDownTrait; - private static $vendorDir; private static $prefixDirsPsr4; @@ -164,7 +161,7 @@ public function providerGetTypeDetectsSelf() 'triggering_file' => 'dummy_vendor_path', 'files_stack' => [], ]), - SymfonyTestsListenerForV5::class, + SymfonyTestsListenerForV7::class, '', ], ]; @@ -208,7 +205,7 @@ public function providerGetTypeUsesRightTrace() $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php', ], ]), - [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], + [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV7::class, 'method' => 'mymethod']], ], 'serialized_stack_files_from_various_packages' => [ Deprecation::TYPE_INDIRECT, @@ -221,7 +218,7 @@ public function providerGetTypeUsesRightTrace() $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php', ], ]), - [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], + [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV7::class, 'method' => 'mymethod']], ], ]; } @@ -261,7 +258,7 @@ private static function removeDir($dir) rmdir($dir); } - private static function doSetupBeforeClass() + public static function setupBeforeClass(): void { foreach (get_declared_classes() as $class) { if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { @@ -281,7 +278,7 @@ private static function doSetupBeforeClass() } } - private static function doTearDownAfterClass() + public static function tearDownAfterClass(): void { foreach (self::$prefixDirsPsr4 as [$prop, $loader, $prefixDirsPsr4]) { $prop->setValue($loader, $prefixDirsPsr4); From 4e4a81c3461920a17845ca94bcc2d67addf05d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sun, 13 Dec 2020 19:17:49 +0100 Subject: [PATCH 045/188] Allow env variables in `json_manifest_path` --- UPGRADE-5.3.md | 5 ++ UPGRADE-6.0.md | 5 ++ .../FrameworkExtension.php | 7 +- .../Resources/config/assets.php | 2 + .../Fixtures/php/assets.php | 9 +++ .../Fixtures/xml/assets.xml | 7 ++ .../Fixtures/yml/assets.yml | 8 +++ .../FrameworkExtensionTest.php | 14 +++- .../Bundle/FrameworkBundle/composer.json | 4 +- src/Symfony/Component/Asset/CHANGELOG.md | 5 ++ .../JsonManifestVersionStrategyTest.php | 66 ++++++++++++++----- .../RemoteJsonManifestVersionStrategyTest.php | 3 + .../JsonManifestVersionStrategy.php | 29 ++++++-- .../RemoteJsonManifestVersionStrategy.php | 4 ++ src/Symfony/Component/Asset/composer.json | 3 +- 15 files changed, 138 insertions(+), 33 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 922a1b0ad858e..2ccc3bc039595 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -1,6 +1,11 @@ UPGRADE FROM 5.2 to 5.3 ======================= +Asset +----- + + * Deprecated `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead. + Form ---- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 0413478ff7f19..a72f4731efc94 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -1,6 +1,11 @@ UPGRADE FROM 5.x to 6.0 ======================= +Asset +----- + + * Removed `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead. + Config ------ diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 51af309978392..7a2506e2ee517 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1129,12 +1129,7 @@ private function createVersion(ContainerBuilder $container, ?string $version, ?s } if (null !== $jsonManifestPath) { - $definitionName = 'assets.json_manifest_version_strategy'; - if (0 === strpos(parse_url($jsonManifestPath, \PHP_URL_SCHEME), 'http')) { - $definitionName = 'assets.remote_json_manifest_version_strategy'; - } - - $def = new ChildDefinition($definitionName); + $def = new ChildDefinition('assets.json_manifest_version_strategy'); $def->replaceArgument(0, $jsonManifestPath); $container->setDefinition('assets._version_'.$name, $def); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.php index 28f7bba6a45fb..db889670c251c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.php @@ -77,10 +77,12 @@ ->abstract() ->args([ abstract_arg('manifest path'), + service('http_client'), ]) ->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/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php index ef2fd77013f85..ab16a52e21e9b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php @@ -30,6 +30,15 @@ 'remote_manifest' => [ 'json_manifest_path' => 'https://cdn.example.com/manifest.json', ], + 'var_manifest' => [ + 'json_manifest_path' => '%var_json_manifest_path%', + ], + 'env_manifest' => [ + 'json_manifest_path' => '%env(env_manifest)%', + ], ], ], ]); + +$container->setParameter('var_json_manifest_path', 'https://cdn.example.com/manifest.json'); +$container->setParameter('env(env_manifest)', 'https://cdn.example.com/manifest.json'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml index 24bfdc6456185..ae0e0e099bc93 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml @@ -23,6 +23,13 @@ + + + + + https://cdn.example.com/manifest.json + https://cdn.example.com/manifest.json + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml index 4a4a57bc43a79..ab9eb1b610ce8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml @@ -21,3 +21,11 @@ framework: json_manifest_path: '/path/to/manifest.json' remote_manifest: json_manifest_path: 'https://cdn.example.com/manifest.json' + var_manifest: + json_manifest_path: '%var_json_manifest_path%' + env_manifest: + json_manifest_path: '%env(env_manifest)%' + +parameters: + var_json_manifest_path: 'https://cdn.example.com/manifest.json' + env(env_manifest): https://cdn.example.com/manifest.json diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 6d4beaaccbbdd..63538ebff83d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -597,7 +597,7 @@ public function testAssets() // packages $packages = $packages->getArgument(1); - $this->assertCount(7, $packages); + $this->assertCount(9, $packages); $package = $container->getDefinition((string) $packages['images_path']); $this->assertPathPackage($container, $package, '/foo', 'SomeVersionScheme', '%%s?version=%%s'); @@ -621,8 +621,18 @@ public function testAssets() $package = $container->getDefinition($packages['remote_manifest']); $versionStrategy = $container->getDefinition($package->getArgument(1)); - $this->assertSame('assets.remote_json_manifest_version_strategy', $versionStrategy->getParent()); + $this->assertSame('assets.json_manifest_version_strategy', $versionStrategy->getParent()); $this->assertSame('https://cdn.example.com/manifest.json', $versionStrategy->getArgument(0)); + + $package = $container->getDefinition($packages['var_manifest']); + $versionStrategy = $container->getDefinition($package->getArgument(1)); + $this->assertSame('assets.json_manifest_version_strategy', $versionStrategy->getParent()); + $this->assertSame('https://cdn.example.com/manifest.json', $versionStrategy->getArgument(0)); + + $package = $container->getDefinition($packages['env_manifest']); + $versionStrategy = $container->getDefinition($package->getArgument(1)); + $this->assertSame('assets.json_manifest_version_strategy', $versionStrategy->getParent()); + $this->assertStringMatchesFormat('env_%s', $versionStrategy->getArgument(0)); } public function testAssetsDefaultVersionStrategyAsService() diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 8f8dae611f45e..22d582d7ecd66 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -34,7 +34,7 @@ "require-dev": { "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "symfony/asset": "^5.1", + "symfony/asset": "^5.3", "symfony/browser-kit": "^4.4|^5.0", "symfony/console": "^5.2", "symfony/css-selector": "^4.4|^5.0", @@ -71,7 +71,7 @@ "phpdocumentor/reflection-docblock": "<3.0", "phpdocumentor/type-resolver": "<0.2.1", "phpunit/phpunit": "<5.4.3", - "symfony/asset": "<5.1", + "symfony/asset": "<5.3", "symfony/browser-kit": "<4.4", "symfony/console": "<5.2", "symfony/dotenv": "<5.1", diff --git a/src/Symfony/Component/Asset/CHANGELOG.md b/src/Symfony/Component/Asset/CHANGELOG.md index 9df5fc14d0697..5fd63351cfe43 100644 --- a/src/Symfony/Component/Asset/CHANGELOG.md +++ b/src/Symfony/Component/Asset/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * deprecated `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead. + 5.1.0 ----- diff --git a/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php b/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php index 7f2d44c4a2ec3..924fb33686db4 100644 --- a/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php +++ b/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php @@ -13,47 +13,83 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; class JsonManifestVersionStrategyTest extends TestCase { - public function testGetVersion() + /** + * @dataProvider ProvideValidStrategies + */ + public function testGetVersion(JsonManifestVersionStrategy $strategy) { - $strategy = $this->createStrategy('manifest-valid.json'); - $this->assertSame('main.123abc.js', $strategy->getVersion('main.js')); } - public function testApplyVersion() + /** + * @dataProvider ProvideValidStrategies + */ + public function testApplyVersion(JsonManifestVersionStrategy $strategy) { - $strategy = $this->createStrategy('manifest-valid.json'); - $this->assertSame('css/styles.555def.css', $strategy->applyVersion('css/styles.css')); } - public function testApplyVersionWhenKeyDoesNotExistInManifest() + /** + * @dataProvider ProvideValidStrategies + */ + public function testApplyVersionWhenKeyDoesNotExistInManifest(JsonManifestVersionStrategy $strategy) { - $strategy = $this->createStrategy('manifest-valid.json'); - $this->assertSame('css/other.css', $strategy->applyVersion('css/other.css')); } - public function testMissingManifestFileThrowsException() + /** + * @dataProvider ProvideMissingStrategies + */ + public function testMissingManifestFileThrowsException(JsonManifestVersionStrategy $strategy) { $this->expectException('RuntimeException'); - $strategy = $this->createStrategy('non-existent-file.json'); $strategy->getVersion('main.js'); } - public function testManifestFileWithBadJSONThrowsException() + /** + * @dataProvider ProvideInvalidStrategies + */ + public function testManifestFileWithBadJSONThrowsException(JsonManifestVersionStrategy $strategy) { $this->expectException('RuntimeException'); $this->expectExceptionMessage('Error parsing JSON'); - $strategy = $this->createStrategy('manifest-invalid.json'); $strategy->getVersion('main.js'); } - private function createStrategy($manifestFilename) + public function provideValidStrategies() + { + yield from $this->provideStrategies('manifest-valid.json'); + } + + public function provideInvalidStrategies() + { + yield from $this->provideStrategies('manifest-invalid.json'); + } + + public function provideMissingStrategies() + { + yield from $this->provideStrategies('non-existent-file.json'); + } + + public function provideStrategies(string $manifestPath) { - return new JsonManifestVersionStrategy(__DIR__.'/../fixtures/'.$manifestFilename); + $httpClient = new MockHttpClient(function ($method, $url, $options) { + $filename = __DIR__.'/../fixtures/'.basename($url); + + if (file_exists($filename)) { + return new MockResponse(file_get_contents($filename), ['http_headers' => ['content-type' => 'application/json']]); + } + + return new MockResponse('{}', ['http_code' => 404]); + }); + + yield [new JsonManifestVersionStrategy('https://cdn.example.com/'.$manifestPath, $httpClient)]; + + yield [new JsonManifestVersionStrategy(__DIR__.'/../fixtures/'.$manifestPath)]; } } diff --git a/src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php b/src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php index cc153c5487005..1ea8f100e7745 100644 --- a/src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php +++ b/src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php @@ -17,6 +17,9 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; +/** + * @group legacy + */ class RemoteJsonManifestVersionStrategyTest extends TestCase { public function testGetVersion() diff --git a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php index c4de580d0b514..3314d9e17c96f 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php @@ -11,6 +11,9 @@ namespace Symfony\Component\Asset\VersionStrategy; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + /** * Reads the versioned path of an asset from a JSON manifest file. * @@ -26,13 +29,15 @@ class JsonManifestVersionStrategy implements VersionStrategyInterface { private $manifestPath; private $manifestData; + private $httpClient; /** * @param string $manifestPath Absolute path to the manifest file */ - public function __construct(string $manifestPath) + public function __construct(string $manifestPath, HttpClientInterface $httpClient = null) { $this->manifestPath = $manifestPath; + $this->httpClient = $httpClient; } /** @@ -53,13 +58,23 @@ public function applyVersion(string $path) private function getManifestPath(string $path): ?string { if (null === $this->manifestData) { - if (!is_file($this->manifestPath)) { - throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist.', $this->manifestPath)); - } + if (null !== $this->httpClient && 0 === strpos(parse_url($this->manifestPath, \PHP_URL_SCHEME), 'http')) { + try { + $this->manifestData = $this->httpClient->request('GET', $this->manifestPath, [ + 'headers' => ['accept' => 'application/json'], + ])->toArray(); + } catch (DecodingExceptionInterface $e) { + throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest URL "%s".', $this->manifestPath), 0, $e); + } + } else { + if (!is_file($this->manifestPath)) { + throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist.', $this->manifestPath)); + } - $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); - if (0 < json_last_error()) { - throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": ', $this->manifestPath).json_last_error_msg()); + $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); + if (0 < json_last_error()) { + throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": ', $this->manifestPath).json_last_error_msg()); + } } } diff --git a/src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php index db45b3b7ec177..cc6170a27e4c2 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php @@ -13,6 +13,8 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; +trigger_deprecation('symfony/asset', '5.3', 'The "%s" class is deprecated, use "%s" instead.', RemoteJsonManifestVersionStrategy::class, JsonManifestVersionStrategy::class); + /** * Reads the versioned path of an asset from a remote JSON manifest file. * @@ -23,6 +25,8 @@ * } * * You could then ask for the version of "main.js" or "css/styles.css". + * + * @deprecated since Symfony 5.3, use JsonManifestVersionStrategy instead. */ class RemoteJsonManifestVersionStrategy implements VersionStrategyInterface { diff --git a/src/Symfony/Component/Asset/composer.json b/src/Symfony/Component/Asset/composer.json index 65295f8797432..79b0b1d6e9a84 100644 --- a/src/Symfony/Component/Asset/composer.json +++ b/src/Symfony/Component/Asset/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "php": ">=7.2.5" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1" }, "suggest": { "symfony/http-foundation": "" From 00d11735d6301f9cdc3d813caeade5784c60b04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Mon, 14 Dec 2020 21:55:44 +0100 Subject: [PATCH 046/188] Add missing symfony/deprecation-contracts requirement --- src/Symfony/Bridge/PhpUnit/composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 9095e1a46cba5..db31ddd58fd19 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -18,10 +18,10 @@ "require": { "php": ">=7.1.3 EVEN ON LATEST SYMFONY VERSIONS TO ALLOW USING", "php": "THIS BRIDGE WHEN TESTING LOWEST SYMFONY VERSIONS.", - "php": ">=7.1.3" + "php": ">=7.1.3", + "symfony/deprecation-contracts": "^2.1" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1", "symfony/error-handler": "^4.4|^5.0" }, "suggest": { From 414591448259c8e72bbb33e98394e06b4e59e9a8 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 15 Dec 2020 00:14:20 +0100 Subject: [PATCH 047/188] [PhpUnitBridge] Remove linter. --- .github/workflows/phpunit-bridge.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/phpunit-bridge.yml diff --git a/.github/workflows/phpunit-bridge.yml b/.github/workflows/phpunit-bridge.yml deleted file mode 100644 index bdda65de1b6f5..0000000000000 --- a/.github/workflows/phpunit-bridge.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: PhpUnitBridge - -on: - push: - paths: - - 'src/Symfony/Bridge/PhpUnit/**' - pull_request: - paths: - - 'src/Symfony/Bridge/PhpUnit/**' - -jobs: - - lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - coverage: "none" - php-version: "5.5" - - - name: Lint - run: find ./src/Symfony/Bridge/PhpUnit -name '*.php' | grep -v -e /Tests/ -e ForV6 -e ForV7 -e ForV8 -e ForV9 -e ConstraintLogicTrait | parallel -j 4 php -l {} From 437cad7c2b425226661074341d610a4d824334f4 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 14 Dec 2020 14:28:22 +0100 Subject: [PATCH 048/188] [Notifier] Introduce LengthException --- .../Bridge/Discord/DiscordTransport.php | 4 ++-- .../Discord/Tests/DiscordTransportTest.php | 4 ++-- .../Bridge/Slack/Block/SlackHeaderBlock.php | 6 +++--- .../Tests/Block/SlackHeaderBlockTest.php | 6 +++--- .../Notifier/Exception/LengthException.php | 21 +++++++++++++++++++ 5 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 src/Symfony/Component/Notifier/Exception/LengthException.php diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php index 70b638db19c4a..b8b865b81208e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Notifier\Bridge\Discord; -use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\LengthException; use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; @@ -67,7 +67,7 @@ protected function doSend(MessageInterface $message): SentMessage $content = $message->getSubject(); if (\strlen($content) > 2000) { - throw new LogicException('The subject length of a Discord message must not exceed 2000 characters.'); + throw new LengthException('The subject length of a Discord message must not exceed 2000 characters.'); } $endpoint = sprintf('https://%s/api/webhooks/%s/%s', $this->getEndpoint(), $this->webhookId, $this->token); diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php index 5f034db10bd85..3a64c1bd76879 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransport; -use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\LengthException; use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; @@ -55,7 +55,7 @@ public function testSendChatMessageWithMoreThan2000CharsThrowsLogicException() { $transport = new DiscordTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class)); - $this->expectException(LogicException::class); + $this->expectException(LengthException::class); $this->expectExceptionMessage('The subject length of a Discord message must not exceed 2000 characters.'); $transport->send(new ChatMessage(str_repeat('d', 2001))); diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php index c958962f1b6a4..5a4d4189ca5b1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Notifier\Bridge\Slack\Block; -use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\LengthException; /** * @author Tomas Norkūnas @@ -26,7 +26,7 @@ final class SlackHeaderBlock extends AbstractSlackBlock public function __construct(string $text) { if (\strlen($text) > self::TEXT_LIMIT) { - throw new LogicException(sprintf('Maximum length for the text is %d characters.', self::TEXT_LIMIT)); + throw new LengthException(sprintf('Maximum length for the text is %d characters.', self::TEXT_LIMIT)); } $this->options = [ @@ -41,7 +41,7 @@ public function __construct(string $text) public function id(string $id): self { if (\strlen($id) > self::ID_LIMIT) { - throw new LogicException(sprintf('Maximum length for the block id is %d characters.', self::ID_LIMIT)); + throw new LengthException(sprintf('Maximum length for the block id is %d characters.', self::ID_LIMIT)); } $this->options['block_id'] = $id; diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php index c899b1822c091..1174fe9712496 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/Block/SlackHeaderBlockTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Notifier\Bridge\Slack\Block\SlackHeaderBlock; -use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\LengthException; final class SlackHeaderBlockTest extends TestCase { @@ -34,7 +34,7 @@ public function testCanBeInstantiated(): void public function testThrowsWhenTextExceedsCharacterLimit(): void { - $this->expectException(LogicException::class); + $this->expectException(LengthException::class); $this->expectExceptionMessage('Maximum length for the text is 150 characters.'); new SlackHeaderBlock(str_repeat('h', 151)); @@ -42,7 +42,7 @@ public function testThrowsWhenTextExceedsCharacterLimit(): void public function testThrowsWhenBlockIdExceedsCharacterLimit(): void { - $this->expectException(LogicException::class); + $this->expectException(LengthException::class); $this->expectExceptionMessage('Maximum length for the block id is 255 characters.'); $header = new SlackHeaderBlock('header'); diff --git a/src/Symfony/Component/Notifier/Exception/LengthException.php b/src/Symfony/Component/Notifier/Exception/LengthException.php new file mode 100644 index 0000000000000..fec27f39d8b61 --- /dev/null +++ b/src/Symfony/Component/Notifier/Exception/LengthException.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\Notifier\Exception; + +/** + * @author Oskar Stark + * + * @experimental in 5.3 + */ +class LengthException extends LogicException +{ +} From bf94bcb1f651f25af25a4ed5d64251c8e5a6e2aa Mon Sep 17 00:00:00 2001 From: Oleksandr Barabolia Date: Mon, 16 Nov 2020 18:55:32 +0200 Subject: [PATCH 049/188] [Notifier] add iqsms bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 ++ .../Notifier/Bridge/Iqsms/.gitattributes | 4 + .../Notifier/Bridge/Iqsms/CHANGELOG.md | 7 ++ .../Notifier/Bridge/Iqsms/IqsmsTransport.php | 88 +++++++++++++++++++ .../Bridge/Iqsms/IqsmsTransportFactory.php | 56 ++++++++++++ .../Component/Notifier/Bridge/Iqsms/LICENSE | 19 ++++ .../Component/Notifier/Bridge/Iqsms/README.md | 24 +++++ .../Iqsms/Tests/IqsmsTransportFactoryTest.php | 76 ++++++++++++++++ .../Bridge/Iqsms/Tests/IqsmsTransportTest.php | 51 +++++++++++ .../Notifier/Bridge/Iqsms/composer.json | 34 +++++++ .../Notifier/Bridge/Iqsms/phpunit.xml.dist | 31 +++++++ .../Exception/UnsupportedSchemeException.php | 4 + src/Symfony/Component/Notifier/Transport.php | 2 + 14 files changed, 403 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Iqsms/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 8c91a05462e7b..3afcf17d8cc68 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -107,6 +107,7 @@ use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; 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\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; @@ -2220,6 +2221,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', GoogleChatTransportFactory::class => 'notifier.transport_factory.googlechat', NexmoTransportFactory::class => 'notifier.transport_factory.nexmo', + IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', RocketChatTransportFactory::class => 'notifier.transport_factory.rocketchat', InfobipTransportFactory::class => 'notifier.transport_factory.infobip', TwilioTransportFactory::class => 'notifier.transport_factory.twilio', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index be40329cc9ebc..88952b80aeb99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -17,6 +17,7 @@ use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; 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\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; @@ -111,6 +112,10 @@ ->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.discord', DiscordTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Iqsms/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Iqsms/CHANGELOG.md new file mode 100644 index 0000000000000..ee65aa9b4a466 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.3.0 +----- + + * Added the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php new file mode 100644 index 0000000000000..6e94473fe39eb --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Iqsms; + +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Oleksandr Barabolia + * + * @experimental in 5.3 + */ +final class IqsmsTransport extends AbstractTransport +{ + protected const HOST = 'api.iqsms.ru'; + + private $login; + private $password; + private $from; + + public function __construct(string $login, string $password, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + $this->login = $login; + $this->password = $password; + $this->from = $from; + + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('iqsms://%s?from=%s', $this->getEndpoint(), $this->from); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/messages/v2/send.json', [ + 'json' => [ + 'messages' => [ + [ + 'phone' => $message->getPhone(), + 'text' => $message->getSubject(), + 'sender' => $this->from, + 'clientId' => uniqid(), + ], + ], + 'login' => $this->login, + 'password' => $this->password, + ], + ]); + + $result = $response->toArray(false); + foreach ($result['messages'] as $msg) { + if ('accepted' !== $msg['status']) { + throw new TransportException(sprintf('Unable to send the SMS: "%s".', $msg['status']), $response); + } + } + + $message = new SentMessage($message, (string) $this); + $message->setMessageId($result['messages'][0]['smscId']); + + return $message; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php new file mode 100644 index 0000000000000..8faafc8185228 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Iqsms; + +use Symfony\Component\Notifier\Exception\IncompleteDsnException; +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; +use Symfony\Component\Notifier\Transport\TransportInterface; + +/** + * @author Oleksandr Barabolia + * + * @experimental in 5.3 + */ +final class IqsmsTransportFactory extends AbstractTransportFactory +{ + /** + * @return IqsmsTransport + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + + if ('iqsms' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'iqsms', $this->getSupportedSchemes()); + } + + $login = $this->getUser($dsn); + $password = $this->getPassword($dsn); + $from = $dsn->getOption('from'); + + if (!$from) { + throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn()); + } + + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new IqsmsTransport($login, $password, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return ['iqsms']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE b/src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE new file mode 100644 index 0000000000000..5593b1d84f74a --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md b/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md new file mode 100644 index 0000000000000..ac137c462840c --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md @@ -0,0 +1,24 @@ +Iqsms Notifier +============== + +Provides [Iqsms[(https://iqsms.ru) integration for Symfony Notifier. + +DSN example +----------- + +``` +IQSMS_DSN=iqsms://LOGIN:PASSWORD@default?from=FROM +``` + +where: + - `LOGIN` is your IQSMS login + - `PASSWORD` is your IQSMS password + - `FROM` is the sender + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [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) diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportFactoryTest.php new file mode 100644 index 0000000000000..cb4c079fd2c3e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportFactoryTest.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\Notifier\Bridge\Iqsms\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Exception\IncompleteDsnException; +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\Dsn; + +final class IqsmsTransportFactoryTest extends TestCase +{ + public function testCreateWithDsn() + { + $factory = $this->createFactory(); + + $transport = $factory->create(Dsn::fromString('iqsms://login:password@host.test?from=some')); + $this->assertSame('iqsms://host.test?from=some', (string) $transport); + } + + public function testCreateWithMissingOptionFromThrowsIncompleteDsnException() + { + $factory = $this->createFactory(); + + $this->expectException(IncompleteDsnException::class); + + $factory->create(Dsn::fromString('iqsms://login:password@default')); + } + + public function testSupportsReturnsTrueWithSupportedScheme() + { + $factory = $this->createFactory(); + + $this->assertTrue($factory->supports(Dsn::fromString('iqsms://login:password@default?from=some'))); + } + + public function testSupportsReturnsFalseWithUnsupportedScheme() + { + $factory = $this->createFactory(); + + $this->assertFalse($factory->supports(Dsn::fromString('somethingElse://login:password@default?from=some'))); + } + + public function testUnsupportedSchemeThrowsUnsupportedSchemeException() + { + $factory = $this->createFactory(); + + $this->expectException(UnsupportedSchemeException::class); + + $factory->create(Dsn::fromString('somethingElse://login:password@default?from=some')); + } + + public function testUnsupportedSchemeThrowsUnsupportedSchemeExceptionEvenIfRequiredOptionIsMissing() + { + $factory = $this->createFactory(); + + $this->expectException(UnsupportedSchemeException::class); + + // unsupported scheme and missing "from" option + $factory->create(Dsn::fromString('somethingElse://login:password@default')); + } + + private function createFactory(): IqsmsTransportFactory + { + return new IqsmsTransportFactory(); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php new file mode 100644 index 0000000000000..1266d16a84e77 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Iqsms\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransport; +use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class IqsmsTransportTest extends TestCase +{ + public function testToStringContainsProperties() + { + $transport = $this->createTransport(); + + $this->assertSame('iqsms://host.test?from=sender', (string) $transport); + } + + public function testSupportsMessageInterface() + { + $transport = $this->createTransport(); + + $this->assertTrue($transport->supports(new SmsMessage('9031223344', 'Hello!'))); + $this->assertFalse($transport->supports($this->createMock(MessageInterface::class))); + } + + public function testSendNonSmsMessageThrowsException() + { + $transport = $this->createTransport(); + + $this->expectException(LogicException::class); + + $transport->send($this->createMock(MessageInterface::class)); + } + + private function createTransport(): IqsmsTransport + { + return (new IqsmsTransport('login', 'password', 'sender', $this->createMock(HttpClientInterface::class)))->setHost('host.test'); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json b/src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json new file mode 100644 index 0000000000000..bcc6f74e43153 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/iqsms-notifier", + "type": "symfony-bridge", + "description": "Symfony Iqsms Notifier Bridge", + "keywords": ["sms", "iqsms", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Oleksandr Barabolia", + "email": "alexandrbarabolya@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/http-client": "^4.3|^5.0", + "symfony/notifier": "^5.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Iqsms\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Iqsms/phpunit.xml.dist new file mode 100644 index 0000000000000..d73fef8d446f6 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 206711f0333c0..2ac36c47d19e9 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -42,6 +42,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Nexmo\NexmoTransportFactory::class, 'package' => 'symfony/nexmo-notifier', ], + 'iqsms' => [ + 'class' => Bridge\Iqsms\IqsmsTransportFactory::class, + 'package' => 'symfony/iqsms-notifier', + ], 'rocketchat' => [ 'class' => Bridge\RocketChat\RocketChatTransportFactory::class, 'package' => 'symfony/rocket-chat-notifier', diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index a5d419367eb64..f370af913bd94 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -16,6 +16,7 @@ use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; +use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory; @@ -51,6 +52,7 @@ class Transport TelegramTransportFactory::class, MattermostTransportFactory::class, NexmoTransportFactory::class, + IqsmsTransportFactory::class, RocketChatTransportFactory::class, TwilioTransportFactory::class, InfobipTransportFactory::class, From 9513ea8caa09bb5a63eb6108f9f5ce3e82847dd9 Mon Sep 17 00:00:00 2001 From: Oleksandr Barabolia Date: Thu, 17 Dec 2020 20:28:25 +0200 Subject: [PATCH 050/188] fix README.md --- src/Symfony/Component/Notifier/Bridge/Iqsms/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md b/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md index ac137c462840c..1cd32c9e2947a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md @@ -1,7 +1,7 @@ Iqsms Notifier ============== -Provides [Iqsms[(https://iqsms.ru) integration for Symfony Notifier. +Provides [Iqsms](https://iqsms.ru) integration for Symfony Notifier. DSN example ----------- From c33abafc80d9e9d9bcab45954ad9ed692cc29d90 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 18 Dec 2020 12:11:21 +0100 Subject: [PATCH 051/188] [Notifier] Fix test --- .../Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php index 78b2f6dd3c2eb..96f2031becf3e 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php @@ -34,7 +34,7 @@ public function testSupportsChatMessage() public function testSendNonChatMessageThrows() { - $transport = $this->getTransport(); + $transport = $this->createTransport(); $this->expectException(UnsupportedMessageTypeException::class); From fed253bf08adbb614639a4ab2087327cc5a052f1 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 18 Dec 2020 13:48:29 +0100 Subject: [PATCH 052/188] [Notifier] Bump conflict rules for all notifier bridges. --- src/Symfony/Component/Notifier/composer.json | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Notifier/composer.json b/src/Symfony/Component/Notifier/composer.json index b6e6150d06a24..cdf890dd09a12 100644 --- a/src/Symfony/Component/Notifier/composer.json +++ b/src/Symfony/Component/Notifier/composer.json @@ -26,16 +26,25 @@ }, "conflict": { "symfony/http-kernel": "<4.4", - "symfony/firebase-notifier": "<5.2", - "symfony/free-mobile-notifier": "<5.2", - "symfony/mattermost-notifier": "<5.2", - "symfony/nexmo-notifier": "<5.2", - "symfony/ovh-cloud-notifier": "<5.2", - "symfony/rocket-chat-notifier": "<5.2", - "symfony/sinch-notifier": "<5.2", - "symfony/slack-notifier": "<5.2", - "symfony/telegram-notifier": "<5.2", - "symfony/twilio-notifier": "<5.2" + "symfony/discord-notifier": "<5.3", + "symfony/esendex-notifier": "<5.3", + "symfony/firebase-notifier": "<5.3", + "symfony/free-mobile-notifier": "<5.3", + "symfony/google-chat-notifier": "<5.3", + "symfony/infobip-notifier": "<5.3", + "symfony/linked-in-notifier": "<5.3", + "symfony/mattermost-notifier": "<5.3", + "symfony/mobyt-notifier": "<5.3", + "symfony/nexmo-notifier": "<5.3", + "symfony/ovh-cloud-notifier": "<5.3", + "symfony/rocket-chat-notifier": "<5.3", + "symfony/sendinblue-notifier": "<5.3", + "symfony/sinch-notifier": "<5.3", + "symfony/slack-notifier": "<5.3", + "symfony/smsapi-notifier": "<5.3", + "symfony/telegram-notifier": "<5.3", + "symfony/twilio-notifier": "<5.3", + "symfony/zulip-notifier": "<5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\": "" }, From b8f742d68b19c87ca6dd3595f78b2fc51b9da340 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 22 Dec 2020 09:33:37 +0100 Subject: [PATCH 053/188] mark some methods as internal --- src/Symfony/Component/DomCrawler/CHANGELOG.md | 6 ++++++ src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/Symfony/Component/DomCrawler/CHANGELOG.md b/src/Symfony/Component/DomCrawler/CHANGELOG.md index 1d16a305a5d6b..8a5b0e2341ea6 100644 --- a/src/Symfony/Component/DomCrawler/CHANGELOG.md +++ b/src/Symfony/Component/DomCrawler/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +5.3.0 +----- + + * Marked the `containsOption()`, `availableOptionValues()`, and `disableValidation()` methods of the + `ChoiceFormField` class as internal + 5.1.0 ----- diff --git a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php index 9b763ab20a28b..c5d44f2deb0d7 100644 --- a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php +++ b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php @@ -268,6 +268,8 @@ private function buildOptionValue(\DOMElement $node): array /** * Checks whether given value is in the existing options. * + * @internal since Symfony 5.3 + * * @return bool */ public function containsOption(string $optionValue, array $options) @@ -288,6 +290,8 @@ public function containsOption(string $optionValue, array $options) /** * Returns list of available field options. * + * @internal since Symfony 5.3 + * * @return array */ public function availableOptionValues() @@ -304,6 +308,8 @@ public function availableOptionValues() /** * Disables the internal validation of the field. * + * @internal since Symfony 5.3 + * * @return self */ public function disableValidation() From bcdd5da849dccc30dd552fd1019bec103772742e Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Fri, 18 Dec 2020 23:05:12 +0100 Subject: [PATCH 054/188] [Notifier] Fix component version constraint in bridges --- src/Symfony/Component/Notifier/Bridge/Discord/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Esendex/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Firebase/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Infobip/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Sinch/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Slack/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Telegram/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Twilio/composer.json | 2 +- src/Symfony/Component/Notifier/Bridge/Zulip/composer.json | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json index daaa43ce484d6..1e4d1254a8560 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0", + "symfony/notifier": "^5.3", "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json b/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json index 57b6ffa6ac7fb..6358037b60394 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.4|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Esendex\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json b/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json index dd40cadfc6fb2..782c5239be47b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Firebase\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json b/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json index 28c7c59e700b1..36d7277c44f80 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/composer.json @@ -19,7 +19,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.1", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\FreeMobile\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json b/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json index 52f0e9f2846e7..cb24cfc52fd6a 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\GoogleChat\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json b/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json index 3aa0c9dae1b91..00d7e4386b7ed 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/composer.json @@ -22,7 +22,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Infobip\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json b/src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json index 8805380f86f1a..bcc6f74e43153 100644 --- a/src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/composer.json @@ -22,7 +22,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Iqsms\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json b/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json index 52f950a3990b6..36e10ccf5699b 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\LinkedIn\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json b/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json index a4d3372673f43..9f94864b3bc12 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mattermost\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json b/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json index 6016efd8da182..93217a5b9828f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-json": "*", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mobyt\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json b/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json index c985726953a9d..74cf98d8cc1fb 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Nexmo\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json b/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json index 7ac94b4b1f16f..7030cb30143d0 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\OvhCloud\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json b/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json index be41b3ea1a80a..823f51d02cef6 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\RocketChat\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json b/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json index c430710b45027..68e44aa84be8f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-json": "*", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Sendinblue\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json b/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json index a27b754eb390c..6ff9d8e282ea0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-json": "*", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Sinch\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json index 2a6ff04f45b08..ae69796774d9b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "require-dev": { "symfony/event-dispatcher": "^4.3|^5.0" diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json b/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json index 32a2afdd07b53..28564eaa91c96 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Smsapi\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json index 2787ee183218e..ffca90186448a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "require-dev": { "symfony/event-dispatcher": "^4.3|^5.0" diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json b/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json index 77b562f3c8c6a..04bc30f7306ca 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Twilio\\": "" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json b/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json index d579ae7e77bb3..b96a349288591 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/http-client": "^4.3|^5.0", - "symfony/notifier": "~5.3.0" + "symfony/notifier": "^5.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Zulip\\": "" }, From b66368a9e23ca92c5bdd50beddc940dd05f2c981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 23 Dec 2020 09:55:12 +0100 Subject: [PATCH 055/188] Remove @experimental annotations --- src/Symfony/Component/Notifier/Bridge/Discord/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/Discord/DiscordOptions.php | 2 -- .../Component/Notifier/Bridge/Discord/DiscordTransport.php | 2 -- .../Notifier/Bridge/Discord/DiscordTransportFactory.php | 2 -- .../Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php | 2 -- .../Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php | 2 -- .../Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php | 2 -- .../Notifier/Bridge/Discord/Embeds/DiscordEmbed.php | 2 -- .../Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php | 2 -- .../Bridge/Discord/Embeds/DiscordFieldEmbedObject.php | 2 -- .../Bridge/Discord/Embeds/DiscordFooterEmbedObject.php | 2 -- .../Bridge/Discord/Embeds/DiscordMediaEmbedObject.php | 2 -- src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/Esendex/EsendexTransport.php | 3 --- .../Notifier/Bridge/Esendex/EsendexTransportFactory.php | 3 --- src/Symfony/Component/Notifier/Bridge/Firebase/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/Firebase/FirebaseOptions.php | 2 -- .../Component/Notifier/Bridge/Firebase/FirebaseTransport.php | 2 -- .../Notifier/Bridge/Firebase/FirebaseTransportFactory.php | 2 -- .../Bridge/Firebase/Notification/AndroidNotification.php | 3 --- .../Bridge/Firebase/Notification/IOSNotification.php | 3 --- .../Bridge/Firebase/Notification/WebNotification.php | 3 --- .../Component/Notifier/Bridge/FreeMobile/CHANGELOG.md | 5 +++++ .../Notifier/Bridge/FreeMobile/FreeMobileTransport.php | 2 -- .../Bridge/FreeMobile/FreeMobileTransportFactory.php | 2 -- .../Component/Notifier/Bridge/GoogleChat/CHANGELOG.md | 5 +++++ .../Notifier/Bridge/GoogleChat/GoogleChatOptions.php | 2 -- .../Notifier/Bridge/GoogleChat/GoogleChatTransport.php | 2 -- .../Bridge/GoogleChat/GoogleChatTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/Infobip/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/Infobip/InfobipTransport.php | 2 -- .../Notifier/Bridge/Infobip/InfobipTransportFactory.php | 2 -- .../Component/Notifier/Bridge/Iqsms/IqsmsTransport.php | 2 -- .../Notifier/Bridge/Iqsms/IqsmsTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php | 2 -- .../Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php | 2 -- .../Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php | 2 -- .../Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php | 2 -- .../Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php | 2 -- .../Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php | 2 -- .../Notifier/Bridge/LinkedIn/Share/ShareContentShare.php | 2 -- .../Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php | 2 -- .../Notifier/Bridge/LinkedIn/Share/VisibilityShare.php | 2 -- .../Component/Notifier/Bridge/Mattermost/CHANGELOG.md | 5 +++++ .../Notifier/Bridge/Mattermost/MattermostTransport.php | 2 -- .../Bridge/Mattermost/MattermostTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/Mobyt/CHANGELOG.md | 5 +++++ src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php | 2 -- .../Component/Notifier/Bridge/Mobyt/MobytTransport.php | 2 -- .../Notifier/Bridge/Mobyt/MobytTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/Nexmo/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/Nexmo/NexmoTransport.php | 2 -- .../Notifier/Bridge/Nexmo/NexmoTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/OvhCloud/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php | 2 -- .../Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php | 2 -- .../Component/Notifier/Bridge/RocketChat/CHANGELOG.md | 5 +++++ .../Notifier/Bridge/RocketChat/RocketChatOptions.php | 2 -- .../Notifier/Bridge/RocketChat/RocketChatTransport.php | 2 -- .../Bridge/RocketChat/RocketChatTransportFactory.php | 2 -- .../Component/Notifier/Bridge/Sendinblue/CHANGELOG.md | 5 +++++ .../Notifier/Bridge/Sendinblue/SendinblueTransport.php | 2 -- .../Bridge/Sendinblue/SendinblueTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/Sinch/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/Sinch/SinchTransport.php | 2 -- .../Notifier/Bridge/Sinch/SinchTransportFactory.php | 2 -- .../Notifier/Bridge/Slack/Block/SlackHeaderBlock.php | 2 -- src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md | 1 + src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php | 2 -- .../Component/Notifier/Bridge/Slack/SlackTransport.php | 2 -- .../Notifier/Bridge/Slack/SlackTransportFactory.php | 2 -- .../Component/Notifier/Bridge/Smsapi/SmsapiTransport.php | 1 - .../Notifier/Bridge/Smsapi/SmsapiTransportFactory.php | 1 - src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md | 5 +++++ .../Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php | 2 -- .../Telegram/Reply/Markup/Button/AbstractKeyboardButton.php | 2 -- .../Telegram/Reply/Markup/Button/InlineKeyboardButton.php | 2 -- .../Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php | 2 -- .../Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php | 2 -- .../Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php | 2 -- .../Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php | 2 -- .../Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php | 2 -- .../Component/Notifier/Bridge/Telegram/TelegramOptions.php | 2 -- .../Component/Notifier/Bridge/Telegram/TelegramTransport.php | 2 -- .../Notifier/Bridge/Telegram/TelegramTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/Twilio/CHANGELOG.md | 5 +++++ .../Component/Notifier/Bridge/Twilio/TwilioTransport.php | 2 -- .../Notifier/Bridge/Twilio/TwilioTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md | 5 +++++ src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php | 2 -- .../Component/Notifier/Bridge/Zulip/ZulipTransport.php | 2 -- .../Notifier/Bridge/Zulip/ZulipTransportFactory.php | 2 -- src/Symfony/Component/Notifier/CHANGELOG.md | 5 +++++ src/Symfony/Component/Notifier/Channel/AbstractChannel.php | 2 -- src/Symfony/Component/Notifier/Channel/BrowserChannel.php | 2 -- src/Symfony/Component/Notifier/Channel/ChannelInterface.php | 2 -- src/Symfony/Component/Notifier/Channel/ChannelPolicy.php | 2 -- .../Component/Notifier/Channel/ChannelPolicyInterface.php | 2 -- src/Symfony/Component/Notifier/Channel/ChatChannel.php | 2 -- src/Symfony/Component/Notifier/Channel/EmailChannel.php | 2 -- src/Symfony/Component/Notifier/Channel/SmsChannel.php | 2 -- src/Symfony/Component/Notifier/Chatter.php | 2 -- src/Symfony/Component/Notifier/ChatterInterface.php | 2 -- .../Notifier/DataCollector/NotificationDataCollector.php | 2 -- src/Symfony/Component/Notifier/Event/MessageEvent.php | 2 -- src/Symfony/Component/Notifier/Event/NotificationEvents.php | 2 -- .../Notifier/EventListener/NotificationLoggerListener.php | 2 -- .../EventListener/SendFailedMessageToNotifierListener.php | 2 -- .../Component/Notifier/Exception/ExceptionInterface.php | 2 -- .../Component/Notifier/Exception/IncompleteDsnException.php | 2 -- .../Notifier/Exception/InvalidArgumentException.php | 2 -- src/Symfony/Component/Notifier/Exception/LengthException.php | 2 -- src/Symfony/Component/Notifier/Exception/LogicException.php | 2 -- .../Component/Notifier/Exception/RuntimeException.php | 2 -- .../Component/Notifier/Exception/TransportException.php | 2 -- .../Notifier/Exception/TransportExceptionInterface.php | 2 -- .../Notifier/Exception/UnsupportedMessageTypeException.php | 2 -- .../Notifier/Exception/UnsupportedSchemeException.php | 2 -- src/Symfony/Component/Notifier/Message/ChatMessage.php | 2 -- src/Symfony/Component/Notifier/Message/EmailMessage.php | 2 -- src/Symfony/Component/Notifier/Message/MessageInterface.php | 2 -- .../Component/Notifier/Message/MessageOptionsInterface.php | 2 -- src/Symfony/Component/Notifier/Message/NullMessage.php | 2 -- src/Symfony/Component/Notifier/Message/SentMessage.php | 2 -- src/Symfony/Component/Notifier/Message/SmsMessage.php | 2 -- src/Symfony/Component/Notifier/Messenger/MessageHandler.php | 2 -- .../Notifier/Notification/ChatNotificationInterface.php | 2 -- .../Notifier/Notification/EmailNotificationInterface.php | 2 -- src/Symfony/Component/Notifier/Notification/Notification.php | 2 -- .../Notifier/Notification/SmsNotificationInterface.php | 2 -- src/Symfony/Component/Notifier/Notifier.php | 2 -- src/Symfony/Component/Notifier/NotifierInterface.php | 2 -- src/Symfony/Component/Notifier/README.md | 5 ----- .../Component/Notifier/Recipient/EmailRecipientInterface.php | 2 -- .../Component/Notifier/Recipient/EmailRecipientTrait.php | 2 -- src/Symfony/Component/Notifier/Recipient/NoRecipient.php | 2 -- src/Symfony/Component/Notifier/Recipient/Recipient.php | 2 -- .../Component/Notifier/Recipient/RecipientInterface.php | 2 -- .../Component/Notifier/Recipient/SmsRecipientInterface.php | 2 -- .../Component/Notifier/Recipient/SmsRecipientTrait.php | 2 -- src/Symfony/Component/Notifier/Texter.php | 2 -- src/Symfony/Component/Notifier/TexterInterface.php | 2 -- src/Symfony/Component/Notifier/Transport.php | 2 -- .../Component/Notifier/Transport/AbstractTransport.php | 2 -- .../Notifier/Transport/AbstractTransportFactory.php | 2 -- src/Symfony/Component/Notifier/Transport/Dsn.php | 2 -- .../Component/Notifier/Transport/FailoverTransport.php | 2 -- src/Symfony/Component/Notifier/Transport/NullTransport.php | 2 -- .../Component/Notifier/Transport/NullTransportFactory.php | 2 -- .../Component/Notifier/Transport/RoundRobinTransport.php | 2 -- .../Notifier/Transport/TransportFactoryInterface.php | 2 -- .../Component/Notifier/Transport/TransportInterface.php | 2 -- src/Symfony/Component/Notifier/Transport/Transports.php | 2 -- 154 files changed, 91 insertions(+), 276 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Discord/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Discord/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php index 1fda563ec9619..8cc74e3b50b41 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordOptions.php @@ -17,8 +17,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ final class DiscordOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php index a85d29b274624..f4c223e1eb1b9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php @@ -23,8 +23,6 @@ /** * @author Mathieu Piot - * - * @experimental in 5.3 */ final class DiscordTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php index e068340c1075a..776e1d7bc7db7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Mathieu Piot - * - * @experimental in 5.3 */ final class DiscordTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php index bf369caf075e2..57ef5a9c9ade0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbed.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ abstract class AbstractDiscordEmbed implements DiscordEmbedInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php index 09f874eaaf521..6fa50370380e7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/AbstractDiscordEmbedObject.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ abstract class AbstractDiscordEmbedObject implements DiscordEmbedObjectInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php index c0fe90189a67a..04987ef9f75c8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordAuthorEmbedObject.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ final class DiscordAuthorEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php index ca2adb7635022..71c2e783a4148 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbed.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ final class DiscordEmbed extends AbstractDiscordEmbed { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php index bc1f1398cc300..3baac40bc71cc 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordEmbedObjectInterface.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ interface DiscordEmbedObjectInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php index 20eb38031f96b..01761080ba52d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFieldEmbedObject.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ final class DiscordFieldEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php index 516f56c863acf..42320e35169c9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordFooterEmbedObject.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ final class DiscordFooterEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php index a53bb42947b52..fdddf21ed2388 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Embeds/DiscordMediaEmbedObject.php @@ -13,8 +13,6 @@ /** * @author Karoly Gossler - * - * @experimental in 5.3 */ class DiscordMediaEmbedObject extends AbstractDiscordEmbedObject { diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php index 75a43a96bb31f..0cdfc933a7302 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php @@ -22,9 +22,6 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; -/** - * @experimental in 5.3 - */ final class EsendexTransport extends AbstractTransport { protected const HOST = 'api.esendex.com'; diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php index 0ab3e493eb5ac..cde7d600279d8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php @@ -17,9 +17,6 @@ use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Transport\TransportInterface; -/** - * @experimental in 5.3 - */ final class EsendexTransportFactory extends AbstractTransportFactory { /** diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Firebase/CHANGELOG.md index 7bd5e9a57fd19..b1c417c0b309b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.1.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php index f212e26f8c708..d632f7d3413e4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseOptions.php @@ -17,8 +17,6 @@ * @author Jeroen Spee * * @see https://firebase.google.com/docs/cloud-messaging/xmpp-server-ref.html - * - * @experimental in 5.3 */ abstract class FirebaseOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php index e53beb6d99173..6e6cefbc1630a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php @@ -23,8 +23,6 @@ /** * @author Jeroen Spee - * - * @experimental in 5.3 */ final class FirebaseTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php index dabe532322ec8..962978b1d24e7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransportFactory.php @@ -18,8 +18,6 @@ /** * @author Jeroen Spee - * - * @experimental in 5.3 */ final class FirebaseTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php index 472bd69631649..add6a8e4b4125 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/AndroidNotification.php @@ -13,9 +13,6 @@ use Symfony\Component\Notifier\Bridge\Firebase\FirebaseOptions; -/** - * @experimental in 5.3 - */ final class AndroidNotification extends FirebaseOptions { public function channelId(string $channelId): self diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php index e8cc16cebc9ed..23f44f9182153 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/IOSNotification.php @@ -13,9 +13,6 @@ use Symfony\Component\Notifier\Bridge\Firebase\FirebaseOptions; -/** - * @experimental in 5.3 - */ final class IOSNotification extends FirebaseOptions { public function sound(string $sound): self diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php index e8c7b45956342..89d0e742b7e49 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/Notification/WebNotification.php @@ -13,9 +13,6 @@ use Symfony\Component\Notifier\Bridge\Firebase\FirebaseOptions; -/** - * @experimental in 5.3 - */ final class WebNotification extends FirebaseOptions { public function icon(string $icon): self diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/FreeMobile/CHANGELOG.md index 7bd5e9a57fd19..b1c417c0b309b 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.1.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php index 6943029ad8a3c..c63abcecaf532 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php @@ -22,8 +22,6 @@ /** * @author Antoine Makdessi - * - * @experimental in 5.3 */ final class FreeMobileTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php index 5556c2fb3418d..94a53235466da 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Antoine Makdessi - * - * @experimental in 5.3 */ final class FreeMobileTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php index 3e6195bac7c57..77372cd5102c6 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php @@ -18,8 +18,6 @@ /** * @author Jérôme Tamarelle - * - * @experimental in 5.3 */ final class GoogleChatOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php index 0323a705a4191..72acd33594a23 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php @@ -24,8 +24,6 @@ /** * @author Jérôme Tamarelle - * - * @experimental in 5.3 */ final class GoogleChatTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php index ad83a57929de1..359c5bbccbb8c 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransportFactory.php @@ -18,8 +18,6 @@ /** * @author Jérôme Tamarelle - * - * @experimental in 5.3 */ final class GoogleChatTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Infobip/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php index e4bf4a9e7a2b8..df76522386405 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php @@ -23,8 +23,6 @@ /** * @author Fabien Potencier * @author Jérémy Romey - * - * @experimental in 5.3 */ final class InfobipTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php index 4fb3e3692fa45..29755598a3b12 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransportFactory.php @@ -20,8 +20,6 @@ /** * @author Fabien Potencier * @author Jérémy Romey - * - * @experimental in 5.3 */ final class InfobipTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php index 6e94473fe39eb..a83aee83ebb50 100644 --- a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php @@ -22,8 +22,6 @@ /** * @author Oleksandr Barabolia - * - * @experimental in 5.3 */ final class IqsmsTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php index 8faafc8185228..6c607a6a73b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Oleksandr Barabolia - * - * @experimental in 5.3 */ final class IqsmsTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php index 611a924c674a2..630265645334a 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInOptions.php @@ -20,8 +20,6 @@ /** * @author Smaïne Milianni - * - * @experimental in 5.3 */ final class LinkedInOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php index 9ff68b66bfe5f..bb281df6f7f69 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php @@ -25,8 +25,6 @@ /** * @author Smaïne Milianni * - * @experimental in 5.3 - * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#sharecontent */ final class LinkedInTransport extends AbstractTransport diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php index 78d1946230af0..c3dfc7357e113 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php @@ -18,8 +18,6 @@ /** * @author Smaïne Milianni - * - * @experimental in 5.3 */ class LinkedInTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php index ccae190e53e71..8a02cc734df17 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AbstractLinkedInShare.php @@ -13,8 +13,6 @@ /** * @author Smaïne Milianni - * - * @experimental in 5.3 */ abstract class AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php index 4b801b8485dea..9558031a71409 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/AuthorShare.php @@ -13,8 +13,6 @@ /** * @author Smaïne Milianni - * - * @experimental in 5.3 */ final class AuthorShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php index 6a340bbf3ac1d..cf51d1835d88b 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/LifecycleStateShare.php @@ -17,8 +17,6 @@ * @author Smaïne Milianni * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#schema lifecycleState section - * - * @experimental in 5.3 */ final class LifecycleStateShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php index 6395a88066685..1c70a6cde9273 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php @@ -17,8 +17,6 @@ * @author Smaïne Milianni * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#sharecontent - * - * @experimental in 5.3 */ final class ShareContentShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php index 0983652e69f44..f41fb85d45e3c 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php @@ -17,8 +17,6 @@ * @author Smaïne Milianni * * @see https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#sharemedia - * - * @experimental in 5.3 */ class ShareMediaShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php index 633ae1fe82c24..03ca05bde25ba 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/VisibilityShare.php @@ -15,8 +15,6 @@ /** * @author Smaïne Milianni - * - * @experimental in 5.3 */ final class VisibilityShare extends AbstractLinkedInShare { diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md index 7bd5e9a57fd19..b1c417c0b309b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.1.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php index f1916342e7110..59730b97f78b0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php @@ -22,8 +22,6 @@ /** * @author Emanuele Panzeri - * - * @experimental in 5.3 */ final class MattermostTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php index b75e46e8a87dc..002c760cb7f32 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Emanuele Panzeri - * - * @experimental in 5.3 */ final class MattermostTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Mobyt/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php index c0d760ac0dffd..695e50fe871fe 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytOptions.php @@ -16,8 +16,6 @@ /** * @author Bastien Durand - * - * @experimental in 5.3 */ final class MobytOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php index 98eff8730dab6..4e2ec1d94390d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php @@ -23,8 +23,6 @@ /** * @author Basien Durand - * - * @experimental in 5.3 */ final class MobytTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php index c7c3a9fae0400..955fefd63d64e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Bastien Durand - * - * @experimental in 5.3 */ final class MobytTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Nexmo/CHANGELOG.md index 10f7e1ea8506e..a807785c30ac3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.0.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php index e2fafe17bc597..25dcb991d9f74 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php @@ -22,8 +22,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class NexmoTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php index 558f3523772e0..3095550d392f0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class NexmoTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/OvhCloud/CHANGELOG.md index 7bd5e9a57fd19..b1c417c0b309b 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.1.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php index 6698b28cd9b18..614b3eef7c0ba 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php @@ -22,8 +22,6 @@ /** * @author Thomas Ferney - * - * @experimental in 5.3 */ final class OvhCloudTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php index 66f00a9979609..6fa9e1626e336 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Thomas Ferney - * - * @experimental in 5.3 */ final class OvhCloudTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/RocketChat/CHANGELOG.md index 7bd5e9a57fd19..b1c417c0b309b 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.1.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php index e70f74ca8c321..d7f5d63bfb979 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatOptions.php @@ -16,8 +16,6 @@ /** * @author Jeroen Spee * - * @experimental in 5.3 - * * @see https://rocket.chat/docs/administrator-guides/integrations/ */ final class RocketChatOptions implements MessageOptionsInterface diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php index 2f6302a049869..abd8ce6b86a91 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php @@ -23,8 +23,6 @@ /** * @author Jeroen Spee - * - * @experimental in 5.3 */ final class RocketChatTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php index 1600819777a7a..abba7062baa06 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransportFactory.php @@ -18,8 +18,6 @@ /** * @author Jeroen Spee - * - * @experimental in 5.3 */ final class RocketChatTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Sendinblue/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php index 429fda4c7986f..0e5994ae31a37 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransport.php @@ -22,8 +22,6 @@ /** * @author Pierre Tondereau - * - * @experimental in 5.3 */ final class SendinblueTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php index a5e6c963b0c24..e36b3f2e3e9cd 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/SendinblueTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Pierre Tondereau - * - * @experimental in 5.3 */ final class SendinblueTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Sinch/CHANGELOG.md index abf66cd8cac35..a2c4e84b43346 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.1 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php index 7a6aace6478ec..55ff922db4b43 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php @@ -22,8 +22,6 @@ /** * @author Iliya Miroslavov Iliev - * - * @experimental in 5.3 */ final class SinchTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php index 795b00a48d423..1ef55d6ec9dd0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Iliya Miroslavov Iliev - * - * @experimental in 5.3 */ final class SinchTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php index 5a4d4189ca5b1..bbcb19e7ed2f8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackHeaderBlock.php @@ -15,8 +15,6 @@ /** * @author Tomas Norkūnas - * - * @experimental in 5.3 */ final class SlackHeaderBlock extends AbstractSlackBlock { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md index 650a09bc1eec8..c6384e85cf3de 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3.0 ----- + * The bridge is not marked as `@experimental` anymore * Check for maximum number of buttons in Slack action block * Add HeaderBlock diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php index 6b697aede59fc..eb75fea5cb35c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php @@ -19,8 +19,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class SlackOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php index dba82100503bb..fedc1b648a926 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php @@ -23,8 +23,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class SlackTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php index 7750d2f628113..ea724d0003019 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class SlackTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php index 0451fe768e981..79df322bf1ae9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php @@ -22,7 +22,6 @@ /** * @author Marcin Szepczynski - * @experimental in 5.3 */ final class SmsapiTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php index 69d67af4d6a66..5ce25623202ac 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php @@ -19,7 +19,6 @@ /** * @author Marcin Szepczynski - * @experimental in 5.3 */ class SmsapiTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md index 10f7e1ea8506e..a807785c30ac3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.0.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php index 10a9154001bd9..8bf4bb0cd3db6 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/AbstractTelegramReplyMarkup.php @@ -13,8 +13,6 @@ /** * @author Mihail Krasilnikov - * - * @experimental in 5.3 */ abstract class AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php index b5b87f75ca0ea..03d05ab4f4679 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/AbstractKeyboardButton.php @@ -13,8 +13,6 @@ /** * @author Mihail Krasilnikov - * - * @experimental in 5.3 */ abstract class AbstractKeyboardButton { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php index b5cf6b3fd8405..89e4c4f1ecf3f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/InlineKeyboardButton.php @@ -15,8 +15,6 @@ * @author Mihail Krasilnikov * * @see https://core.telegram.org/bots/api#inlinekeyboardbutton - * - * @experimental in 5.3 */ final class InlineKeyboardButton extends AbstractKeyboardButton { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php index 15359af302ad8..3e8240baa2e7f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/Button/KeyboardButton.php @@ -15,8 +15,6 @@ * @author Mihail Krasilnikov * * @see https://core.telegram.org/bots/api#keyboardbutton - * - * @experimental in 5.3 */ final class KeyboardButton extends AbstractKeyboardButton { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php index b6dc92317fa5e..63779c4165491 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ForceReply.php @@ -15,8 +15,6 @@ * @author Mihail Krasilnikov * * @see https://core.telegram.org/bots/api#forcereply - * - * @experimental in 5.3 */ final class ForceReply extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php index 00a80183bec53..c7cc371ea7c08 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/InlineKeyboardMarkup.php @@ -17,8 +17,6 @@ * @author Mihail Krasilnikov * * @see https://core.telegram.org/bots/api#inlinekeyboardmarkup - * - * @experimental in 5.3 */ final class InlineKeyboardMarkup extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php index 6767707624b00..a5b8ef600d69c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardMarkup.php @@ -17,8 +17,6 @@ * @author Mihail Krasilnikov * * @see https://core.telegram.org/bots/api#replykeyboardmarkup - * - * @experimental in 5.3 */ final class ReplyKeyboardMarkup extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php index b0619f35976b1..b4a2daee10484 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Reply/Markup/ReplyKeyboardRemove.php @@ -15,8 +15,6 @@ * @author Mihail Krasilnikov * * @see https://core.telegram.org/bots/api#replykeyboardremove - * - * @experimental in 5.3 */ final class ReplyKeyboardRemove extends AbstractTelegramReplyMarkup { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php index bb7932c1c9921..9794d8744ee0d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php @@ -16,8 +16,6 @@ /** * @author Mihail Krasilnikov - * - * @experimental in 5.3 */ final class TelegramOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php index 33b182b31b531..a31a394c518b7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php @@ -27,8 +27,6 @@ * command. * * @author Fabien Potencier - * - * @experimental in 5.3 */ final class TelegramTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php index 025a594ff8490..490fe1d26e9b7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class TelegramTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Twilio/CHANGELOG.md index 10f7e1ea8506e..a807785c30ac3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.0.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php index db16fe5e639ed..f7d7f186eb689 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php @@ -22,8 +22,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class TwilioTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php index 659cd6c288bfa..bda4b5c5eea71 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class TwilioTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md index 0d994e934e55a..d8e243dcb9b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The bridge is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php index 4391544dc9198..49059384fc76c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php @@ -15,8 +15,6 @@ /** * @author Mohammad Emran Hasan - * - * @experimental in 5.3 */ final class ZulipOptions implements MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php index b6683179d2e61..32102dc8344b4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php @@ -23,8 +23,6 @@ /** * @author Mohammad Emran Hasan - * - * @experimental in 5.3 */ class ZulipTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php index a4af57ee07aed..9ea1ec66a948c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php @@ -19,8 +19,6 @@ /** * @author Mohammad Emran Hasan - * - * @experimental in 5.3 */ class ZulipTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/CHANGELOG.md b/src/Symfony/Component/Notifier/CHANGELOG.md index adb97747a6c51..528aa24018742 100644 --- a/src/Symfony/Component/Notifier/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * The component is not marked as `@experimental` anymore + 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Channel/AbstractChannel.php b/src/Symfony/Component/Notifier/Channel/AbstractChannel.php index b6c6d0c54ba8f..a83a51da4115c 100644 --- a/src/Symfony/Component/Notifier/Channel/AbstractChannel.php +++ b/src/Symfony/Component/Notifier/Channel/AbstractChannel.php @@ -17,8 +17,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ abstract class AbstractChannel implements ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php index 715c87204432b..0201e0f1382b0 100644 --- a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php +++ b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php @@ -17,8 +17,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class BrowserChannel implements ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChannelInterface.php b/src/Symfony/Component/Notifier/Channel/ChannelInterface.php index 48a0228134fa2..ab3115230d79d 100644 --- a/src/Symfony/Component/Notifier/Channel/ChannelInterface.php +++ b/src/Symfony/Component/Notifier/Channel/ChannelInterface.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php b/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php index 2d0c0eb0afa8f..b333950183213 100644 --- a/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php +++ b/src/Symfony/Component/Notifier/Channel/ChannelPolicy.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class ChannelPolicy implements ChannelPolicyInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php b/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php index 792176853e4c9..df236d229cd44 100644 --- a/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php +++ b/src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface ChannelPolicyInterface { diff --git a/src/Symfony/Component/Notifier/Channel/ChatChannel.php b/src/Symfony/Component/Notifier/Channel/ChatChannel.php index 61c3c91f485a4..ea41c2e4fa443 100644 --- a/src/Symfony/Component/Notifier/Channel/ChatChannel.php +++ b/src/Symfony/Component/Notifier/Channel/ChatChannel.php @@ -18,8 +18,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class ChatChannel extends AbstractChannel { diff --git a/src/Symfony/Component/Notifier/Channel/EmailChannel.php b/src/Symfony/Component/Notifier/Channel/EmailChannel.php index 4f22b1b04d65d..691cdc5de6ee8 100644 --- a/src/Symfony/Component/Notifier/Channel/EmailChannel.php +++ b/src/Symfony/Component/Notifier/Channel/EmailChannel.php @@ -25,8 +25,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class EmailChannel implements ChannelInterface { diff --git a/src/Symfony/Component/Notifier/Channel/SmsChannel.php b/src/Symfony/Component/Notifier/Channel/SmsChannel.php index 0251c95224368..ebed8010d5334 100644 --- a/src/Symfony/Component/Notifier/Channel/SmsChannel.php +++ b/src/Symfony/Component/Notifier/Channel/SmsChannel.php @@ -19,8 +19,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class SmsChannel extends AbstractChannel { diff --git a/src/Symfony/Component/Notifier/Chatter.php b/src/Symfony/Component/Notifier/Chatter.php index efbf9538df9ca..7b3c9d566e4a4 100644 --- a/src/Symfony/Component/Notifier/Chatter.php +++ b/src/Symfony/Component/Notifier/Chatter.php @@ -22,8 +22,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class Chatter implements ChatterInterface { diff --git a/src/Symfony/Component/Notifier/ChatterInterface.php b/src/Symfony/Component/Notifier/ChatterInterface.php index 4519821edcc9d..915190e623aaa 100644 --- a/src/Symfony/Component/Notifier/ChatterInterface.php +++ b/src/Symfony/Component/Notifier/ChatterInterface.php @@ -17,8 +17,6 @@ * Interface for classes able to send chat messages synchronous and/or asynchronous. * * @author Fabien Potencier - * - * @experimental in 5.3 */ interface ChatterInterface extends TransportInterface { diff --git a/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php b/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php index a685e48694eb0..0bd03e919b2ab 100644 --- a/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php +++ b/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php @@ -19,8 +19,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class NotificationDataCollector extends DataCollector { diff --git a/src/Symfony/Component/Notifier/Event/MessageEvent.php b/src/Symfony/Component/Notifier/Event/MessageEvent.php index 2ab2ad8f2463d..2fdba8c69c6f2 100644 --- a/src/Symfony/Component/Notifier/Event/MessageEvent.php +++ b/src/Symfony/Component/Notifier/Event/MessageEvent.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class MessageEvent extends Event { diff --git a/src/Symfony/Component/Notifier/Event/NotificationEvents.php b/src/Symfony/Component/Notifier/Event/NotificationEvents.php index b7f60e224b6cc..19d698b61f3a1 100644 --- a/src/Symfony/Component/Notifier/Event/NotificationEvents.php +++ b/src/Symfony/Component/Notifier/Event/NotificationEvents.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class NotificationEvents { diff --git a/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php b/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php index 2cebd9b8a7e36..29d295529209c 100644 --- a/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php +++ b/src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php @@ -18,8 +18,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class NotificationLoggerListener implements EventSubscriberInterface, ResetInterface { diff --git a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php index ce5a87f35cc06..b6807f1091594 100644 --- a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php +++ b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php @@ -20,8 +20,6 @@ * Sends a rejected message to the notifier. * * @author Fabien Potencier - * - * @experimental in 5.3 */ class SendFailedMessageToNotifierListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php b/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php index bef37cefc10bc..457ed613606db 100644 --- a/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php +++ b/src/Symfony/Component/Notifier/Exception/ExceptionInterface.php @@ -15,8 +15,6 @@ * Exception interface for all exceptions thrown by the component. * * @author Fabien Potencier - * - * @experimental in 5.3 */ interface ExceptionInterface extends \Throwable { diff --git a/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php b/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php index 8452dd6d507df..55fc0438b356e 100644 --- a/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php +++ b/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class IncompleteDsnException extends InvalidArgumentException { diff --git a/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php b/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php index 9f82bee7db800..1130f8bd861f2 100644 --- a/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php +++ b/src/Symfony/Component/Notifier/Exception/InvalidArgumentException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/LengthException.php b/src/Symfony/Component/Notifier/Exception/LengthException.php index fec27f39d8b61..7feed7ba66172 100644 --- a/src/Symfony/Component/Notifier/Exception/LengthException.php +++ b/src/Symfony/Component/Notifier/Exception/LengthException.php @@ -13,8 +13,6 @@ /** * @author Oskar Stark - * - * @experimental in 5.3 */ class LengthException extends LogicException { diff --git a/src/Symfony/Component/Notifier/Exception/LogicException.php b/src/Symfony/Component/Notifier/Exception/LogicException.php index 0919d32003c10..8a67897df0e04 100644 --- a/src/Symfony/Component/Notifier/Exception/LogicException.php +++ b/src/Symfony/Component/Notifier/Exception/LogicException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class LogicException extends \LogicException implements ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/RuntimeException.php b/src/Symfony/Component/Notifier/Exception/RuntimeException.php index 6b91df7819a41..2e61a3109a675 100644 --- a/src/Symfony/Component/Notifier/Exception/RuntimeException.php +++ b/src/Symfony/Component/Notifier/Exception/RuntimeException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class RuntimeException extends \RuntimeException implements ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/TransportException.php b/src/Symfony/Component/Notifier/Exception/TransportException.php index 24ce418ad9b62..acf6ec7b36d82 100644 --- a/src/Symfony/Component/Notifier/Exception/TransportException.php +++ b/src/Symfony/Component/Notifier/Exception/TransportException.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class TransportException extends RuntimeException implements TransportExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php b/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php index 72023e4a963a3..294f79bd9e19f 100644 --- a/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php +++ b/src/Symfony/Component/Notifier/Exception/TransportExceptionInterface.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface TransportExceptionInterface extends ExceptionInterface { diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.php index d52ce21417c50..59207de540fce 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedMessageTypeException.php @@ -15,8 +15,6 @@ /** * @author Oskar Stark - * - * @experimental in 5.3 */ class UnsupportedMessageTypeException extends LogicException { diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 2ac36c47d19e9..ce78ee37caca2 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -16,8 +16,6 @@ /** * @author Konstantin Myakshin - * - * @experimental in 5.3 */ class UnsupportedSchemeException extends LogicException { diff --git a/src/Symfony/Component/Notifier/Message/ChatMessage.php b/src/Symfony/Component/Notifier/Message/ChatMessage.php index 082adafdad0cc..d7c028a7c90a3 100644 --- a/src/Symfony/Component/Notifier/Message/ChatMessage.php +++ b/src/Symfony/Component/Notifier/Message/ChatMessage.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class ChatMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/EmailMessage.php b/src/Symfony/Component/Notifier/Message/EmailMessage.php index 530f19c582204..a41211e885503 100644 --- a/src/Symfony/Component/Notifier/Message/EmailMessage.php +++ b/src/Symfony/Component/Notifier/Message/EmailMessage.php @@ -22,8 +22,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class EmailMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/MessageInterface.php b/src/Symfony/Component/Notifier/Message/MessageInterface.php index 7fff691ed3dc7..05c4266d93068 100644 --- a/src/Symfony/Component/Notifier/Message/MessageInterface.php +++ b/src/Symfony/Component/Notifier/Message/MessageInterface.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php b/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php index 1c52a4cb9a79e..992a1cec96f10 100644 --- a/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php +++ b/src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface MessageOptionsInterface { diff --git a/src/Symfony/Component/Notifier/Message/NullMessage.php b/src/Symfony/Component/Notifier/Message/NullMessage.php index 8cda1603aa7d0..b6fd6696d1aa8 100644 --- a/src/Symfony/Component/Notifier/Message/NullMessage.php +++ b/src/Symfony/Component/Notifier/Message/NullMessage.php @@ -13,8 +13,6 @@ /** * @author Jan Schädlich - * - * @experimental in 5.3 */ final class NullMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Message/SentMessage.php b/src/Symfony/Component/Notifier/Message/SentMessage.php index 852b70375f34b..91343d7e42dbb 100644 --- a/src/Symfony/Component/Notifier/Message/SentMessage.php +++ b/src/Symfony/Component/Notifier/Message/SentMessage.php @@ -13,8 +13,6 @@ /** * @author Jérémy Romey - * - * @experimental in 5.3 */ final class SentMessage { diff --git a/src/Symfony/Component/Notifier/Message/SmsMessage.php b/src/Symfony/Component/Notifier/Message/SmsMessage.php index 19cf7edd7abb8..f773512a8933b 100644 --- a/src/Symfony/Component/Notifier/Message/SmsMessage.php +++ b/src/Symfony/Component/Notifier/Message/SmsMessage.php @@ -17,8 +17,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class SmsMessage implements MessageInterface { diff --git a/src/Symfony/Component/Notifier/Messenger/MessageHandler.php b/src/Symfony/Component/Notifier/Messenger/MessageHandler.php index dee0d2c06119f..e7c8d12068b2b 100644 --- a/src/Symfony/Component/Notifier/Messenger/MessageHandler.php +++ b/src/Symfony/Component/Notifier/Messenger/MessageHandler.php @@ -17,8 +17,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class MessageHandler { diff --git a/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php index 4d3a48ff4c549..7b42fac5fe8c1 100644 --- a/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface ChatNotificationInterface { diff --git a/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php index 817d9a3835ceb..55660427ab01e 100644 --- a/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface EmailNotificationInterface { diff --git a/src/Symfony/Component/Notifier/Notification/Notification.php b/src/Symfony/Component/Notifier/Notification/Notification.php index fcbaa3c72ddab..f03bf42a757d5 100644 --- a/src/Symfony/Component/Notifier/Notification/Notification.php +++ b/src/Symfony/Component/Notifier/Notification/Notification.php @@ -17,8 +17,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class Notification { diff --git a/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php index 13744cbc41578..02db67ac2a6f3 100644 --- a/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface SmsNotificationInterface { diff --git a/src/Symfony/Component/Notifier/Notifier.php b/src/Symfony/Component/Notifier/Notifier.php index bbcfcd5c0d1cb..426829a74c1c0 100644 --- a/src/Symfony/Component/Notifier/Notifier.php +++ b/src/Symfony/Component/Notifier/Notifier.php @@ -22,8 +22,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class Notifier implements NotifierInterface { diff --git a/src/Symfony/Component/Notifier/NotifierInterface.php b/src/Symfony/Component/Notifier/NotifierInterface.php index 4f87ebb33933b..2e62b8b87b48a 100644 --- a/src/Symfony/Component/Notifier/NotifierInterface.php +++ b/src/Symfony/Component/Notifier/NotifierInterface.php @@ -18,8 +18,6 @@ * Interface for the Notifier system. * * @author Fabien Potencier - * - * @experimental in 5.3 */ interface NotifierInterface { diff --git a/src/Symfony/Component/Notifier/README.md b/src/Symfony/Component/Notifier/README.md index cde2108ff702e..92ce150079641 100644 --- a/src/Symfony/Component/Notifier/README.md +++ b/src/Symfony/Component/Notifier/README.md @@ -3,11 +3,6 @@ Notifier Component The Notifier component sends notifications via one or more channels (email, SMS, ...). -**This Component is experimental**. -[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) -are not covered by Symfony's -[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). - Resources --------- diff --git a/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php b/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php index bc3781ac3f813..6f8fb9de9c4cb 100644 --- a/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php +++ b/src/Symfony/Component/Notifier/Recipient/EmailRecipientInterface.php @@ -13,8 +13,6 @@ /** * @author Jan Schädlich - * - * @experimental in 5.3 */ interface EmailRecipientInterface extends RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php b/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php index 5424ce9367eaa..eaee0b1f26bd6 100644 --- a/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php +++ b/src/Symfony/Component/Notifier/Recipient/EmailRecipientTrait.php @@ -13,8 +13,6 @@ /** * @author Jan Schädlich - * - * @experimental in 5.3 */ trait EmailRecipientTrait { diff --git a/src/Symfony/Component/Notifier/Recipient/NoRecipient.php b/src/Symfony/Component/Notifier/Recipient/NoRecipient.php index 0e84cc83ab675..ebf21939a5bcd 100644 --- a/src/Symfony/Component/Notifier/Recipient/NoRecipient.php +++ b/src/Symfony/Component/Notifier/Recipient/NoRecipient.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class NoRecipient implements RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/Recipient.php b/src/Symfony/Component/Notifier/Recipient/Recipient.php index 38eca09530d41..f8cc625c90a34 100644 --- a/src/Symfony/Component/Notifier/Recipient/Recipient.php +++ b/src/Symfony/Component/Notifier/Recipient/Recipient.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier * @author Jan Schädlich - * - * @experimental in 5.3 */ class Recipient implements EmailRecipientInterface, SmsRecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php b/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php index cc165d02a1755..14cc075760680 100644 --- a/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php +++ b/src/Symfony/Component/Notifier/Recipient/RecipientInterface.php @@ -13,8 +13,6 @@ /** * @author Jan Schädlich - * - * @experimental in 5.3 */ interface RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php b/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php index 81c6abeb51b8f..cb513847b1032 100644 --- a/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php +++ b/src/Symfony/Component/Notifier/Recipient/SmsRecipientInterface.php @@ -14,8 +14,6 @@ /** * @author Fabien Potencier * @author Jan Schädlich - * - * @experimental in 5.3 */ interface SmsRecipientInterface extends RecipientInterface { diff --git a/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php b/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php index c17b6d65e148f..15e3c1b869adb 100644 --- a/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php +++ b/src/Symfony/Component/Notifier/Recipient/SmsRecipientTrait.php @@ -13,8 +13,6 @@ /** * @author Jan Schädlich - * - * @experimental in 5.3 */ trait SmsRecipientTrait { diff --git a/src/Symfony/Component/Notifier/Texter.php b/src/Symfony/Component/Notifier/Texter.php index 5c69e042a10e8..03b1bcf8bb193 100644 --- a/src/Symfony/Component/Notifier/Texter.php +++ b/src/Symfony/Component/Notifier/Texter.php @@ -22,8 +22,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class Texter implements TexterInterface { diff --git a/src/Symfony/Component/Notifier/TexterInterface.php b/src/Symfony/Component/Notifier/TexterInterface.php index c6bfe8e85cead..e65547755cd70 100644 --- a/src/Symfony/Component/Notifier/TexterInterface.php +++ b/src/Symfony/Component/Notifier/TexterInterface.php @@ -17,8 +17,6 @@ * Interface for classes able to send SMS messages synchronous and/or asynchronous. * * @author Fabien Potencier - * - * @experimental in 5.3 */ interface TexterInterface extends TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index f370af913bd94..c2c8d2d8409bb 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -42,8 +42,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class Transport { diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php index 8c81a3018d26d..ccb1f8c84086b 100644 --- a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php @@ -23,8 +23,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ abstract class AbstractTransport implements TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php index 69cfa61138c2a..d595aa623a723 100644 --- a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php +++ b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php @@ -20,8 +20,6 @@ /** * @author Konstantin Myakshin * @author Fabien Potencier - * - * @experimental in 5.3 */ abstract class AbstractTransportFactory implements TransportFactoryInterface { diff --git a/src/Symfony/Component/Notifier/Transport/Dsn.php b/src/Symfony/Component/Notifier/Transport/Dsn.php index 5b510615557c7..f2385a5584534 100644 --- a/src/Symfony/Component/Notifier/Transport/Dsn.php +++ b/src/Symfony/Component/Notifier/Transport/Dsn.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class Dsn { diff --git a/src/Symfony/Component/Notifier/Transport/FailoverTransport.php b/src/Symfony/Component/Notifier/Transport/FailoverTransport.php index 8714050372966..cc0191e234738 100644 --- a/src/Symfony/Component/Notifier/Transport/FailoverTransport.php +++ b/src/Symfony/Component/Notifier/Transport/FailoverTransport.php @@ -17,8 +17,6 @@ * Uses several Transports using a failover algorithm. * * @author Fabien Potencier - * - * @experimental in 5.3 */ class FailoverTransport extends RoundRobinTransport { diff --git a/src/Symfony/Component/Notifier/Transport/NullTransport.php b/src/Symfony/Component/Notifier/Transport/NullTransport.php index 94fee129d2b45..5178b1e14e8f4 100644 --- a/src/Symfony/Component/Notifier/Transport/NullTransport.php +++ b/src/Symfony/Component/Notifier/Transport/NullTransport.php @@ -21,8 +21,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ class NullTransport implements TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php b/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php index 231837eabbb21..1ee6ed6ed832c 100644 --- a/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php +++ b/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class NullTransportFactory extends AbstractTransportFactory { diff --git a/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php b/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php index c78b8763f1ead..f438d407f43b5 100644 --- a/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php +++ b/src/Symfony/Component/Notifier/Transport/RoundRobinTransport.php @@ -21,8 +21,6 @@ * Uses several Transports using a round robin algorithm. * * @author Fabien Potencier - * - * @experimental in 5.3 */ class RoundRobinTransport implements TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php b/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php index 432f69d04a9d1..ff62e189c6304 100644 --- a/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php +++ b/src/Symfony/Component/Notifier/Transport/TransportFactoryInterface.php @@ -16,8 +16,6 @@ /** * @author Konstantin Myakshin - * - * @experimental in 5.3 */ interface TransportFactoryInterface { diff --git a/src/Symfony/Component/Notifier/Transport/TransportInterface.php b/src/Symfony/Component/Notifier/Transport/TransportInterface.php index 812b03919ed87..85cd910a4aaf8 100644 --- a/src/Symfony/Component/Notifier/Transport/TransportInterface.php +++ b/src/Symfony/Component/Notifier/Transport/TransportInterface.php @@ -18,8 +18,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ interface TransportInterface { diff --git a/src/Symfony/Component/Notifier/Transport/Transports.php b/src/Symfony/Component/Notifier/Transport/Transports.php index 049b64da921dc..f411d6bc0c126 100644 --- a/src/Symfony/Component/Notifier/Transport/Transports.php +++ b/src/Symfony/Component/Notifier/Transport/Transports.php @@ -18,8 +18,6 @@ /** * @author Fabien Potencier - * - * @experimental in 5.3 */ final class Transports implements TransportInterface { From c2e84c610a97ea9a8827717704c9619b7cc458c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 22 Dec 2020 12:19:39 +0100 Subject: [PATCH 056/188] Setup queues once in AMQP --- .../Bridge/AmazonSqs/Transport/Connection.php | 2 +- .../Amqp/Tests/Transport/ConnectionTest.php | 18 ++++++++ .../Bridge/Amqp/Transport/Connection.php | 46 +++++++------------ 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php index f2408dc0588c8..7ccd5889e8e12 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php @@ -118,7 +118,7 @@ public static function fromDsn(string $dsn, array $options = [], HttpClientInter 'wait_time' => (int) $options['wait_time'], 'poll_timeout' => $options['poll_timeout'], 'visibility_timeout' => $options['visibility_timeout'], - 'auto_setup' => (bool) $options['auto_setup'], + 'auto_setup' => filter_var($options['auto_setup'], \FILTER_VALIDATE_BOOLEAN), 'queue_name' => (string) $options['queue_name'], ]; diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php index 36fde1250587c..db8c5e1bb23e9 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php @@ -421,6 +421,24 @@ public function testItCanDisableTheSetup() $connection->publish('body'); } + public function testItSetupQueuesOnce() + { + $factory = new TestAmqpFactory( + $amqpConnection = $this->createMock(\AMQPConnection::class), + $amqpChannel = $this->createMock(\AMQPChannel::class), + $amqpQueue = $this->createMock(\AMQPQueue::class), + $amqpExchange = $this->createMock(\AMQPExchange::class) + ); + + $amqpExchange->expects($this->once())->method('declareExchange'); + $amqpQueue->expects($this->once())->method('declareQueue'); + $amqpQueue->expects($this->once())->method('bind'); + + $connection = Connection::fromDsn('amqp://localhost', ['auto_setup' => true], $factory); + $connection->publish('body'); + $connection->publish('body'); + } + public function testSetChannelPrefetchWhenSetup() { $factory = new TestAmqpFactory( diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index 5c7e76291060d..d48c5474c2efe 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -79,6 +79,8 @@ class Connection private $exchangeOptions; private $queuesOptions; private $amqpFactory; + private $autoSetupExchange; + private $autoSetup; /** * @var \AMQPChannel|null @@ -112,6 +114,7 @@ public function __construct(array $connectionOptions, array $exchangeOptions, ar 'queue_name_pattern' => 'delay_%exchange_name%_%routing_key%_%delay%', ], ], $connectionOptions); + $this->autoSetupExchange = $this->autoSetup = $connectionOptions['auto_setup'] ?? true; $this->exchangeOptions = $exchangeOptions; $this->queuesOptions = $queuesOptions; $this->amqpFactory = $amqpFactory ?: new AmqpFactory(); @@ -207,6 +210,9 @@ public static function fromDsn(string $dsn, array $options = [], AmqpFactory $am $exchangeOptions = $amqpOptions['exchange']; $queuesOptions = $amqpOptions['queues']; unset($amqpOptions['queues'], $amqpOptions['exchange']); + if (isset($amqpOptions['auto_setup'])) { + $amqpOptions['auto_setup'] = filter_var($amqpOptions['auto_setup'], \FILTER_VALIDATE_BOOLEAN); + } $queuesOptions = array_map(function ($queueOptions) { if (!\is_array($queueOptions)) { @@ -285,7 +291,7 @@ public function publish(string $body, array $headers = [], int $delayInMs = 0, A return; } - if ($this->shouldSetup()) { + if ($this->autoSetupExchange) { $this->setupExchangeAndQueues(); } @@ -347,7 +353,7 @@ private function publishOnExchange(\AMQPExchange $exchange, string $body, string private function setupDelay(int $delay, ?string $routingKey) { - if ($this->shouldSetup()) { + if ($this->autoSetup) { $this->setup(); // setup delay exchange and normal exchange for delay queue to DLX messages to } @@ -418,23 +424,12 @@ public function get(string $queueName): ?\AMQPEnvelope { $this->clearWhenDisconnected(); - if ($this->shouldSetup()) { + if ($this->autoSetupExchange) { $this->setupExchangeAndQueues(); } - try { - if (false !== $message = $this->queue($queueName)->get()) { - return $message; - } - } catch (\AMQPQueueException $e) { - if (404 === $e->getCode() && $this->shouldSetup()) { - // If we get a 404 for the queue, it means we need to set up the exchange & queue. - $this->setupExchangeAndQueues(); - - return $this->get($queueName); - } - - throw $e; + if (false !== $message = $this->queue($queueName)->get()) { + return $message; } return null; @@ -452,8 +447,11 @@ public function nack(\AMQPEnvelope $message, string $queueName, int $flags = \AM public function setup(): void { - $this->setupExchangeAndQueues(); + if ($this->autoSetupExchange) { + $this->setupExchangeAndQueues(); + } $this->getDelayExchange()->declareExchange(); + $this->autoSetup = false; } private function setupExchangeAndQueues(): void @@ -466,6 +464,7 @@ private function setupExchangeAndQueues(): void $this->queue($queueName)->bind($this->exchangeOptions['name'], $bindingKey, $queueConfig['binding_arguments'] ?? []); } } + $this->autoSetupExchange = false; } /** @@ -558,19 +557,6 @@ private function clearWhenDisconnected(): void } } - private function shouldSetup(): bool - { - if (!\array_key_exists('auto_setup', $this->connectionOptions)) { - return true; - } - - if (\in_array($this->connectionOptions['auto_setup'], [false, 'false'], true)) { - return false; - } - - return true; - } - private function getDefaultPublishRoutingKey(): ?string { return $this->exchangeOptions['default_publish_routing_key'] ?? null; From d4f001afec98f005a5836e17bf15a29ab29009b3 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 18 Dec 2020 09:57:00 +0100 Subject: [PATCH 057/188] [Notifier] Change return type --- UPGRADE-5.3.md | 5 +++++ .../Component/Notifier/Bridge/Infobip/InfobipTransport.php | 2 +- .../Notifier/Bridge/Mattermost/MattermostTransport.php | 2 +- src/Symfony/Component/Notifier/CHANGELOG.md | 1 + .../Component/Notifier/Transport/AbstractTransport.php | 2 +- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 2ccc3bc039595..cd7dda750eeb5 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -23,6 +23,11 @@ HttpKernel * Marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal +Notifier +------- + +* Changed the return type of `Symfony\Component\Notifier\Transport\AbstractTransportFactory::getEndpoint()` from `?string` to `string` + PhpunitBridge ------------- diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php index df76522386405..32984b3c2f0dc 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php @@ -85,7 +85,7 @@ protected function doSend(MessageInterface $message): SentMessage return new SentMessage($message, (string) $this); } - protected function getEndpoint(): ?string + protected function getEndpoint(): string { return $this->host.($this->port ? ':'.$this->port : ''); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php index 59730b97f78b0..6a718b9145d10 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php @@ -85,7 +85,7 @@ protected function doSend(MessageInterface $message): SentMessage return $sentMessage; } - protected function getEndpoint(): ?string + protected function getEndpoint(): string { return rtrim($this->host.($this->port ? ':'.$this->port : '').($this->path ?? ''), '/'); } diff --git a/src/Symfony/Component/Notifier/CHANGELOG.md b/src/Symfony/Component/Notifier/CHANGELOG.md index 528aa24018742..f6106137d9478 100644 --- a/src/Symfony/Component/Notifier/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * The component is not marked as `@experimental` anymore +* [BC BREAK] Changed the return type of `AbstractTransportFactory::getEndpoint()` from `?string` to `string` 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php index ccb1f8c84086b..670fd49847ebd 100644 --- a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php @@ -79,7 +79,7 @@ public function send(MessageInterface $message): SentMessage abstract protected function doSend(MessageInterface $message): SentMessage; - protected function getEndpoint(): ?string + protected function getEndpoint(): string { return ($this->host ?: $this->getDefaultHost()).($this->port ? ':'.$this->port : ''); } From 8d455dbd0c4310af1a3b0b70bfd084b58ac82524 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Fri, 23 Feb 2018 17:37:28 -0800 Subject: [PATCH 058/188] [WIP] Implements #24314: Support binary / negatable options, e.g. --foo and --no-foo. --- src/Symfony/Component/Console/Application.php | 3 +- .../Console/Descriptor/JsonDescriptor.php | 4 + .../Console/Descriptor/MarkdownDescriptor.php | 7 +- .../Console/Descriptor/TextDescriptor.php | 12 +- .../Console/Descriptor/XmlDescriptor.php | 5 +- .../Component/Console/Input/ArgvInput.php | 17 +-- .../Component/Console/Input/ArrayInput.php | 18 +-- src/Symfony/Component/Console/Input/Input.php | 30 +++-- .../Console/Input/InputDefinition.php | 18 ++- .../Component/Console/Input/InputOption.php | 71 ++++++++++- .../Console/Input/NegatedInputOption.php | 112 ++++++++++++++++++ 11 files changed, 245 insertions(+), 52 deletions(-) create mode 100644 src/Symfony/Component/Console/Input/NegatedInputOption.php diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index cdb5a81d25a26..3dc15b3c6bf7b 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -1033,8 +1033,7 @@ protected function getDefaultInputDefinition() new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), - new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), - new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--ansi', '', InputOption::VALUE_BINARY, 'Force ANSI output', null), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), ]); } diff --git a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php index 8b2a279489d5b..8b9c966b30c1b 100644 --- a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php @@ -119,6 +119,7 @@ private function getInputOptionData(InputOption $option): array 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), + 'is_negatable' => $option->isNegatable(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), ]; @@ -133,6 +134,9 @@ private function getInputDefinitionData(InputDefinition $definition): array $inputOptions = []; foreach ($definition->getOptions() as $name => $option) { + if ($option->isHidden()) { + continue; + } $inputOptions[$name] = $this->getInputOptionData($option); } diff --git a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php index 483a8338dfc1f..42043ac1d82db 100644 --- a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php @@ -68,7 +68,8 @@ protected function describeInputArgument(InputArgument $argument, array $options */ protected function describeInputOption(InputOption $option, array $options = []) { - $name = '--'.$option->getName(); + $negatable = $option->isNegatable() ? '[no-]' : ''; + $name = '--'.$negatable.$option->getName(); if ($option->getShortcut()) { $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; } @@ -79,6 +80,7 @@ protected function describeInputOption(InputOption $option, array $options = []) .'* 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)).'`' ); } @@ -105,6 +107,9 @@ protected function describeInputDefinition(InputDefinition $definition, array $o $this->write('### Options'); foreach ($definition->getOptions() as $option) { + if ($option->isHidden()) { + continue; + } $this->write("\n\n"); if (null !== $describeInputOption = $this->describeInputOption($option)) { $this->write($describeInputOption); diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index e73b8a99fbedd..77a9a49e52413 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -56,10 +56,12 @@ protected function describeInputArgument(InputArgument $argument, array $options */ protected function describeInputOption(InputOption $option, array $options = []) { + $default = ''; if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); - } else { - $default = ''; + } elseif ($option->isNegatable() && (null !== $option->getDefault())) { + $negative_default = $option->getDefault() ? '' : 'no-'; + $default = sprintf(' [default: --%s%s]', $negative_default, $option->getName()); } $value = ''; @@ -72,9 +74,10 @@ protected function describeInputOption(InputOption $option, array $options = []) } $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); + $negatable = $option->isNegatable() ? '[no-]' : ''; $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', - sprintf('--%s%s', $option->getName(), $value) + sprintf('--%s%s%s', $negatable, $option->getName(), $value) ); $spacingWidth = $totalWidth - Helper::strlen($synopsis); @@ -117,6 +120,9 @@ protected function describeInputDefinition(InputDefinition $definition, array $o $this->writeText('Options:', $options); foreach ($definition->getOptions() as $option) { + if ($option->isHidden()) { + continue; + } if (\strlen($option->getShortcut()) > 1) { $laterOptions[] = $option; continue; diff --git a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php index 24035f5a3b58b..0ed8b24e65972 100644 --- a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php @@ -38,7 +38,9 @@ public function getInputDefinitionDocument(InputDefinition $definition): \DOMDoc $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { - $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + if (!$option->isHidden()) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } } return $dom; @@ -210,6 +212,7 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->setAttribute('is_negatable', $option->isNegatable() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 2171bdc968519..8d8fee820acc7 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -208,11 +208,7 @@ private function addShortOption(string $shortcut, $value) */ private function addLongOption(string $name, $value) { - if (!$this->definition->hasOption($name)) { - throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); - } - - $option = $this->definition->getOption($name); + $option = $this->getOptionDefinition($name); if (null !== $value && !$option->acceptValue()) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); @@ -229,15 +225,8 @@ private function addLongOption(string $name, $value) } } - if (null === $value) { - if ($option->isValueRequired()) { - throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); - } - - if (!$option->isArray() && !$option->isValueOptional()) { - $value = true; - } - } + $name = $option->effectiveName(); + $value = $option->checkValue($value); if ($option->isArray()) { $this->options[$name][] = $value; diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index 66f0bedc3626b..353f6185ead47 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -164,23 +164,7 @@ private function addShortOption(string $shortcut, $value) */ private function addLongOption(string $name, $value) { - if (!$this->definition->hasOption($name)) { - throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); - } - - $option = $this->definition->getOption($name); - - if (null === $value) { - if ($option->isValueRequired()) { - throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); - } - - if (!$option->isValueOptional()) { - $value = true; - } - } - - $this->options[$name] = $value; + $this->setOption($name, $value); } /** diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php index ff5d3170f4e04..d57a0f7d4e033 100644 --- a/src/Symfony/Component/Console/Input/Input.php +++ b/src/Symfony/Component/Console/Input/Input.php @@ -146,11 +146,8 @@ public function getOptions() */ public function getOption(string $name) { - if (!$this->definition->hasOption($name)) { - throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); - } - - return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + $option = $this->getOptionDefinition($name); + return \array_key_exists($name, $this->options) ? $this->options[$name] : $option->getDefault(); } /** @@ -158,11 +155,8 @@ public function getOption(string $name) */ public function setOption(string $name, $value) { - if (!$this->definition->hasOption($name)) { - throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); - } - - $this->options[$name] = $value; + $option = $this->getOptionDefinition($name); + $this->options[$option->effectiveName()] = $option->checkValue($value); } /** @@ -198,4 +192,20 @@ public function getStream() { return $this->stream; } + + /** + * Look up the option definition for the given option name. + * + * @param string $name + * + * @return InputOption + */ + protected function getOptionDefinition($name) + { + if (!$this->definition->hasOption($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->definition->getOption($name); + } } diff --git a/src/Symfony/Component/Console/Input/InputDefinition.php b/src/Symfony/Component/Console/Input/InputDefinition.php index a32e913b7d5f9..0a22f2570dcce 100644 --- a/src/Symfony/Component/Console/Input/InputDefinition.php +++ b/src/Symfony/Component/Console/Input/InputDefinition.php @@ -227,6 +227,19 @@ public function addOptions(array $options = []) * @throws LogicException When option given already exist */ public function addOption(InputOption $option) + { + $this->doAddOption($option); + + if ($option->isNegatable()) { + $negatedOption = new NegatedInputOption($option); + $this->doAddOption($negatedOption); + } + } + + /** + * @throws LogicException When option given already exist + */ + private function doAddOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); @@ -316,7 +329,7 @@ public function getOptionDefaults() { $values = []; foreach ($this->options as $option) { - $values[$option->getName()] = $option->getDefault(); + $values[$option->effectiveName()] = $option->getDefault(); } return $values; @@ -351,6 +364,9 @@ public function getSynopsis(bool $short = false) $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { + if ($option->isHidden()) { + continue; + } $value = ''; if ($option->acceptValue()) { $value = sprintf( diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index 66f857a6c0d3b..941d1f7726e75 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Exception\LogicException; /** @@ -25,6 +26,9 @@ class InputOption public const VALUE_REQUIRED = 2; public const VALUE_OPTIONAL = 4; public const VALUE_IS_ARRAY = 8; + public const VALUE_NEGATABLE = 16; + public const VALUE_HIDDEN = 32; + public const VALUE_BINARY = (self::VALUE_NONE | self::VALUE_NEGATABLE); private $name; private $shortcut; @@ -70,7 +74,7 @@ public function __construct(string $name, $shortcut = null, int $mode = null, st if (null === $mode) { $mode = self::VALUE_NONE; - } elseif ($mode > 15 || $mode < 1) { + } elseif ($mode >= (self::VALUE_HIDDEN << 1) || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } @@ -106,6 +110,11 @@ public function getName() return $this->name; } + public function effectiveName() + { + return $this->getName(); + } + /** * Returns true if the option accepts a value. * @@ -146,6 +155,39 @@ public function isArray() return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } + /** + * Returns true if the option is negatable (option --foo can be forced + * to 'false' via the --no-foo option). + * + * @return bool true if mode is self::VALUE_NEGATABLE, false otherwise + */ + public function isNegatable() + { + return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); + } + + /** + * Returns true if the option should not be shown in help (e.g. a negated + * option). + * + * @return bool true if mode is self::VALUE_HIDDEN, false otherwise + */ + public function isHidden() + { + return self::VALUE_HIDDEN === (self::VALUE_HIDDEN & $this->mode); + } + + /** + * Returns true if the option is binary (can be --foo or --no-foo, and + * nothing else). + * + * @return bool true if negatable and does not have a value. + */ + public function isBinary() + { + return $this->isNegatable() && !$this->acceptValue(); + } + /** * Sets the default value. * @@ -155,7 +197,7 @@ public function isArray() */ public function setDefault($default = null) { - if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + if (self::VALUE_NONE === ((self::VALUE_NONE | self::VALUE_NEGATABLE) & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } @@ -167,7 +209,7 @@ public function setDefault($default = null) } } - $this->default = $this->acceptValue() ? $default : false; + $this->default = ($this->acceptValue() || $this->isNegatable()) ? $default : false; } /** @@ -190,6 +232,27 @@ public function getDescription() return $this->description; } + /** + * Checks the validity of a value, and alters it as necessary + * + * @param mixed $value + * + * @return @mixed + */ + public function checkValue($value) + { + if (null === $value) { + if ($this->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $this->getName())); + } + + if (!$this->isValueOptional()) { + return true; + } + } + return $value; + } + /** * Checks whether the given option equals this one. * @@ -200,6 +263,8 @@ public function equals(self $option) return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() + && $option->isHidden() === $this->isHidden() + && $option->isNegatable() === $this->isNegatable() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional() diff --git a/src/Symfony/Component/Console/Input/NegatedInputOption.php b/src/Symfony/Component/Console/Input/NegatedInputOption.php new file mode 100644 index 0000000000000..07a4bb6d09482 --- /dev/null +++ b/src/Symfony/Component/Console/Input/NegatedInputOption.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\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class NegatedInputOption extends InputOption +{ + private $primaryOption; + + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + const VALUE_NEGATABLE = 16; + const VALUE_HIDDEN = 32; + const VALUE_BINARY = (self::VALUE_NONE | self::VALUE_NEGATABLE); + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * @param string $name The option name + * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct(InputOption $primaryOption) + { + $this->primaryOption = $primaryOption; + + /* string $name, $shortcut = null, int $mode = null, string $description = '', $default = null */ + $name = 'no-' . $primaryOption->getName(); + $shortcut = null; + $mode = self::VALUE_HIDDEN; + $description = $primaryOption->getDescription(); + $default = $this->negate($primaryOption->getDefault()); + + parent::__construct($name, $shortcut, $mode, $description, $default); + } + + public function effectiveName() + { + return $this->primaryOption->getName(); + } + + /** + * Sets the default value. + * + * @param mixed $default The default value + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + $this->primaryOption->setDefault($this->negate($default)); + } + + /** + * Returns the default value. + * + * @return mixed The default value + */ + public function getDefault() + { + return $this->negate($this->primaryOption->getDefault()); + } + + /** + * @inheritdoc + */ + public function checkValue($value) + { + return false; + } + + /** + * Negate a value if it is provided. + * + * @param mixed $value + * + * @return mixed + */ + private function negate($value) + { + if ($value === null) { + return $value; + } + return !$value; + } +} From 59f29c592b2e7d55fca7b9efdbffcb507b729c31 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Tue, 22 Dec 2020 10:44:23 +0100 Subject: [PATCH 059/188] [Notifier] [Slack] Validate token syntax --- .../Notifier/Bridge/Slack/CHANGELOG.md | 1 + .../Notifier/Bridge/Slack/SlackTransport.php | 5 ++++ .../Slack/Tests/SlackTransportFactoryTest.php | 4 +-- .../Bridge/Slack/Tests/SlackTransportTest.php | 27 ++++++++++++------- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md index 650a09bc1eec8..bb2c7a9ecc0a0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Check for maximum number of buttons in Slack action block * Add HeaderBlock + * Slack access tokens needs to start with "xox" (see https://api.slack.com/authentication/token-types) 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php index dba82100503bb..c877d706d2635 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Notifier\Bridge\Slack; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; @@ -35,6 +36,10 @@ final class SlackTransport extends AbstractTransport public function __construct(string $accessToken, string $channel = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) { + if (!preg_match('/^xox(b-|p-|a-2)/', $accessToken)) { + throw new InvalidArgumentException('A valid Slack token needs to start with "xoxb-", "xoxp-" or "xoxa-2". See https://api.slack.com/authentication/token-types for further information.'); + } + $this->accessToken = $accessToken; $this->chatChannel = $channel; $this->client = $client; diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportFactoryTest.php index 39c5396179455..74c8147cb3ff1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportFactoryTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportFactoryTest.php @@ -24,7 +24,7 @@ public function testCreateWithDsn() { $factory = $this->createFactory(); - $transport = $factory->create(Dsn::fromString('slack://testUser@host.test/?channel=testChannel')); + $transport = $factory->create(Dsn::fromString('slack://xoxb-TestUser@host.test/?channel=testChannel')); $this->assertSame('slack://host.test?channel=testChannel', (string) $transport); } @@ -33,7 +33,7 @@ public function testCreateWithDsnWithoutPath() { $factory = $this->createFactory(); - $transport = $factory->create(Dsn::fromString('slack://testUser@host.test?channel=testChannel')); + $transport = $factory->create(Dsn::fromString('slack://xoxb-TestUser@host.test?channel=testChannel')); $this->assertSame('slack://host.test?channel=testChannel', (string) $transport); } diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php index b97f59c2a74de..6e033c07b4a7a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\Slack\SlackOptions; use Symfony\Component\Notifier\Bridge\Slack\SlackTransport; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; @@ -31,15 +32,23 @@ public function testToStringContainsProperties() { $channel = 'test Channel'; // invalid channel name to test url encoding of the channel - $transport = new SlackTransport('testToken', $channel, $this->createMock(HttpClientInterface::class)); + $transport = new SlackTransport('xoxb-TestToken', $channel, $this->createMock(HttpClientInterface::class)); $transport->setHost('host.test'); $this->assertSame('slack://host.test?channel=test+Channel', (string) $transport); } + public function testInstatiatingWithAnInvalidSlackTokenThrowsInvalidArgumentException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('A valid Slack token needs to start with "xoxb-", "xoxp-" or "xoxa-2". See https://api.slack.com/authentication/token-types for further information.'); + + new SlackTransport('token', 'testChannel', $this->createMock(HttpClientInterface::class)); + } + public function testSupportsChatMessage() { - $transport = new SlackTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class)); + $transport = new SlackTransport('xoxb-TestToken', 'testChannel', $this->createMock(HttpClientInterface::class)); $this->assertTrue($transport->supports(new ChatMessage('testChatMessage'))); $this->assertFalse($transport->supports($this->createMock(MessageInterface::class))); @@ -47,7 +56,7 @@ public function testSupportsChatMessage() public function testSendNonChatMessageThrowsLogicException() { - $transport = new SlackTransport('testToken', 'testChannel', $this->createMock(HttpClientInterface::class)); + $transport = new SlackTransport('xoxb-TestToken', 'testChannel', $this->createMock(HttpClientInterface::class)); $this->expectException(UnsupportedMessageTypeException::class); @@ -70,7 +79,7 @@ public function testSendWithEmptyArrayResponseThrows() return $response; }); - $transport = new SlackTransport('testToken', 'testChannel', $client); + $transport = new SlackTransport('xoxb-TestToken', 'testChannel', $client); $transport->send(new ChatMessage('testMessage')); } @@ -93,14 +102,14 @@ public function testSendWithErrorResponseThrows() return $response; }); - $transport = new SlackTransport('testToken', 'testChannel', $client); + $transport = new SlackTransport('xoxb-TestToken', 'testChannel', $client); $transport->send(new ChatMessage('testMessage')); } public function testSendWithOptions() { - $token = 'testToken'; + $token = 'xoxb-TestToken'; $channel = 'testChannel'; $message = 'testMessage'; @@ -129,7 +138,7 @@ public function testSendWithOptions() public function testSendWithNotification() { - $token = 'testToken'; + $token = 'xoxb-TestToken'; $channel = 'testChannel'; $message = 'testMessage'; @@ -172,14 +181,14 @@ public function testSendWithInvalidOptions() return $this->createMock(ResponseInterface::class); }); - $transport = new SlackTransport('testToken', 'testChannel', $client); + $transport = new SlackTransport('xoxb-TestToken', 'testChannel', $client); $transport->send(new ChatMessage('testMessage', $this->createMock(MessageOptionsInterface::class))); } public function testSendWith200ResponseButNotOk() { - $token = 'testToken'; + $token = 'xoxb-TestToken'; $channel = 'testChannel'; $message = 'testMessage'; From 90e0f5a97df268bebb7486e6f6625a7fa7ea7b33 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 28 Dec 2020 23:22:29 +0100 Subject: [PATCH 060/188] [PhpUnitBridge] Remove obsolete polyfills. --- .../PhpUnit/Legacy/PolyfillAssertTrait.php | 401 ------------------ .../PhpUnit/Legacy/PolyfillTestCaseTrait.php | 101 +---- 2 files changed, 3 insertions(+), 499 deletions(-) diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php index 5a66282d855ca..7424b7226ea14 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php @@ -11,9 +11,7 @@ namespace Symfony\Bridge\PhpUnit\Legacy; -use PHPUnit\Framework\Constraint\IsEqual; use PHPUnit\Framework\Constraint\LogicalNot; -use PHPUnit\Framework\Constraint\StringContains; use PHPUnit\Framework\Constraint\TraversableContains; /** @@ -21,18 +19,6 @@ */ trait PolyfillAssertTrait { - /** - * @param float $delta - * @param string $message - * - * @return void - */ - public static function assertEqualsWithDelta($expected, $actual, $delta, $message = '') - { - $constraint = new IsEqual($expected, $delta); - static::assertThat($actual, $constraint, $message); - } - /** * @param iterable $haystack * @param string $message @@ -57,225 +43,6 @@ public static function assertNotContainsEquals($needle, $haystack, $message = '' static::assertThat($haystack, $constraint, $message); } - /** - * @param string $message - * - * @return void - */ - public static function assertIsArray($actual, $message = '') - { - static::assertInternalType('array', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsBool($actual, $message = '') - { - static::assertInternalType('bool', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsFloat($actual, $message = '') - { - static::assertInternalType('float', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsInt($actual, $message = '') - { - static::assertInternalType('int', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsNumeric($actual, $message = '') - { - static::assertInternalType('numeric', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsObject($actual, $message = '') - { - static::assertInternalType('object', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsResource($actual, $message = '') - { - static::assertInternalType('resource', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsString($actual, $message = '') - { - static::assertInternalType('string', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsScalar($actual, $message = '') - { - static::assertInternalType('scalar', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsCallable($actual, $message = '') - { - static::assertInternalType('callable', $actual, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertIsIterable($actual, $message = '') - { - static::assertInternalType('iterable', $actual, $message); - } - - /** - * @param string $needle - * @param string $haystack - * @param string $message - * - * @return void - */ - public static function assertStringContainsString($needle, $haystack, $message = '') - { - $constraint = new StringContains($needle, false); - static::assertThat($haystack, $constraint, $message); - } - - /** - * @param string $needle - * @param string $haystack - * @param string $message - * - * @return void - */ - public static function assertStringContainsStringIgnoringCase($needle, $haystack, $message = '') - { - $constraint = new StringContains($needle, true); - static::assertThat($haystack, $constraint, $message); - } - - /** - * @param string $needle - * @param string $haystack - * @param string $message - * - * @return void - */ - public static function assertStringNotContainsString($needle, $haystack, $message = '') - { - $constraint = new LogicalNot(new StringContains($needle, false)); - static::assertThat($haystack, $constraint, $message); - } - - /** - * @param string $needle - * @param string $haystack - * @param string $message - * - * @return void - */ - public static function assertStringNotContainsStringIgnoringCase($needle, $haystack, $message = '') - { - $constraint = new LogicalNot(new StringContains($needle, true)); - static::assertThat($haystack, $constraint, $message); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertFinite($actual, $message = '') - { - static::assertInternalType('float', $actual, $message); - static::assertTrue(is_finite($actual), $message ?: "Failed asserting that $actual is finite."); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertInfinite($actual, $message = '') - { - static::assertInternalType('float', $actual, $message); - static::assertTrue(is_infinite($actual), $message ?: "Failed asserting that $actual is infinite."); - } - - /** - * @param string $message - * - * @return void - */ - public static function assertNan($actual, $message = '') - { - static::assertInternalType('float', $actual, $message); - static::assertTrue(is_nan($actual), $message ?: "Failed asserting that $actual is nan."); - } - - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertIsReadable($filename, $message = '') - { - static::assertInternalType('string', $filename, $message); - static::assertTrue(is_readable($filename), $message ?: "Failed asserting that $filename is readable."); - } - - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertNotIsReadable($filename, $message = '') - { - static::assertInternalType('string', $filename, $message); - static::assertFalse(is_readable($filename), $message ?: "Failed asserting that $filename is not readable."); - } - /** * @param string $filename * @param string $message @@ -287,30 +54,6 @@ public static function assertIsNotReadable($filename, $message = '') static::assertNotIsReadable($filename, $message); } - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertIsWritable($filename, $message = '') - { - static::assertInternalType('string', $filename, $message); - static::assertTrue(is_writable($filename), $message ?: "Failed asserting that $filename is writable."); - } - - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertNotIsWritable($filename, $message = '') - { - static::assertInternalType('string', $filename, $message); - static::assertFalse(is_writable($filename), $message ?: "Failed asserting that $filename is not writable."); - } - /** * @param string $filename * @param string $message @@ -322,30 +65,6 @@ public static function assertIsNotWritable($filename, $message = '') static::assertNotIsWritable($filename, $message); } - /** - * @param string $directory - * @param string $message - * - * @return void - */ - public static function assertDirectoryExists($directory, $message = '') - { - static::assertInternalType('string', $directory, $message); - static::assertTrue(is_dir($directory), $message ?: "Failed asserting that $directory exists."); - } - - /** - * @param string $directory - * @param string $message - * - * @return void - */ - public static function assertDirectoryNotExists($directory, $message = '') - { - static::assertInternalType('string', $directory, $message); - static::assertFalse(is_dir($directory), $message ?: "Failed asserting that $directory does not exist."); - } - /** * @param string $directory * @param string $message @@ -357,30 +76,6 @@ public static function assertDirectoryDoesNotExist($directory, $message = '') static::assertDirectoryNotExists($directory, $message); } - /** - * @param string $directory - * @param string $message - * - * @return void - */ - public static function assertDirectoryIsReadable($directory, $message = '') - { - static::assertDirectoryExists($directory, $message); - static::assertIsReadable($directory, $message); - } - - /** - * @param string $directory - * @param string $message - * - * @return void - */ - public static function assertDirectoryNotIsReadable($directory, $message = '') - { - static::assertDirectoryExists($directory, $message); - static::assertNotIsReadable($directory, $message); - } - /** * @param string $directory * @param string $message @@ -392,30 +87,6 @@ public static function assertDirectoryIsNotReadable($directory, $message = '') static::assertDirectoryNotIsReadable($directory, $message); } - /** - * @param string $directory - * @param string $message - * - * @return void - */ - public static function assertDirectoryIsWritable($directory, $message = '') - { - static::assertDirectoryExists($directory, $message); - static::assertIsWritable($directory, $message); - } - - /** - * @param string $directory - * @param string $message - * - * @return void - */ - public static function assertDirectoryNotIsWritable($directory, $message = '') - { - static::assertDirectoryExists($directory, $message); - static::assertNotIsWritable($directory, $message); - } - /** * @param string $directory * @param string $message @@ -427,30 +98,6 @@ public static function assertDirectoryIsNotWritable($directory, $message = '') static::assertDirectoryNotIsWritable($directory, $message); } - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertFileExists($filename, $message = '') - { - static::assertInternalType('string', $filename, $message); - static::assertTrue(file_exists($filename), $message ?: "Failed asserting that $filename exists."); - } - - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertFileNotExists($filename, $message = '') - { - static::assertInternalType('string', $filename, $message); - static::assertFalse(file_exists($filename), $message ?: "Failed asserting that $filename does not exist."); - } - /** * @param string $filename * @param string $message @@ -462,30 +109,6 @@ public static function assertFileDoesNotExist($filename, $message = '') static::assertFileNotExists($filename, $message); } - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertFileIsReadable($filename, $message = '') - { - static::assertFileExists($filename, $message); - static::assertIsReadable($filename, $message); - } - - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertFileNotIsReadable($filename, $message = '') - { - static::assertFileExists($filename, $message); - static::assertNotIsReadable($filename, $message); - } - /** * @param string $filename * @param string $message @@ -497,30 +120,6 @@ public static function assertFileIsNotReadable($filename, $message = '') static::assertFileNotIsReadable($filename, $message); } - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertFileIsWritable($filename, $message = '') - { - static::assertFileExists($filename, $message); - static::assertIsWritable($filename, $message); - } - - /** - * @param string $filename - * @param string $message - * - * @return void - */ - public static function assertFileNotIsWritable($filename, $message = '') - { - static::assertFileExists($filename, $message); - static::assertNotIsWritable($filename, $message); - } - /** * @param string $filename * @param string $message diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php index ad2150436833d..8673bdc0a1d2b 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php @@ -14,88 +14,12 @@ use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\Error\Notice; use PHPUnit\Framework\Error\Warning; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; /** * This trait is @internal. */ trait PolyfillTestCaseTrait { - /** - * @param string|string[] $originalClassName - * - * @return MockObject - */ - protected function createMock($originalClassName) - { - $mock = $this->getMockBuilder($originalClassName) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning(); - - if (method_exists($mock, 'disallowMockingUnknownTypes')) { - $mock = $mock->disallowMockingUnknownTypes(); - } - - return $mock->getMock(); - } - - /** - * @param string|string[] $originalClassName - * @param string[] $methods - * - * @return MockObject - */ - protected function createPartialMock($originalClassName, array $methods) - { - $mock = $this->getMockBuilder($originalClassName) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->setMethods(empty($methods) ? null : $methods); - - if (method_exists($mock, 'disallowMockingUnknownTypes')) { - $mock = $mock->disallowMockingUnknownTypes(); - } - - return $mock->getMock(); - } - - /** - * @param string $exception - * - * @return void - */ - public function expectException($exception) - { - $this->doExpectException($exception); - } - - /** - * @param int|string $code - * - * @return void - */ - public function expectExceptionCode($code) - { - $property = new \ReflectionProperty(TestCase::class, 'expectedExceptionCode'); - $property->setAccessible(true); - $property->setValue($this, $code); - } - - /** - * @param string $message - * - * @return void - */ - public function expectExceptionMessage($message) - { - $property = new \ReflectionProperty(TestCase::class, 'expectedExceptionMessage'); - $property->setAccessible(true); - $property->setValue($this, $message); - } - /** * @param string $messageRegExp * @@ -106,24 +30,12 @@ public function expectExceptionMessageMatches($messageRegExp) $this->expectExceptionMessageRegExp($messageRegExp); } - /** - * @param string $messageRegExp - * - * @return void - */ - public function expectExceptionMessageRegExp($messageRegExp) - { - $property = new \ReflectionProperty(TestCase::class, 'expectedExceptionMessageRegExp'); - $property->setAccessible(true); - $property->setValue($this, $messageRegExp); - } - /** * @return void */ public function expectNotice() { - $this->doExpectException(Notice::class); + $this->expectException(Notice::class); } /** @@ -151,7 +63,7 @@ public function expectNoticeMessageMatches($regularExpression) */ public function expectWarning() { - $this->doExpectException(Warning::class); + $this->expectException(Warning::class); } /** @@ -179,7 +91,7 @@ public function expectWarningMessageMatches($regularExpression) */ public function expectError() { - $this->doExpectException(Error::class); + $this->expectException(Error::class); } /** @@ -201,11 +113,4 @@ public function expectErrorMessageMatches($regularExpression) { $this->expectExceptionMessageMatches($regularExpression); } - - private function doExpectException($exception) - { - $property = new \ReflectionProperty(TestCase::class, 'expectedException'); - $property->setAccessible(true); - $property->setValue($this, $exception); - } } From eb1669069b9e38e8f90d06027d759b9c205077c0 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 29 Dec 2020 00:56:14 +0100 Subject: [PATCH 061/188] [PhpUnitBridge] Modernize CoverageListener --- .../Bridge/PhpUnit/CoverageListener.php | 105 +++++++++++- .../PhpUnit/Legacy/CoverageListenerForV7.php | 41 ----- .../PhpUnit/Legacy/CoverageListenerTrait.php | 160 ------------------ .../Fixtures/coverage/tests/bootstrap.php | 2 - 4 files changed, 102 insertions(+), 206 deletions(-) delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php diff --git a/src/Symfony/Bridge/PhpUnit/CoverageListener.php b/src/Symfony/Bridge/PhpUnit/CoverageListener.php index 5dd29a9cbb718..766252b8728b7 100644 --- a/src/Symfony/Bridge/PhpUnit/CoverageListener.php +++ b/src/Symfony/Bridge/PhpUnit/CoverageListener.php @@ -11,10 +11,109 @@ namespace Symfony\Bridge\PhpUnit; -class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV7', 'Symfony\Bridge\PhpUnit\CoverageListener'); +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestListenerDefaultImplementation; +use PHPUnit\Framework\Warning; +use PHPUnit\Util\Annotation\Registry; +use PHPUnit\Util\Test as TestUtil; -if (false) { - class CoverageListener +class CoverageListener implements TestListener +{ + use TestListenerDefaultImplementation; + + private $sutFqcnResolver; + private $warningOnSutNotFound; + + public function __construct(callable $sutFqcnResolver = null, bool $warningOnSutNotFound = false) + { + $this->sutFqcnResolver = $sutFqcnResolver ?? static function (Test $test): ?string { + $class = \get_class($test); + + $sutFqcn = str_replace('\\Tests\\', '\\', $class); + $sutFqcn = preg_replace('{Test$}', '', $sutFqcn); + + return class_exists($sutFqcn) ? $sutFqcn : null; + }; + + $this->warningOnSutNotFound = $warningOnSutNotFound; + } + + public function startTest(Test $test): void { + if (!$test instanceof TestCase) { + return; + } + + $annotations = TestUtil::parseTestMethodAnnotations(\get_class($test), $test->getName(false)); + + $ignoredAnnotations = ['covers', 'coversDefaultClass', 'coversNothing']; + + foreach ($ignoredAnnotations as $annotation) { + if (isset($annotations['class'][$annotation]) || isset($annotations['method'][$annotation])) { + return; + } + } + + $sutFqcn = ($this->sutFqcnResolver)($test); + if (!$sutFqcn) { + if ($this->warningOnSutNotFound) { + $test->getTestResultObject()->addWarning($test, new Warning('Could not find the tested class.'), 0); + } + + return; + } + + $covers = $sutFqcn; + if (!\is_array($sutFqcn)) { + $covers = [$sutFqcn]; + while ($parent = get_parent_class($sutFqcn)) { + $covers[] = $parent; + $sutFqcn = $parent; + } + } + + if (class_exists(Registry::class)) { + $this->addCoversForDocBlockInsideRegistry($test, $covers); + + return; + } + + $this->addCoversForClassToAnnotationCache($test, $covers); + } + + private function addCoversForClassToAnnotationCache(Test $test, array $covers): void + { + $r = new \ReflectionProperty(TestUtil::class, 'annotationCache'); + $r->setAccessible(true); + + $cache = $r->getValue(); + $cache = array_replace_recursive($cache, [ + \get_class($test) => [ + 'covers' => $covers, + ], + ]); + + $r->setValue(TestUtil::class, $cache); + } + + private function addCoversForDocBlockInsideRegistry(Test $test, array $covers): void + { + $docBlock = Registry::getInstance()->forClassName(\get_class($test)); + + $symbolAnnotations = new \ReflectionProperty($docBlock, 'symbolAnnotations'); + $symbolAnnotations->setAccessible(true); + + // Exclude internal classes; PHPUnit 9.1+ is picky about tests covering, say, a \RuntimeException + $covers = array_filter($covers, function (string $class) { + $reflector = new \ReflectionClass($class); + + return $reflector->isUserDefined(); + }); + + $symbolAnnotations->setValue($docBlock, array_replace($docBlock->symbolAnnotations(), [ + 'covers' => $covers, + ])); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php deleted file mode 100644 index a35034c48b32b..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.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\Bridge\PhpUnit\Legacy; - -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestListenerDefaultImplementation; - -/** - * CoverageListener adds `@covers ` on each test when possible to - * make the code coverage more accurate. - * - * @author Grégoire Pineau - * - * @internal - */ -class CoverageListenerForV7 implements TestListener -{ - use TestListenerDefaultImplementation; - - private $trait; - - public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) - { - $this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound); - } - - public function startTest(Test $test): void - { - $this->trait->startTest($test); - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php deleted file mode 100644 index 4ca396ece164b..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php +++ /dev/null @@ -1,160 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Annotation\Registry; -use PHPUnit\Util\Test; - -/** - * PHP 5.3 compatible trait-like shared implementation. - * - * @author Grégoire Pineau - * - * @internal - */ -class CoverageListenerTrait -{ - private $sutFqcnResolver; - private $warningOnSutNotFound; - private $warnings; - - public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) - { - $this->sutFqcnResolver = $sutFqcnResolver; - $this->warningOnSutNotFound = $warningOnSutNotFound; - $this->warnings = []; - } - - public function startTest($test) - { - if (!$test instanceof TestCase) { - return; - } - - $annotations = Test::parseTestMethodAnnotations(\get_class($test), $test->getName(false)); - - $ignoredAnnotations = ['covers', 'coversDefaultClass', 'coversNothing']; - - foreach ($ignoredAnnotations as $annotation) { - if (isset($annotations['class'][$annotation]) || isset($annotations['method'][$annotation])) { - return; - } - } - - $sutFqcn = $this->findSutFqcn($test); - if (!$sutFqcn) { - if ($this->warningOnSutNotFound) { - $message = 'Could not find the tested class.'; - // addWarning does not exist on old PHPUnit version - if (method_exists($test->getTestResultObject(), 'addWarning') && class_exists(Warning::class)) { - $test->getTestResultObject()->addWarning($test, new Warning($message), 0); - } else { - $this->warnings[] = sprintf("%s::%s\n%s", \get_class($test), $test->getName(), $message); - } - } - - return; - } - - $covers = $sutFqcn; - if (!\is_array($sutFqcn)) { - $covers = [$sutFqcn]; - while ($parent = get_parent_class($sutFqcn)) { - $covers[] = $parent; - $sutFqcn = $parent; - } - } - - if (class_exists(Registry::class)) { - $this->addCoversForDocBlockInsideRegistry($test, $covers); - - return; - } - - $this->addCoversForClassToAnnotationCache($test, $covers); - } - - private function addCoversForClassToAnnotationCache($test, $covers) - { - $r = new \ReflectionProperty(Test::class, 'annotationCache'); - $r->setAccessible(true); - - $cache = $r->getValue(); - $cache = array_replace_recursive($cache, [ - \get_class($test) => [ - 'covers' => $covers, - ], - ]); - - $r->setValue(Test::class, $cache); - } - - private function addCoversForDocBlockInsideRegistry($test, $covers) - { - $docBlock = Registry::getInstance()->forClassName(\get_class($test)); - - $symbolAnnotations = new \ReflectionProperty($docBlock, 'symbolAnnotations'); - $symbolAnnotations->setAccessible(true); - - // Exclude internal classes; PHPUnit 9.1+ is picky about tests covering, say, a \RuntimeException - $covers = array_filter($covers, function ($class) { - $reflector = new \ReflectionClass($class); - - return $reflector->isUserDefined(); - }); - - $symbolAnnotations->setValue($docBlock, array_replace($docBlock->symbolAnnotations(), [ - 'covers' => $covers, - ])); - } - - private function findSutFqcn($test) - { - if ($this->sutFqcnResolver) { - $resolver = $this->sutFqcnResolver; - - return $resolver($test); - } - - $class = \get_class($test); - - $sutFqcn = str_replace('\\Tests\\', '\\', $class); - $sutFqcn = preg_replace('{Test$}', '', $sutFqcn); - - return class_exists($sutFqcn) ? $sutFqcn : null; - } - - public function __sleep() - { - throw new \BadMethodCallException('Cannot serialize '.__CLASS__); - } - - public function __wakeup() - { - throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); - } - - public function __destruct() - { - if (!$this->warnings) { - return; - } - - echo "\n"; - - foreach ($this->warnings as $key => $warning) { - echo sprintf("%d) %s\n", ++$key, $warning); - } - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php index 6d78d06f6b3a5..e302fa05ea74c 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php @@ -12,6 +12,4 @@ require __DIR__.'/../src/BarCov.php'; require __DIR__.'/../src/FooCov.php'; -require __DIR__.'/../../../../Legacy/CoverageListenerTrait.php'; -require_once __DIR__.'/../../../../Legacy/CoverageListenerForV7.php'; require __DIR__.'/../../../../CoverageListener.php'; From c5b9acf5d5c4d7a4b6985f1b312e4bd110a114d0 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 21 Dec 2020 13:19:05 +0100 Subject: [PATCH 062/188] [Notifier] [BC BREAK] Change constructor signature for Mattermost and Esendex transport --- .../Notifier/Bridge/Esendex/CHANGELOG.md | 4 ++++ .../Bridge/Esendex/EsendexTransport.php | 18 ++++++++++------- .../Esendex/EsendexTransportFactory.php | 5 +++-- .../Tests/EsendexTransportFactoryTest.php | 20 ++++++++++++++++++- .../Esendex/Tests/EsendexTransportTest.php | 4 ++-- .../Notifier/Bridge/Mattermost/CHANGELOG.md | 4 ++++ .../Bridge/Mattermost/MattermostTransport.php | 2 +- .../Mattermost/MattermostTransportFactory.php | 2 +- .../Tests/MattermostTransportTest.php | 2 +- 9 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md index d8e243dcb9b42..aba2665873c56 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md @@ -5,6 +5,10 @@ CHANGELOG ----- * The bridge is not marked as `@experimental` anymore +* [BC BREAK] Changed signature of `EsendexTransport::__construct()` method from: + `public function __construct(string $token, string $accountReference, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)` + to: + `public function __construct(string $email, string $password, string $accountReference, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)` 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php index 0cdfc933a7302..7be72d8264849 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php @@ -26,13 +26,15 @@ final class EsendexTransport extends AbstractTransport { protected const HOST = 'api.esendex.com'; - private $token; + private $email; + private $password; private $accountReference; private $from; - public function __construct(string $token, string $accountReference, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + public function __construct(string $email, string $password, string $accountReference, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) { - $this->token = $token; + $this->email = $email; + $this->password = $password; $this->accountReference = $accountReference; $this->from = $from; @@ -41,7 +43,7 @@ public function __construct(string $token, string $accountReference, string $fro public function __toString(): string { - return sprintf('esendex://%s', $this->getEndpoint()); + return sprintf('esendex://%s?accountreference=%s&from=%s', $this->getEndpoint(), $this->accountReference, $this->from); } public function supports(MessageInterface $message): bool @@ -65,18 +67,20 @@ protected function doSend(MessageInterface $message): SentMessage } $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v1.0/messagedispatcher', [ - 'auth_basic' => $this->token, + 'auth_basic' => sprintf('%s:%s', $this->email, $this->password), 'json' => [ 'accountreference' => $this->accountReference, 'messages' => [$messageData], ], ]); - if (200 === $response->getStatusCode()) { + $statusCode = $response->getStatusCode(); + + if (200 === $statusCode) { return new SentMessage($message, (string) $this); } - $message = sprintf('Unable to send the SMS: error %d.', $response->getStatusCode()); + $message = sprintf('Unable to send the SMS: error %d.', $statusCode); try { $result = $response->toArray(false); diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php index cde7d600279d8..62b8b6a559134 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransportFactory.php @@ -30,7 +30,8 @@ public function create(Dsn $dsn): TransportInterface throw new UnsupportedSchemeException($dsn, 'esendex', $this->getSupportedSchemes()); } - $token = $this->getUser($dsn).':'.$this->getPassword($dsn); + $email = $this->getUser($dsn); + $password = $this->getPassword($dsn); $accountReference = $dsn->getOption('accountreference'); if (!$accountReference) { @@ -46,7 +47,7 @@ public function create(Dsn $dsn): TransportInterface $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); $port = $dsn->getPort(); - return (new EsendexTransport($token, $accountReference, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + return (new EsendexTransport($email, $password, $accountReference, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); } protected function getSupportedSchemes(): array diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportFactoryTest.php index 469a23c985367..0fe29f74e4656 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportFactoryTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportFactoryTest.php @@ -25,7 +25,25 @@ public function testCreateWithDsn() $transport = $factory->create(Dsn::fromString('esendex://email:password@host.test?accountreference=testAccountreference&from=testFrom')); - $this->assertSame('esendex://host.test', (string) $transport); + $this->assertSame('esendex://host.test?accountreference=testAccountreference&from=testFrom', (string) $transport); + } + + public function testCreateWithMissingEmailThrowsIncompleteDsnException() + { + $factory = $this->createFactory(); + + $this->expectException(IncompleteDsnException::class); + + $factory->create(Dsn::fromString('esendex://:password@host?accountreference=testAccountreference&from=FROM')); + } + + public function testCreateWithMissingPasswordThrowsIncompleteDsnException() + { + $factory = $this->createFactory(); + + $this->expectException(IncompleteDsnException::class); + + $factory->create(Dsn::fromString('esendex://email:@host?accountreference=testAccountreference&from=FROM')); } public function testCreateWithMissingOptionAccountreferenceThrowsIncompleteDsnException() diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php index 28999d43eef42..7f606ad983790 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php @@ -27,7 +27,7 @@ public function testToString() { $transport = $this->createTransport(); - $this->assertSame('esendex://host.test', (string) $transport); + $this->assertSame('esendex://host.test?accountreference=testAccountReference&from=testFrom', (string) $transport); } public function testSupportsSmsMessage() @@ -90,6 +90,6 @@ public function testSendWithErrorResponseContainingDetailsThrows() private function createTransport(?HttpClientInterface $client = null): EsendexTransport { - return (new EsendexTransport('testToken', 'testAccountReference', 'testFrom', $client ?: $this->createMock(HttpClientInterface::class)))->setHost('host.test'); + return (new EsendexTransport('testEmail', 'testPassword', 'testAccountReference', 'testFrom', $client ?: $this->createMock(HttpClientInterface::class)))->setHost('host.test'); } } diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md index b1c417c0b309b..34c179f2c7ea5 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md @@ -5,6 +5,10 @@ CHANGELOG ----- * The bridge is not marked as `@experimental` anymore +* [BC BREAK] Changed signature of `MattermostTransport::__construct()` method from: + `public function __construct(string $token, string $channel, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, string $path = null)` + to: + `public function __construct(string $token, string $channel, ?string $path = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)` 5.1.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php index 6a718b9145d10..fd5b892071480 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php @@ -29,7 +29,7 @@ final class MattermostTransport extends AbstractTransport private $channel; private $path; - public function __construct(string $token, string $channel, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, string $path = null) + public function __construct(string $token, string $channel, ?string $path = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) { $this->token = $token; $this->channel = $channel; diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php index 002c760cb7f32..472f63e0eaa9c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransportFactory.php @@ -41,7 +41,7 @@ public function create(Dsn $dsn): TransportInterface $host = $dsn->getHost(); $port = $dsn->getPort(); - return (new MattermostTransport($token, $channel, $this->client, $this->dispatcher, $path))->setHost($host)->setPort($port); + return (new MattermostTransport($token, $channel, $path, $this->client, $this->dispatcher))->setHost($host)->setPort($port); } protected function getSupportedSchemes(): array diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php index 53f91afd46cbd..c5ebe44db40fd 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php @@ -29,7 +29,7 @@ final class MattermostTransportTest extends TransportTestCase */ public function createTransport(?HttpClientInterface $client = null): TransportInterface { - return (new MattermostTransport('testAccessToken', 'testChannel', $client ?: $this->createMock(HttpClientInterface::class)))->setHost('host.test'); + return (new MattermostTransport('testAccessToken', 'testChannel', null, $client ?: $this->createMock(HttpClientInterface::class)))->setHost('host.test'); } public function toStringProvider(): iterable From 733ba619a1a1a5f3462566da0aacc5c53a503beb Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 18 Dec 2020 13:27:56 +0100 Subject: [PATCH 063/188] [Notifier] [BC BREAK] Final classes --- .../Component/Notifier/Bridge/LinkedIn/CHANGELOG.md | 1 + .../Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php | 2 +- src/Symfony/Component/Notifier/Bridge/Smsapi/CHANGELOG.md | 7 +++++++ .../Component/Notifier/Bridge/Smsapi/SmsapiTransport.php | 2 ++ .../Notifier/Bridge/Smsapi/SmsapiTransportFactory.php | 4 +++- src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md | 2 ++ .../Component/Notifier/Bridge/Zulip/ZulipTransport.php | 2 +- .../Notifier/Bridge/Zulip/ZulipTransportFactory.php | 2 +- 8 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Notifier/Bridge/Smsapi/CHANGELOG.md diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md index d8e243dcb9b42..d61929abd637c 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * The bridge is not marked as `@experimental` anymore +* [BC BREAK] `LinkedInTransportFactory` is now final 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php index c3dfc7357e113..f052e28e29075 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransportFactory.php @@ -19,7 +19,7 @@ /** * @author Smaïne Milianni */ -class LinkedInTransportFactory extends AbstractTransportFactory +final class LinkedInTransportFactory extends AbstractTransportFactory { public function create(Dsn $dsn): TransportInterface { diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Smsapi/CHANGELOG.md new file mode 100644 index 0000000000000..ee65aa9b4a466 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.3.0 +----- + + * Added the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php index 79df322bf1ae9..61595a6079510 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php @@ -22,6 +22,8 @@ /** * @author Marcin Szepczynski + * + * @experimental in 5.3 */ final class SmsapiTransport extends AbstractTransport { diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php index 5ce25623202ac..6f3e42d97928a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransportFactory.php @@ -19,8 +19,10 @@ /** * @author Marcin Szepczynski + * + * @experimental in 5.3 */ -class SmsapiTransportFactory extends AbstractTransportFactory +final class SmsapiTransportFactory extends AbstractTransportFactory { /** * @return SmsapiTransport diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md index d8e243dcb9b42..b4a92ac9bde52 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG ----- * The bridge is not marked as `@experimental` anymore +* [BC BREAK] `ZulipTransport` is now final +* [BC BREAK] `ZulipTransportFactory` is now final 5.2.0 ----- diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php index 32102dc8344b4..82290188ea91a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php @@ -24,7 +24,7 @@ /** * @author Mohammad Emran Hasan */ -class ZulipTransport extends AbstractTransport +final class ZulipTransport extends AbstractTransport { private $email; private $token; diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php index 9ea1ec66a948c..3914202529243 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php @@ -20,7 +20,7 @@ /** * @author Mohammad Emran Hasan */ -class ZulipTransportFactory extends AbstractTransportFactory +final class ZulipTransportFactory extends AbstractTransportFactory { /** * @return ZulipTransport From 84dd1784cb17dc628c7d1c030b0a847624a6001c Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Fri, 6 Nov 2020 20:34:22 +0100 Subject: [PATCH 064/188] Support multiple types for collection keys & values --- UPGRADE-5.3.md | 5 ++ .../Component/PropertyInfo/CHANGELOG.md | 6 ++ .../Component/PropertyInfo/Tests/TypeTest.php | 73 ++++++++++++++++- src/Symfony/Component/PropertyInfo/Type.php | 82 ++++++++++++++++++- .../Normalizer/AbstractObjectNormalizer.php | 14 ++-- .../Component/Serializer/composer.json | 2 +- .../Mapping/Loader/PropertyInfoLoader.php | 3 +- src/Symfony/Component/Validator/composer.json | 2 +- 8 files changed, 173 insertions(+), 14 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index cd7dda750eeb5..18e85ca3632f7 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -33,6 +33,11 @@ PhpunitBridge * Deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint. +PropertyInfo +------------ + +* Deprecated the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead. + Security -------- diff --git a/src/Symfony/Component/PropertyInfo/CHANGELOG.md b/src/Symfony/Component/PropertyInfo/CHANGELOG.md index a766e6b302242..01fa820c39473 100644 --- a/src/Symfony/Component/PropertyInfo/CHANGELOG.md +++ b/src/Symfony/Component/PropertyInfo/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +5.3.0 +----- + +* Added support for multiple types for collection keys & values +* Deprecated the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead. + 5.2.0 ----- diff --git a/src/Symfony/Component/PropertyInfo/Tests/TypeTest.php b/src/Symfony/Component/PropertyInfo/Tests/TypeTest.php index 30382bec8dbfd..4d1c516b4912e 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/TypeTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/TypeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\PropertyInfo\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\PropertyInfo\Type; /** @@ -19,8 +20,16 @@ */ class TypeTest extends TestCase { - public function testConstruct() + use ExpectDeprecationTrait; + + /** + * @group legacy + */ + public function testLegacyConstruct() { + $this->expectDeprecation('Since symfony/property-info 5.3: The "Symfony\Component\PropertyInfo\Type::getCollectionKeyType()" method is deprecated, use "getCollectionKeyTypes()" instead.'); + $this->expectDeprecation('Since symfony/property-info 5.3: The "Symfony\Component\PropertyInfo\Type::getCollectionValueType()" method is deprecated, use "getCollectionValueTypes()" instead.'); + $type = new Type('object', true, 'ArrayObject', true, new Type('int'), new Type('string')); $this->assertEquals(Type::BUILTIN_TYPE_OBJECT, $type->getBuiltinType()); @@ -37,6 +46,26 @@ public function testConstruct() $this->assertEquals(Type::BUILTIN_TYPE_STRING, $collectionValueType->getBuiltinType()); } + public function testConstruct() + { + $type = new Type('object', true, 'ArrayObject', true, new Type('int'), new Type('string')); + + $this->assertEquals(Type::BUILTIN_TYPE_OBJECT, $type->getBuiltinType()); + $this->assertTrue($type->isNullable()); + $this->assertEquals('ArrayObject', $type->getClassName()); + $this->assertTrue($type->isCollection()); + + $collectionKeyTypes = $type->getCollectionKeyTypes(); + $this->assertIsArray($collectionKeyTypes); + $this->assertContainsOnlyInstancesOf('Symfony\Component\PropertyInfo\Type', $collectionKeyTypes); + $this->assertEquals(Type::BUILTIN_TYPE_INT, $collectionKeyTypes[0]->getBuiltinType()); + + $collectionValueTypes = $type->getCollectionValueTypes(); + $this->assertIsArray($collectionValueTypes); + $this->assertContainsOnlyInstancesOf('Symfony\Component\PropertyInfo\Type', $collectionValueTypes); + $this->assertEquals(Type::BUILTIN_TYPE_STRING, $collectionValueTypes[0]->getBuiltinType()); + } + public function testIterable() { $type = new Type('iterable'); @@ -49,4 +78,46 @@ public function testInvalidType() $this->expectExceptionMessage('"foo" is not a valid PHP type.'); new Type('foo'); } + + public function testArrayCollection() + { + $type = new Type('array', false, null, true, [new Type('int'), new Type('string')], [new Type('object', false, \ArrayObject::class, true), new Type('array', false, null, true)]); + + $this->assertEquals(Type::BUILTIN_TYPE_ARRAY, $type->getBuiltinType()); + $this->assertFalse($type->isNullable()); + $this->assertTrue($type->isCollection()); + + [$firstKeyType, $secondKeyType] = $type->getCollectionKeyTypes(); + $this->assertEquals(Type::BUILTIN_TYPE_INT, $firstKeyType->getBuiltinType()); + $this->assertFalse($firstKeyType->isNullable()); + $this->assertFalse($firstKeyType->isCollection()); + $this->assertEquals(Type::BUILTIN_TYPE_STRING, $secondKeyType->getBuiltinType()); + $this->assertFalse($secondKeyType->isNullable()); + $this->assertFalse($secondKeyType->isCollection()); + + [$firstValueType, $secondValueType] = $type->getCollectionValueTypes(); + $this->assertEquals(Type::BUILTIN_TYPE_OBJECT, $firstValueType->getBuiltinType()); + $this->assertEquals(\ArrayObject::class, $firstValueType->getClassName()); + $this->assertFalse($firstValueType->isNullable()); + $this->assertTrue($firstValueType->isCollection()); + $this->assertEquals(Type::BUILTIN_TYPE_ARRAY, $secondValueType->getBuiltinType()); + $this->assertFalse($secondValueType->isNullable()); + $this->assertTrue($firstValueType->isCollection()); + } + + public function testInvalidCollectionArgument() + { + $this->expectException('TypeError'); + $this->expectExceptionMessage('"Symfony\Component\PropertyInfo\Type::validateCollectionArgument()": Argument #5 ($collectionKeyType) must be of type "Symfony\Component\PropertyInfo\Type[]", "Symfony\Component\PropertyInfo\Type" or "null", "stdClass" given.'); + + new Type('array', false, null, true, new \stdClass(), [new Type('string')]); + } + + public function testInvalidCollectionValueArgument() + { + $this->expectException('TypeError'); + $this->expectExceptionMessage('"Symfony\Component\PropertyInfo\Type::validateCollectionArgument()": Argument #5 ($collectionKeyType) must be of type "Symfony\Component\PropertyInfo\Type[]", "Symfony\Component\PropertyInfo\Type" or "null", array value "array" given.'); + + new Type('array', false, null, true, [new \stdClass()], [new Type('string')]); + } } diff --git a/src/Symfony/Component/PropertyInfo/Type.php b/src/Symfony/Component/PropertyInfo/Type.php index 582b98d6411f5..93255b2a56712 100644 --- a/src/Symfony/Component/PropertyInfo/Type.php +++ b/src/Symfony/Component/PropertyInfo/Type.php @@ -57,9 +57,12 @@ class Type private $collectionValueType; /** + * @param Type[]|Type|null $collectionKeyType + * @param Type[]|Type|null $collectionValueType + * * @throws \InvalidArgumentException */ - public function __construct(string $builtinType, bool $nullable = false, string $class = null, bool $collection = false, self $collectionKeyType = null, self $collectionValueType = null) + public function __construct(string $builtinType, bool $nullable = false, string $class = null, bool $collection = false, $collectionKeyType = null, $collectionValueType = null) { if (!\in_array($builtinType, self::$builtinTypes)) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid PHP type.', $builtinType)); @@ -69,8 +72,31 @@ public function __construct(string $builtinType, bool $nullable = false, string $this->nullable = $nullable; $this->class = $class; $this->collection = $collection; - $this->collectionKeyType = $collectionKeyType; - $this->collectionValueType = $collectionValueType; + $this->collectionKeyType = $this->validateCollectionArgument($collectionKeyType, 5, '$collectionKeyType') ?? []; + $this->collectionValueType = $this->validateCollectionArgument($collectionValueType, 6, '$collectionValueType') ?? []; + } + + private function validateCollectionArgument($collectionArgument, int $argumentIndex, string $argumentName): ?array + { + if (null === $collectionArgument) { + return null; + } + + if (!\is_array($collectionArgument) && !$collectionArgument instanceof self) { + throw new \TypeError(sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, get_debug_type($collectionArgument))); + } + + if (\is_array($collectionArgument)) { + foreach ($collectionArgument as $type) { + if (!$type instanceof self) { + throw new \TypeError(sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", array value "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, get_debug_type($collectionArgument))); + } + } + + return $collectionArgument; + } + + return [$collectionArgument]; } /** @@ -107,8 +133,33 @@ public function isCollection(): bool * Gets collection key type. * * Only applicable for a collection type. + * + * @deprecated since Symfony 5.3, use "getCollectionKeyTypes()" instead */ public function getCollectionKeyType(): ?self + { + trigger_deprecation('symfony/property-info', '5.3', 'The "%s()" method is deprecated, use "getCollectionKeyTypes()" instead.', __METHOD__); + + $type = $this->getCollectionKeyTypes(); + if (0 === \count($type)) { + return null; + } + + if (\is_array($type)) { + [$type] = $type; + } + + return $type; + } + + /** + * Gets collection key types. + * + * Only applicable for a collection type. + * + * @return Type[] + */ + public function getCollectionKeyTypes(): array { return $this->collectionKeyType; } @@ -117,8 +168,33 @@ public function getCollectionKeyType(): ?self * Gets collection value type. * * Only applicable for a collection type. + * + * @deprecated since Symfony 5.3, use "getCollectionValueTypes()" instead */ public function getCollectionValueType(): ?self + { + trigger_deprecation('symfony/property-info', '5.3', 'The "%s()" method is deprecated, use "getCollectionValueTypes()" instead.', __METHOD__); + + $type = $this->getCollectionValueTypes(); + if (0 === \count($type)) { + return null; + } + + if (\is_array($type)) { + [$type] = $type; + } + + return $type; + } + + /** + * Gets collection value types. + * + * Only applicable for a collection type. + * + * @return Type[] + */ + public function getCollectionValueTypes(): array { return $this->collectionValueType; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index aa1be48cfbaf5..36099aa385ab7 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -373,7 +373,7 @@ private function validateAndDenormalize(string $currentClass, string $attribute, return null; } - $collectionValueType = $type->isCollection() ? $type->getCollectionValueType() : null; + $collectionValueType = $type->isCollection() ? $type->getCollectionValueTypes()[0] ?? null : null; // Fix a collection that contains the only one element // This is special to xml format only @@ -431,18 +431,18 @@ private function validateAndDenormalize(string $currentClass, string $attribute, $builtinType = Type::BUILTIN_TYPE_OBJECT; $class = $collectionValueType->getClassName().'[]'; - if (null !== $collectionKeyType = $type->getCollectionKeyType()) { - $context['key_type'] = $collectionKeyType; + if (null !== $collectionKeyType = $type->getCollectionKeyTypes()) { + [$context['key_type']] = $collectionKeyType; } - } elseif ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueType()) && Type::BUILTIN_TYPE_ARRAY === $collectionValueType->getBuiltinType()) { + } elseif ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueTypes()) && \count($collectionValueType) > 0 && Type::BUILTIN_TYPE_ARRAY === $collectionValueType[0]->getBuiltinType()) { // get inner type for any nested array - $innerType = $collectionValueType; + [$innerType] = $collectionValueType; // note that it will break for any other builtinType $dimensions = '[]'; - while (null !== $innerType->getCollectionValueType() && Type::BUILTIN_TYPE_ARRAY === $innerType->getBuiltinType()) { + while (null !== $innerType->getCollectionValueTypes() && Type::BUILTIN_TYPE_ARRAY === $innerType->getBuiltinType()) { $dimensions .= '[]'; - $innerType = $innerType->getCollectionValueType(); + [$innerType] = $innerType->getCollectionValueTypes(); } if (null !== $innerType->getClassName()) { diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 60b8b5c0542ef..b5221f69b9cdc 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -34,7 +34,7 @@ "symfony/http-kernel": "^4.4|^5.0", "symfony/mime": "^4.4|^5.0", "symfony/property-access": "^4.4|^5.0", - "symfony/property-info": "^4.4|^5.0", + "symfony/property-info": "^5.3", "symfony/uid": "^5.1", "symfony/validator": "^4.4|^5.0", "symfony/var-exporter": "^4.4|^5.0", diff --git a/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php index 530348c638448..3f83da8d0517e 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php @@ -119,7 +119,8 @@ public function loadClassMetadata(ClassMetadata $metadata): bool } if (!$hasTypeConstraint) { if (1 === \count($builtinTypes)) { - if ($types[0]->isCollection() && (null !== $collectionValueType = $types[0]->getCollectionValueType())) { + if ($types[0]->isCollection() && (null !== $collectionValueType = $types[0]->getCollectionValueTypes())) { + [$collectionValueType] = $collectionValueType; $this->handleAllConstraint($property, $allConstraint, $collectionValueType, $metadata); } diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 3c4cc5090a3c3..949b36d36035f 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -38,7 +38,7 @@ "symfony/cache": "^4.4|^5.0", "symfony/mime": "^4.4|^5.0", "symfony/property-access": "^4.4|^5.0", - "symfony/property-info": "^4.4|^5.0", + "symfony/property-info": "^5.3", "symfony/translation": "^4.4|^5.0", "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", From 0ecf95021cff8d8a7285eb91e55eb1e9c50c9a2f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 1 Jan 2021 10:27:30 +0100 Subject: [PATCH 065/188] Bump license year --- src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE b/src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE index 5593b1d84f74a..ad85e1737485d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2020 Fabien Potencier +Copyright (c) 2020-2021 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 From 90f6d30b06dfd818b2f0592e55eff7eb59c9173b Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 19 Dec 2020 17:23:05 +0100 Subject: [PATCH 066/188] [Serializer] Migrate ArrayDenormalizer to DenormalizerAwareInterface. --- UPGRADE-5.3.md | 5 ++ UPGRADE-6.0.md | 6 ++ src/Symfony/Component/Serializer/CHANGELOG.md | 5 ++ .../Normalizer/ArrayDenormalizer.php | 35 +++++------ .../Normalizer/ArrayDenormalizerTest.php | 58 +++++++++++++++++-- 5 files changed, 86 insertions(+), 23 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 18e85ca3632f7..8ba314700e5e5 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -42,3 +42,8 @@ Security -------- * Deprecated voters that do not return a valid decision when calling the `vote` method. + +Serializer +---------- + + * Deprecated `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead. diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index a72f4731efc94..78124a7c53888 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -167,6 +167,12 @@ Security * Removed the `AbstractRememberMeServices::$providerKey` property in favor of `AbstractRememberMeServices::$firewallName` * `AccessDecisionManager` now throw an exception when a voter does not return a valid decision. +Serializer +---------- + + * Removed `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead. + * `ArrayDenormalizer` does not implement `SerializerAwareInterface` anymore. + TwigBundle ---------- diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 97ae3fd62fdab..204789fda3b24 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * deprecated `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead. + 5.2.0 ----- diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php index 854400b538e0d..77c746c752282 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php @@ -14,6 +14,7 @@ use Symfony\Component\Serializer\Exception\BadMethodCallException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -24,24 +25,19 @@ * * @final */ -class ArrayDenormalizer implements ContextAwareDenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +class ArrayDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface, SerializerAwareInterface, CacheableSupportsMethodInterface { - /** - * @var SerializerInterface|DenormalizerInterface - */ - private $serializer; + use DenormalizerAwareTrait; /** * {@inheritdoc} * * @throws NotNormalizableValueException - * - * @return array */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize($data, string $type, string $format = null, array $context = []): array { - if (null === $this->serializer) { - throw new BadMethodCallException('Please set a serializer before calling denormalize()!'); + if (null === $this->denormalizer) { + throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); } if (!\is_array($data)) { throw new InvalidArgumentException('Data expected to be an array, '.get_debug_type($data).' given.'); @@ -50,7 +46,6 @@ public function denormalize($data, string $type, string $format = null, array $c throw new InvalidArgumentException('Unsupported class: '.$type); } - $serializer = $this->serializer; $type = substr($type, 0, -2); $builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null; @@ -59,7 +54,7 @@ public function denormalize($data, string $type, string $format = null, array $c throw new NotNormalizableValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, get_debug_type($key))); } - $data[$key] = $serializer->denormalize($value, $type, $format, $context); + $data[$key] = $this->denormalizer->denormalize($value, $type, $format, $context); } return $data; @@ -70,16 +65,18 @@ public function denormalize($data, string $type, string $format = null, array $c */ public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { - if (null === $this->serializer) { - throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used.', __METHOD__)); + if (null === $this->denormalizer) { + throw new BadMethodCallException(sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); } return '[]' === substr($type, -2) - && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); + && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } /** * {@inheritdoc} + * + * @deprecated call setDenormalizer() instead */ public function setSerializer(SerializerInterface $serializer) { @@ -87,7 +84,11 @@ public function setSerializer(SerializerInterface $serializer) throw new InvalidArgumentException('Expected a serializer that also implements DenormalizerInterface.'); } - $this->serializer = $serializer; + if (Serializer::class !== debug_backtrace()[1]['class'] ?? null) { + trigger_deprecation('symfony/serializer', '5.3', 'Calling "%s" is deprecated. Please call setDenormalizer() instead.'); + } + + $this->setDenormalizer($serializer); } /** @@ -95,6 +96,6 @@ public function setSerializer(SerializerInterface $serializer) */ public function hasCacheableSupportsMethod(): bool { - return $this->serializer instanceof CacheableSupportsMethodInterface && $this->serializer->hasCacheableSupportsMethod(); + return $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod(); } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php index ad55636742c77..cde2e1e728554 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php @@ -13,26 +13,30 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; -use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; +use Symfony\Component\Serializer\Serializer; class ArrayDenormalizerTest extends TestCase { + use ExpectDeprecationTrait; + /** * @var ArrayDenormalizer */ private $denormalizer; /** - * @var SerializerInterface|MockObject + * @var ContextAwareDenormalizerInterface|MockObject */ private $serializer; protected function setUp(): void { - $this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')->getMock(); + $this->serializer = $this->getMockBuilder(ContextAwareDenormalizerInterface::class)->getMock(); $this->denormalizer = new ArrayDenormalizer(); - $this->denormalizer->setSerializer($this->serializer); + $this->denormalizer->setDenormalizer($this->serializer); } public function testDenormalize() @@ -65,11 +69,51 @@ public function testDenormalize() ); } + /** + * @group legacy + */ + public function testDenormalizeLegacy() + { + $serializer = $this->getMockBuilder(Serializer::class)->getMock(); + + $serializer->expects($this->exactly(2)) + ->method('denormalize') + ->withConsecutive( + [['foo' => 'one', 'bar' => 'two']], + [['foo' => 'three', 'bar' => 'four']] + ) + ->willReturnOnConsecutiveCalls( + new ArrayDummy('one', 'two'), + new ArrayDummy('three', 'four') + ); + + $denormalizer = new ArrayDenormalizer(); + + $this->expectDeprecation('Since symfony/serializer 5.3: Calling "%s" is deprecated. Please call setDenormalizer() instead.'); + $denormalizer->setSerializer($serializer); + + $result = $denormalizer->denormalize( + [ + ['foo' => 'one', 'bar' => 'two'], + ['foo' => 'three', 'bar' => 'four'], + ], + __NAMESPACE__.'\ArrayDummy[]' + ); + + $this->assertEquals( + [ + new ArrayDummy('one', 'two'), + new ArrayDummy('three', 'four'), + ], + $result + ); + } + public function testSupportsValidArray() { $this->serializer->expects($this->once()) ->method('supportsDenormalization') - ->with($this->anything(), ArrayDummy::class, $this->anything()) + ->with($this->anything(), ArrayDummy::class, 'json', ['con' => 'text']) ->willReturn(true); $this->assertTrue( @@ -78,7 +122,9 @@ public function testSupportsValidArray() ['foo' => 'one', 'bar' => 'two'], ['foo' => 'three', 'bar' => 'four'], ], - __NAMESPACE__.'\ArrayDummy[]' + __NAMESPACE__.'\ArrayDummy[]', + 'json', + ['con' => 'text'] ) ); } From 8baafa2bc0a2c7dc658f2853c04371d8aba5af45 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 2 Jan 2021 17:25:53 +0100 Subject: [PATCH 067/188] deprecate parents() in favor of ancestors() --- UPGRADE-5.3.md | 5 +++ UPGRADE-6.0.md | 5 +++ src/Symfony/Component/DomCrawler/CHANGELOG.md | 1 + src/Symfony/Component/DomCrawler/Crawler.php | 16 +++++++++- .../DomCrawler/Tests/AbstractCrawlerTest.php | 31 ++++++++++++++++++- .../Component/DomCrawler/composer.json | 1 + 6 files changed, 57 insertions(+), 2 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 8ba314700e5e5..4e8ff1dc9de78 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -6,6 +6,11 @@ Asset * Deprecated `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead. +DomCrawler +---------- + +* Deprecated the `parents()` method, use `ancestors()` instead. + Form ---- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 78124a7c53888..ba5084c7db73c 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -33,6 +33,11 @@ DependencyInjection * The `ref()` function from the PHP-DSL has been removed, use `service()` instead. * Removed `Definition::setPrivate()` and `Alias::setPrivate()`, use `setPublic()` instead +DomCrawler +---------- + + * Removed the `parents()` method, use `ancestors()` instead. + Dotenv ------ diff --git a/src/Symfony/Component/DomCrawler/CHANGELOG.md b/src/Symfony/Component/DomCrawler/CHANGELOG.md index 8a5b0e2341ea6..b7ba8b60b1a07 100644 --- a/src/Symfony/Component/DomCrawler/CHANGELOG.md +++ b/src/Symfony/Component/DomCrawler/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3.0 ----- + * The `parents()` method is deprecated. Use `ancestors()` instead. * Marked the `containsOption()`, `availableOptionValues()`, and `disableValidation()` methods of the `ChoiceFormField` class as internal diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 8775508512812..5dbd3b06da846 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -494,13 +494,27 @@ public function previousAll() } /** - * Returns the parents nodes of the current selection. + * Returns the parent nodes of the current selection. * * @return static * * @throws \InvalidArgumentException When current node is empty */ public function parents() + { + @trigger_deprecation('symfony/dom-crawler', '5.3', sprintf('The %s() method is deprecated, use ancestors() instead.', __METHOD__)); + + return $this->ancestors(); + } + + /** + * Returns the ancestors of the current selection. + * + * @return static + * + * @throws \InvalidArgumentException When the current node is empty + */ + public function ancestors() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); diff --git a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php index 19c7e678acacd..3235c5539cd6d 100644 --- a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php @@ -12,10 +12,13 @@ namespace Symfony\Component\DomCrawler\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DomCrawler\Crawler; abstract class AbstractCrawlerTest extends TestCase { + use ExpectDeprecationTrait; + abstract public function getDoctype(): string; protected function createCrawler($node = null, string $uri = null, string $baseHref = null) @@ -409,7 +412,7 @@ public function testFilterXPath() $this->assertCount(6, $crawler->filterXPath('//li'), '->filterXPath() filters the node list with the XPath expression'); $crawler = $this->createTestCrawler(); - $this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained'); + $this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->ancestors(), '->filterXpath() preserves ancestors when chained'); } public function testFilterRemovesDuplicates() @@ -1082,8 +1085,13 @@ public function testFilteredChildren() $this->assertEquals(1, $foo->children('.ipsum')->count()); } + /** + * @group legacy + */ public function testParents() { + $this->expectDeprecation('Since symfony/dom-crawler 5.3: The Symfony\Component\DomCrawler\Crawler::parents() method is deprecated, use ancestors() instead.'); + $crawler = $this->createTestCrawler()->filterXPath('//li[1]'); $this->assertNotSame($crawler, $crawler->parents(), '->parents() returns a new instance of a crawler'); $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->parents() returns a new instance of a crawler'); @@ -1102,6 +1110,27 @@ public function testParents() } } + public function testAncestors() + { + $crawler = $this->createTestCrawler()->filterXPath('//li[1]'); + + $nodes = $crawler->ancestors(); + + $this->assertNotSame($crawler, $nodes, '->ancestors() returns a new instance of a crawler'); + $this->assertInstanceOf(Crawler::class, $nodes, '->ancestors() returns a new instance of a crawler'); + + $this->assertEquals(3, $crawler->ancestors()->count()); + + $this->assertEquals(0, $this->createTestCrawler()->filterXPath('//html')->ancestors()->count()); + } + + public function testAncestorsThrowsIfNodeListIsEmpty() + { + $this->expectException(\InvalidArgumentException::class); + + $this->createTestCrawler()->filterXPath('//ol')->ancestors(); + } + /** * @dataProvider getBaseTagData */ diff --git a/src/Symfony/Component/DomCrawler/composer.json b/src/Symfony/Component/DomCrawler/composer.json index 786f3476d9b8d..9bccde48a7c3c 100644 --- a/src/Symfony/Component/DomCrawler/composer.json +++ b/src/Symfony/Component/DomCrawler/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.15" From 5b55097500bc77e3384fc06c5056fda86e821ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 29 Dec 2020 21:57:03 +0100 Subject: [PATCH 068/188] Deprecate option prefetch_count --- UPGRADE-5.3.md | 5 +++++ UPGRADE-6.0.md | 1 + src/Symfony/Component/Messenger/Bridge/Amqp/CHANGELOG.md | 5 +++++ .../Bridge/Amqp/Tests/Transport/ConnectionTest.php | 9 ++++++--- .../Messenger/Bridge/Amqp/Transport/Connection.php | 9 ++++----- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 8ba314700e5e5..da8682aed102f 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -23,6 +23,11 @@ HttpKernel * Marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal +Messenger +--------- + +* Deprecated the `prefetch_count` parameter in the AMQP bridge, it has no effect and will be removed in Symfony 6.0. + Notifier ------- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 78124a7c53888..dae5bfd4a4cac 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -112,6 +112,7 @@ Messenger * Use of invalid options in Redis and AMQP connections now throws an error. * The signature of method `RetryStrategyInterface::isRetryable()` has been updated to `RetryStrategyInterface::isRetryable(Envelope $message, \Throwable $throwable = null)`. * The signature of method `RetryStrategyInterface::getWaitingTime()` has been updated to `RetryStrategyInterface::getWaitingTime(Envelope $message, \Throwable $throwable = null)`. + * Removed the `prefetch_count` parameter in the AMQP bridge. Mime ---- diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/Amqp/CHANGELOG.md index 81c0100991936..f7a394efdc331 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* Deprecated the `prefetch_count` parameter, it has no effect and will be removed in Symfony 6.0. + 5.2.0 ----- diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php index db8c5e1bb23e9..0d4f63ed6d113 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php @@ -439,6 +439,9 @@ public function testItSetupQueuesOnce() $connection->publish('body'); } + /** + * @group legacy + */ public function testSetChannelPrefetchWhenSetup() { $factory = new TestAmqpFactory( @@ -451,11 +454,11 @@ public function testSetChannelPrefetchWhenSetup() // makes sure the channel looks connected, so it's not re-created $amqpChannel->expects($this->any())->method('isConnected')->willReturn(true); - $amqpChannel->expects($this->exactly(2))->method('setPrefetchCount')->with(2); + $amqpChannel->expects($this->never())->method('setPrefetchCount'); + + $this->expectDeprecation('Since symfony/messenger 5.3: The "prefetch_count" option passed to the AMQP Messenger transport has no effect and should not be used.'); $connection = Connection::fromDsn('amqp://localhost?prefetch_count=2', [], $factory); $connection->setup(); - $connection = Connection::fromDsn('amqp://localhost', ['prefetch_count' => 2], $factory); - $connection->setup(); } public function testAutoSetupWithDelayDeclaresExchangeQueuesAndDelay() diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index d48c5474c2efe..d2d6decf71525 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -149,7 +149,6 @@ public function __construct(array $connectionOptions, array $exchangeOptions, ar * * queue_name_pattern: Pattern to use to create the queues (Default: "delay_%exchange_name%_%routing_key%_%delay%") * * exchange_name: Name of the exchange to be used for the delayed/retried messages (Default: "delays") * * auto_setup: Enable or not the auto-setup of queues and exchanges (Default: true) - * * prefetch_count: set channel prefetch count * * * Connection tuning options (see http://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.tune for details): * * channel_max: Specifies highest channel number that the server permits. 0 means standard extension limit @@ -238,6 +237,10 @@ private static function validateOptions(array $options): void trigger_deprecation('symfony/messenger', '5.1', 'Invalid option(s) "%s" passed to the AMQP Messenger transport. Passing invalid options is deprecated.', implode('", "', $invalidOptions)); } + if (isset($options['prefetch_count'])) { + trigger_deprecation('symfony/messenger', '5.3', 'The "prefetch_count" option passed to the AMQP Messenger transport has no effect and should not be used.'); + } + if (\is_array($options['queues'] ?? false)) { foreach ($options['queues'] as $queue) { if (!\is_array($queue)) { @@ -492,10 +495,6 @@ public function channel(): \AMQPChannel } $this->amqpChannel = $this->amqpFactory->createChannel($connection); - if (isset($this->connectionOptions['prefetch_count'])) { - $this->amqpChannel->setPrefetchCount($this->connectionOptions['prefetch_count']); - } - if ('' !== ($this->connectionOptions['confirm_timeout'] ?? '')) { $this->amqpChannel->confirmSelect(); $this->amqpChannel->setConfirmCallback( From 8fffa2c6f87b7dbaf790ef2d542e3def4acb0801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 31 Dec 2020 01:14:53 +0100 Subject: [PATCH 069/188] [FrameworkBundle][HttpFoundation] add assertResponseFormatSame() --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Test/BrowserKitAssertionsTrait.php | 5 ++ .../Tests/Test/WebTestCaseTest.php | 19 +++++ .../Component/HttpFoundation/CHANGELOG.md | 5 ++ .../Test/Constraint/ResponseFormatSame.php | 71 +++++++++++++++++++ .../Constraint/ResponseFormatSameTest.php | 61 ++++++++++++++++ 6 files changed, 162 insertions(+) create mode 100644 src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseFormatSame.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Test/Constraint/ResponseFormatSameTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index bc5ca7f27dbc2..66710663e54db 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Added support for configuring PHP error level to log levels * Added the `dispatcher` option to `debug:event-dispatcher` * Added the `event_dispatcher.dispatcher` tag + * Added `assertResponseFormatSame()` in `BrowserKitAssertionsTrait` 5.2.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php index 48f2b68e11e32..8caa19fa1f443 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php @@ -38,6 +38,11 @@ public static function assertResponseStatusCodeSame(int $expectedCode, string $m self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message); } + public static function assertResponseFormatSame(?string $expectedFormat, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat), $message); + } + public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void { $constraint = new ResponseConstraint\ResponseIsRedirected(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php index 96e1d8779b31e..0e1ed19e414df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php @@ -23,6 +23,7 @@ use Symfony\Component\HttpFoundation\Cookie as HttpFoundationCookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseFormatSame; class WebTestCaseTest extends TestCase { @@ -75,6 +76,20 @@ public function testAssertResponseRedirectsWithLocationAndStatusCode() $this->getResponseTester(new Response('', 302))->assertResponseRedirects('https://example.com/', 301); } + public function testAssertResponseFormat() + { + if (!class_exists(ResponseFormatSame::class)) { + $this->markTestSkipped('Too old version of HttpFoundation.'); + } + + $this->getResponseTester(new Response('', 200, ['Content-Type' => 'application/vnd.myformat']))->assertResponseFormatSame('custom'); + $this->getResponseTester(new Response('', 200, ['Content-Type' => 'application/ld+json']))->assertResponseFormatSame('jsonld'); + $this->getResponseTester(new Response())->assertResponseFormatSame(null); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage("Failed asserting that the Response format is jsonld.\nHTTP/1.0 200 OK"); + $this->getResponseTester(new Response())->assertResponseFormatSame('jsonld'); + } + public function testAssertResponseHasHeader() { $this->getResponseTester(new Response())->assertResponseHasHeader('Date'); @@ -284,6 +299,10 @@ private function getResponseTester(Response $response): WebTestCase $client = $this->createMock(KernelBrowser::class); $client->expects($this->any())->method('getResponse')->willReturn($response); + $request = new Request(); + $request->setFormat('custom', ['application/vnd.myformat']); + $client->expects($this->any())->method('getRequest')->willReturn($request); + return $this->getTester($client); } diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 472fef05a57ae..b6b90cb855d0b 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * added `ResponseFormatSame` PHPUnit constraint + 5.2.0 ----- diff --git a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseFormatSame.php b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseFormatSame.php new file mode 100644 index 0000000000000..f73aedfa11688 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseFormatSame.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Asserts that the response is in the given format. + * + * @author Kévin Dunglas + */ +final class ResponseFormatSame extends Constraint +{ + private $request; + private $format; + + public function __construct(Request $request, ?string $format) + { + $this->request = $request; + $this->format = $format; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'format is '.($this->format ?? 'null'); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->format === $this->request->getFormat($response->headers->get('Content-Type')); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Test/Constraint/ResponseFormatSameTest.php b/src/Symfony/Component/HttpFoundation/Tests/Test/Constraint/ResponseFormatSameTest.php new file mode 100644 index 0000000000000..aed9285f24224 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Test/Constraint/ResponseFormatSameTest.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\HttpFoundation\Tests\Test\Constraint; + +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestFailure; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseFormatSame; + +/** + * @author Kévin Dunglas + */ +class ResponseFormatSameTest extends TestCase +{ + public function testConstraint() + { + $request = new Request(); + $request->setFormat('custom', ['application/vnd.myformat']); + + $constraint = new ResponseFormatSame($request, 'custom'); + $this->assertTrue($constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/vnd.myformat']), '', true)); + $this->assertFalse($constraint->evaluate(new Response(), '', true)); + + try { + $constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/ld+json'])); + } catch (ExpectationFailedException $e) { + $this->assertStringContainsString("Failed asserting that the Response format is custom.\nHTTP/1.0 200 OK", TestFailure::exceptionToString($e)); + + return; + } + + $this->fail(); + } + + public function testNullFormat() + { + $constraint = new ResponseFormatSame(new Request(), null); + $this->assertTrue($constraint->evaluate(new Response(), '', true)); + + try { + $constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/ld+json'])); + } catch (ExpectationFailedException $e) { + $this->assertStringContainsString("Failed asserting that the Response format is null.\nHTTP/1.0 200 OK", TestFailure::exceptionToString($e)); + + return; + } + + $this->fail(); + } +} From e970027682e27b2605996e6ae3c082dde2a8d828 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sun, 3 Jan 2021 17:04:20 +0100 Subject: [PATCH 070/188] minor: don't mute call to trigger_deprecation() --- src/Symfony/Component/DomCrawler/Crawler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 5dbd3b06da846..b310df7e9ef42 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -502,7 +502,7 @@ public function previousAll() */ public function parents() { - @trigger_deprecation('symfony/dom-crawler', '5.3', sprintf('The %s() method is deprecated, use ancestors() instead.', __METHOD__)); + trigger_deprecation('symfony/dom-crawler', '5.3', sprintf('The %s() method is deprecated, use ancestors() instead.', __METHOD__)); return $this->ancestors(); } From be22c37ea1c93a00e82de4c421082b6ec0d168aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sun, 3 Jan 2021 19:16:03 +0100 Subject: [PATCH 071/188] Create flock directory --- src/Symfony/Component/Lock/Store/FlockStore.php | 8 ++++++-- .../Component/Lock/Tests/Store/FlockStoreTest.php | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Lock/Store/FlockStore.php b/src/Symfony/Component/Lock/Store/FlockStore.php index 76e2b355c1854..0fd55c8456c06 100644 --- a/src/Symfony/Component/Lock/Store/FlockStore.php +++ b/src/Symfony/Component/Lock/Store/FlockStore.php @@ -42,8 +42,12 @@ public function __construct(string $lockPath = null) if (null === $lockPath) { $lockPath = sys_get_temp_dir(); } - if (!is_dir($lockPath) || !is_writable($lockPath)) { - throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $lockPath)); + if (!is_dir($lockPath)) { + if (false === @mkdir($lockPath, 0777, true) && !is_dir($lockPath)) { + throw new InvalidArgumentException(sprintf('The FlockStore directory "%s" does not exists and cannot be created.', $lockPath)); + } + } elseif (!is_writable($lockPath)) { + throw new InvalidArgumentException(sprintf('The FlockStore directory "%s" is not writable.', $lockPath)); } $this->lockPath = $lockPath; diff --git a/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php index d879c6ac256ff..dd6a34a0aab89 100644 --- a/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php @@ -32,10 +32,10 @@ protected function getStore(): PersistingStoreInterface return new FlockStore(); } - public function testConstructWhenRepositoryDoesNotExist() + public function testConstructWhenRepositoryCannotBeCreated() { $this->expectException('Symfony\Component\Lock\Exception\InvalidArgumentException'); - $this->expectExceptionMessage('The directory "/a/b/c/d/e" is not writable.'); + $this->expectExceptionMessage('The FlockStore directory "/a/b/c/d/e" does not exists and cannot be created.'); if (!getenv('USER') || 'root' === getenv('USER')) { $this->markTestSkipped('This test will fail if run under superuser'); } @@ -46,7 +46,7 @@ public function testConstructWhenRepositoryDoesNotExist() public function testConstructWhenRepositoryIsNotWriteable() { $this->expectException('Symfony\Component\Lock\Exception\InvalidArgumentException'); - $this->expectExceptionMessage('The directory "/" is not writable.'); + $this->expectExceptionMessage('The FlockStore directory "/" is not writable.'); if (!getenv('USER') || 'root' === getenv('USER')) { $this->markTestSkipped('This test will fail if run under superuser'); } @@ -54,6 +54,15 @@ public function testConstructWhenRepositoryIsNotWriteable() new FlockStore('/'); } + public function testConstructWithSubdir() + { + if (!getenv('USER') || 'root' === getenv('USER')) { + $this->markTestSkipped('This test will fail if run under superuser'); + } + + new FlockStore(sys_get_temp_dir().'/sf-flock'); + } + public function testSaveSanitizeName() { $store = $this->getStore(); From a174e6b4dead9b64637ffed31a5f9b9c14698a32 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 3 Jan 2021 13:46:28 +0100 Subject: [PATCH 072/188] [DoctrineBridge] Deprecate internal test helpers in Test namespace --- src/Symfony/Bridge/Doctrine/CHANGELOG.md | 5 ++++ .../Doctrine/Test/DoctrineTestHelper.php | 14 +++++++++++ .../Doctrine/Test/TestRepositoryFactory.php | 12 +++++++++- .../Doctrine/Tests/DoctrineTestHelper.php | 23 +++++++++++++++++++ .../ChoiceList/ORMQueryBuilderLoaderTest.php | 2 +- .../Form/Type/EntityTypePerformanceTest.php | 2 +- .../Tests/Form/Type/EntityTypeTest.php | 2 +- .../Security/User/EntityUserProviderTest.php | 2 +- .../Doctrine/Tests/TestRepositoryFactory.php | 21 +++++++++++++++++ .../Constraints/UniqueEntityValidatorTest.php | 4 ++-- .../Tests/Validator/DoctrineLoaderTest.php | 2 +- 11 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index ea6918de35f1c..a062227443411 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * deprecated `DoctrineTestHelper` and `TestRepositoryFactory` + 5.2.0 ----- diff --git a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php index 2ad16dcd4de24..2adb309812eea 100644 --- a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php +++ b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php @@ -25,6 +25,8 @@ * Provides utility functions needed in tests. * * @author Bernhard Schussek + * + * @deprecated in 5.3, will be removed in 6.0. */ class DoctrineTestHelper { @@ -39,6 +41,10 @@ public static function createTestEntityManager(Configuration $config = null) TestCase::markTestSkipped('Extension pdo_sqlite is required.'); } + if (__CLASS__ === static::class) { + trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); + } + if (null === $config) { $config = self::createTestConfiguration(); } @@ -56,6 +62,10 @@ public static function createTestEntityManager(Configuration $config = null) */ public static function createTestConfiguration() { + if (__CLASS__ === static::class) { + trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); + } + $config = new Configuration(); $config->setEntityNamespaces(['SymfonyTestsDoctrine' => 'Symfony\Bridge\Doctrine\Tests\Fixtures']); $config->setAutoGenerateProxyClasses(true); @@ -73,6 +83,10 @@ public static function createTestConfiguration() */ public static function createTestConfigurationWithXmlLoader() { + if (__CLASS__ === static::class) { + trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); + } + $config = static::createTestConfiguration(); $driverChain = new MappingDriverChain(); diff --git a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php index 6197c6ae5169c..ed63b6bd03bcb 100644 --- a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php +++ b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php @@ -18,8 +18,10 @@ /** * @author Andreas Braun + * + * @deprecated in 5.3, will be removed in 6.0. */ -final class TestRepositoryFactory implements RepositoryFactory +class TestRepositoryFactory implements RepositoryFactory { /** * @var ObjectRepository[] @@ -33,6 +35,10 @@ final class TestRepositoryFactory implements RepositoryFactory */ public function getRepository(EntityManagerInterface $entityManager, $entityName) { + if (__CLASS__ === static::class) { + trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); + } + $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); if (isset($this->repositoryList[$repositoryHash])) { @@ -44,6 +50,10 @@ public function getRepository(EntityManagerInterface $entityManager, $entityName public function setRepository(EntityManagerInterface $entityManager, string $entityName, ObjectRepository $repository) { + if (__CLASS__ === static::class) { + trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); + } + $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); $this->repositoryList[$repositoryHash] = $repository; diff --git a/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php new file mode 100644 index 0000000000000..21962088b0fc8 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.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\Bridge\Doctrine\Tests; + +use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper as TestDoctrineTestHelper; + +/** + * Provides utility functions needed in tests. + * + * @author Bernhard Schussek + */ +final class DoctrineTestHelper extends TestDoctrineTestHelper +{ +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index caebae184848f..fc03207e65d55 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -17,7 +17,7 @@ use Doctrine\ORM\Version; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Component\Form\Exception\TransformationFailedException; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index 33e6a6fd87235..5fbe7aa7a86dd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -14,7 +14,7 @@ use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Component\Form\Extension\Core\CoreExtension; use Symfony\Component\Form\Test\FormPerformanceTestCase; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index e32d4b0c6d386..9bcf4ee73b94a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -20,7 +20,7 @@ use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser; use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index 59793406b5d5a..0f10ba4a6dff4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -18,7 +18,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider; use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\User; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; diff --git a/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php new file mode 100644 index 0000000000000..4ed1b7fe157cc --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.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\Bridge\Doctrine\Tests; + +use Symfony\Bridge\Doctrine\Test\TestRepositoryFactory as TestTestRepositoryFactory; + +/** + * @author Andreas Braun + */ +final class TestRepositoryFactory extends TestTestRepositoryFactory +{ +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 7880ea92fede5..87e1719d1cd32 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -18,8 +18,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; -use Symfony\Bridge\Doctrine\Test\TestRepositoryFactory; +use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeObjectNoToStringIdEntity; @@ -31,6 +30,7 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdStringWrapperNameEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\Type\StringWrapper; +use Symfony\Bridge\Doctrine\Tests\TestRepositoryFactory; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index 31129a8c615d0..1ba0b6f254874 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -12,7 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Validator; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\BaseUser; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEmbed; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEntity; From 7c5cf579bb2f2a69a44394408ec30396a66d8ae9 Mon Sep 17 00:00:00 2001 From: Bernhard Rusch Date: Wed, 11 Nov 2020 09:35:05 +0100 Subject: [PATCH 073/188] [WebProfilerBundle] Possibility to dynamically set mode --- .../EventListener/WebDebugToolbarListener.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index e960d9b38bf40..1eb10072579b4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -60,6 +60,15 @@ public function isEnabled(): bool return self::DISABLED !== $this->mode; } + public function setMode(int $mode): void + { + if (self::DISABLED !== $mode && self::ENABLED !== $mode) { + throw new \InvalidArgumentException(sprintf('Invalid value provided for mode, use one of "%s::DISABLED" or "%s::ENABLED".', self::class, self::class)); + } + + $this->mode = $mode; + } + public function onKernelResponse(ResponseEvent $event) { $response = $event->getResponse(); From 1a9a3c34c9b759b43e264ac74b77b75e942df81f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Mon, 4 Jan 2021 09:45:40 +0100 Subject: [PATCH 074/188] Implement negatable option --- src/Symfony/Component/Console/Application.php | 2 +- src/Symfony/Component/Console/CHANGELOG.md | 1 + .../Console/Descriptor/JsonDescriptor.php | 22 +++- .../Console/Descriptor/MarkdownDescriptor.php | 9 +- .../Console/Descriptor/TextDescriptor.php | 17 +-- .../Console/Descriptor/XmlDescriptor.php | 16 ++- .../Component/Console/Input/ArgvInput.php | 27 ++++- .../Component/Console/Input/ArrayInput.php | 25 +++- src/Symfony/Component/Console/Input/Input.php | 30 ++--- .../Console/Input/InputDefinition.php | 55 ++++++--- .../Component/Console/Input/InputOption.php | 72 ++--------- .../Console/Input/NegatedInputOption.php | 112 ------------------ .../Console/Tests/ApplicationTest.php | 7 +- .../Console/Tests/Command/ListCommandTest.php | 3 +- .../Console/Tests/Fixtures/application_1.json | 8 +- .../Console/Tests/Fixtures/application_1.md | 42 ++++--- .../Console/Tests/Fixtures/application_1.txt | 3 +- .../Console/Tests/Fixtures/application_1.xml | 8 +- .../Console/Tests/Fixtures/application_2.json | 24 ++-- .../Console/Tests/Fixtures/application_2.md | 100 +++++++--------- .../Console/Tests/Fixtures/application_2.txt | 3 +- .../Console/Tests/Fixtures/application_2.xml | 24 ++-- .../application_filtered_namespace.txt | 3 +- .../Tests/Fixtures/application_mbstring.md | 62 +++++----- .../Tests/Fixtures/application_mbstring.txt | 3 +- .../Tests/Fixtures/application_run1.txt | 3 +- .../Tests/Fixtures/application_run2.txt | 3 +- .../Tests/Fixtures/application_run3.txt | 3 +- .../Tests/Fixtures/application_run5.txt | 3 +- .../Console/Tests/Fixtures/command_2.md | 1 + .../Tests/Fixtures/command_mbstring.md | 1 + .../Tests/Fixtures/input_definition_3.md | 1 + .../Tests/Fixtures/input_definition_4.md | 1 + .../Console/Tests/Fixtures/input_option_1.md | 1 + .../Console/Tests/Fixtures/input_option_2.md | 1 + .../Console/Tests/Fixtures/input_option_3.md | 1 + .../Console/Tests/Fixtures/input_option_4.md | 1 + .../Console/Tests/Fixtures/input_option_5.md | 1 + .../Console/Tests/Fixtures/input_option_6.md | 1 + .../input_option_with_default_inf_value.md | 1 + .../Tests/Fixtures/input_option_with_style.md | 1 + .../Fixtures/input_option_with_style_array.md | 1 + .../Console/Tests/Input/ArgvInputTest.php | 78 ++++++++++++ .../Console/Tests/Input/InputOptionTest.php | 22 ++++ .../Console/Tests/Input/InputTest.php | 1 + .../phpt/single_application/help_name.phpt | 3 +- 46 files changed, 399 insertions(+), 408 deletions(-) delete mode 100644 src/Symfony/Component/Console/Input/NegatedInputOption.php diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 3dc15b3c6bf7b..57c073e72e39d 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -1033,7 +1033,7 @@ protected function getDefaultInputDefinition() new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), - new InputOption('--ansi', '', InputOption::VALUE_BINARY, 'Force ANSI output', null), + new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), ]); } diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index bea122185e28b..ea52eb2c90818 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Added `GithubActionReporter` to render annotations in a Github Action + * Added `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options. 5.2.0 ----- diff --git a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php index 8b9c966b30c1b..f70fc38f6b92c 100644 --- a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php @@ -40,6 +40,9 @@ protected function describeInputArgument(InputArgument $argument, array $options protected function describeInputOption(InputOption $option, array $options = []) { $this->writeData($this->getInputOptionData($option), $options); + if ($option->isNegatable()) { + $this->writeData($this->getInputOptionData($option, true), $options); + } } /** @@ -111,15 +114,22 @@ private function getInputArgumentData(InputArgument $argument): array ]; } - private function getInputOptionData(InputOption $option): array + private function getInputOptionData(InputOption $option, bool $negated = false): array { - return [ + return $negated ? [ + 'name' => '--no-'.$option->getName(), + 'shortcut' => '', + 'accept_value' => false, + 'is_value_required' => false, + 'is_multiple' => false, + 'description' => 'Negate the "--'.$option->getName().'" option', + 'default' => false, + ] : [ 'name' => '--'.$option->getName(), 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), - 'is_negatable' => $option->isNegatable(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), ]; @@ -134,10 +144,10 @@ private function getInputDefinitionData(InputDefinition $definition): array $inputOptions = []; foreach ($definition->getOptions() as $name => $option) { - if ($option->isHidden()) { - continue; - } $inputOptions[$name] = $this->getInputOptionData($option); + if ($option->isNegatable()) { + $inputOptions['no-'.$name] = $this->getInputOptionData($option, true); + } } return ['arguments' => $inputArguments, 'options' => $inputOptions]; diff --git a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php index 42043ac1d82db..dddba12f884d4 100644 --- a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php @@ -68,8 +68,10 @@ protected function describeInputArgument(InputArgument $argument, array $options */ protected function describeInputOption(InputOption $option, array $options = []) { - $negatable = $option->isNegatable() ? '[no-]' : ''; - $name = '--'.$negatable.$option->getName(); + $name = '--'.$option->getName(); + if ($option->isNegatable()) { + $name .= '|--no-'.$option->getName(); + } if ($option->getShortcut()) { $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; } @@ -107,9 +109,6 @@ protected function describeInputDefinition(InputDefinition $definition, array $o $this->write('### Options'); foreach ($definition->getOptions() as $option) { - if ($option->isHidden()) { - continue; - } $this->write("\n\n"); if (null !== $describeInputOption = $this->describeInputOption($option)) { $this->write($describeInputOption); diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index 77a9a49e52413..8598b41016648 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -56,12 +56,10 @@ protected function describeInputArgument(InputArgument $argument, array $options */ protected function describeInputOption(InputOption $option, array $options = []) { - $default = ''; if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); - } elseif ($option->isNegatable() && (null !== $option->getDefault())) { - $negative_default = $option->getDefault() ? '' : 'no-'; - $default = sprintf(' [default: --%s%s]', $negative_default, $option->getName()); + } else { + $default = ''; } $value = ''; @@ -74,10 +72,9 @@ protected function describeInputOption(InputOption $option, array $options = []) } $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); - $negatable = $option->isNegatable() ? '[no-]' : ''; $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', - sprintf('--%s%s%s', $negatable, $option->getName(), $value) + sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value) ); $spacingWidth = $totalWidth - Helper::strlen($synopsis); @@ -120,9 +117,6 @@ protected function describeInputDefinition(InputDefinition $definition, array $o $this->writeText('Options:', $options); foreach ($definition->getOptions() as $option) { - if ($option->isHidden()) { - continue; - } if (\strlen($option->getShortcut()) > 1) { $laterOptions[] = $option; continue; @@ -331,8 +325,9 @@ private function calculateTotalWidthForOptions(array $options): int foreach ($options as $option) { // "-" + shortcut + ", --" + name $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); - - if ($option->acceptValue()) { + if ($option->isNegatable()) { + $nameLength += 6 + Helper::strlen($option->getName()); // |--no- + name + } elseif ($option->acceptValue()) { $valueLength = 1 + Helper::strlen($option->getName()); // = + value $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] diff --git a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php index 0ed8b24e65972..d9db0b8938fa0 100644 --- a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php @@ -38,9 +38,7 @@ public function getInputDefinitionDocument(InputDefinition $definition): \DOMDoc $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { - if (!$option->isHidden()) { - $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); - } + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); } return $dom; @@ -212,7 +210,6 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); - $objectXML->setAttribute('is_negatable', $option->isNegatable() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); @@ -228,6 +225,17 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument } } + if ($option->isNegatable()) { + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--no-'.$option->getName()); + $objectXML->setAttribute('shortcut', ''); + $objectXML->setAttribute('accept_value', 0); + $objectXML->setAttribute('is_value_required', 0); + $objectXML->setAttribute('is_multiple', 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option')); + } + return $dom; } } diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 8d8fee820acc7..9dd4de780362a 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -208,7 +208,21 @@ private function addShortOption(string $shortcut, $value) */ private function addLongOption(string $name, $value) { - $option = $this->getOptionDefinition($name); + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + if (null !== $value) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); if (null !== $value && !$option->acceptValue()) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); @@ -225,8 +239,15 @@ private function addLongOption(string $name, $value) } } - $name = $option->effectiveName(); - $value = $option->checkValue($value); + if (null === $value) { + if ($option->isValueRequired()) { + throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray() && !$option->isValueOptional()) { + $value = true; + } + } if ($option->isArray()) { $this->options[$name][] = $value; diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index 353f6185ead47..2473f806e5647 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -164,7 +164,30 @@ private function addShortOption(string $shortcut, $value) */ private function addLongOption(string $name, $value) { - $this->setOption($name, $value); + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isValueOptional()) { + $value = true; + } + } + + $this->options[$name] = $value; } /** diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php index d57a0f7d4e033..ff5d3170f4e04 100644 --- a/src/Symfony/Component/Console/Input/Input.php +++ b/src/Symfony/Component/Console/Input/Input.php @@ -146,8 +146,11 @@ public function getOptions() */ public function getOption(string $name) { - $option = $this->getOptionDefinition($name); - return \array_key_exists($name, $this->options) ? $this->options[$name] : $option->getDefault(); + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } /** @@ -155,8 +158,11 @@ public function getOption(string $name) */ public function setOption(string $name, $value) { - $option = $this->getOptionDefinition($name); - $this->options[$option->effectiveName()] = $option->checkValue($value); + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; } /** @@ -192,20 +198,4 @@ public function getStream() { return $this->stream; } - - /** - * Look up the option definition for the given option name. - * - * @param string $name - * - * @return InputOption - */ - protected function getOptionDefinition($name) - { - if (!$this->definition->hasOption($name)) { - throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); - } - - return $this->definition->getOption($name); - } } diff --git a/src/Symfony/Component/Console/Input/InputDefinition.php b/src/Symfony/Component/Console/Input/InputDefinition.php index 0a22f2570dcce..56ae7185f8079 100644 --- a/src/Symfony/Component/Console/Input/InputDefinition.php +++ b/src/Symfony/Component/Console/Input/InputDefinition.php @@ -33,6 +33,7 @@ class InputDefinition private $hasAnArrayArgument = false; private $hasOptional; private $options; + private $negations; private $shortcuts; /** @@ -208,6 +209,7 @@ public function setOptions(array $options = []) { $this->options = []; $this->shortcuts = []; + $this->negations = []; $this->addOptions($options); } @@ -227,19 +229,6 @@ public function addOptions(array $options = []) * @throws LogicException When option given already exist */ public function addOption(InputOption $option) - { - $this->doAddOption($option); - - if ($option->isNegatable()) { - $negatedOption = new NegatedInputOption($option); - $this->doAddOption($negatedOption); - } - } - - /** - * @throws LogicException When option given already exist - */ - private function doAddOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); @@ -259,6 +248,14 @@ private function doAddOption(InputOption $option) $this->shortcuts[$shortcut] = $option->getName(); } } + + if ($option->isNegatable()) { + $negatedName = 'no-'.$option->getName(); + if (isset($this->options[$negatedName])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName)); + } + $this->negations[$negatedName] = $option->getName(); + } } /** @@ -310,6 +307,14 @@ public function hasShortcut(string $name) return isset($this->shortcuts[$name]); } + /** + * Returns true if an InputOption object exists by negated name. + */ + public function hasNegation(string $name): bool + { + return isset($this->negations[$name]); + } + /** * Gets an InputOption by shortcut. * @@ -329,7 +334,7 @@ public function getOptionDefaults() { $values = []; foreach ($this->options as $option) { - $values[$option->effectiveName()] = $option->getDefault(); + $values[$option->getName()] = $option->getDefault(); } return $values; @@ -351,6 +356,22 @@ public function shortcutToName(string $shortcut): string return $this->shortcuts[$shortcut]; } + /** + * Returns the InputOption name given a negation. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function negationToName(string $negation): string + { + if (!isset($this->negations[$negation])) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation)); + } + + return $this->negations[$negation]; + } + /** * Gets the synopsis. * @@ -364,9 +385,6 @@ public function getSynopsis(bool $short = false) $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { - if ($option->isHidden()) { - continue; - } $value = ''; if ($option->acceptValue()) { $value = sprintf( @@ -378,7 +396,8 @@ public function getSynopsis(bool $short = false) } $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; - $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + $negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : ''; + $elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation); } } diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index 941d1f7726e75..08be6eac9477b 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Exception\LogicException; /** @@ -27,8 +26,6 @@ class InputOption public const VALUE_OPTIONAL = 4; public const VALUE_IS_ARRAY = 8; public const VALUE_NEGATABLE = 16; - public const VALUE_HIDDEN = 32; - public const VALUE_BINARY = (self::VALUE_NONE | self::VALUE_NEGATABLE); private $name; private $shortcut; @@ -74,7 +71,7 @@ public function __construct(string $name, $shortcut = null, int $mode = null, st if (null === $mode) { $mode = self::VALUE_NONE; - } elseif ($mode >= (self::VALUE_HIDDEN << 1) || $mode < 1) { + } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } @@ -86,6 +83,9 @@ public function __construct(string $name, $shortcut = null, int $mode = null, st 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.'); } + if ($this->isNegatable() && $this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); + } $this->setDefault($default); } @@ -110,11 +110,6 @@ public function getName() return $this->name; } - public function effectiveName() - { - return $this->getName(); - } - /** * Returns true if the option accepts a value. * @@ -155,39 +150,11 @@ public function isArray() return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } - /** - * Returns true if the option is negatable (option --foo can be forced - * to 'false' via the --no-foo option). - * - * @return bool true if mode is self::VALUE_NEGATABLE, false otherwise - */ - public function isNegatable() + public function isNegatable(): bool { return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); } - /** - * Returns true if the option should not be shown in help (e.g. a negated - * option). - * - * @return bool true if mode is self::VALUE_HIDDEN, false otherwise - */ - public function isHidden() - { - return self::VALUE_HIDDEN === (self::VALUE_HIDDEN & $this->mode); - } - - /** - * Returns true if the option is binary (can be --foo or --no-foo, and - * nothing else). - * - * @return bool true if negatable and does not have a value. - */ - public function isBinary() - { - return $this->isNegatable() && !$this->acceptValue(); - } - /** * Sets the default value. * @@ -197,9 +164,12 @@ public function isBinary() */ public function setDefault($default = null) { - if (self::VALUE_NONE === ((self::VALUE_NONE | self::VALUE_NEGATABLE) & $this->mode) && null !== $default) { + 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.'); } + if (self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode) && null !== $default) { + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NEGATABLE mode.'); + } if ($this->isArray()) { if (null === $default) { @@ -209,7 +179,7 @@ public function setDefault($default = null) } } - $this->default = ($this->acceptValue() || $this->isNegatable()) ? $default : false; + $this->default = $this->acceptValue() ? $default : false; } /** @@ -232,27 +202,6 @@ public function getDescription() return $this->description; } - /** - * Checks the validity of a value, and alters it as necessary - * - * @param mixed $value - * - * @return @mixed - */ - public function checkValue($value) - { - if (null === $value) { - if ($this->isValueRequired()) { - throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $this->getName())); - } - - if (!$this->isValueOptional()) { - return true; - } - } - return $value; - } - /** * Checks whether the given option equals this one. * @@ -263,7 +212,6 @@ public function equals(self $option) return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() - && $option->isHidden() === $this->isHidden() && $option->isNegatable() === $this->isNegatable() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() diff --git a/src/Symfony/Component/Console/Input/NegatedInputOption.php b/src/Symfony/Component/Console/Input/NegatedInputOption.php deleted file mode 100644 index 07a4bb6d09482..0000000000000 --- a/src/Symfony/Component/Console/Input/NegatedInputOption.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; - -/** - * Represents a command line option. - * - * @author Fabien Potencier - */ -class NegatedInputOption extends InputOption -{ - private $primaryOption; - - const VALUE_NONE = 1; - const VALUE_REQUIRED = 2; - const VALUE_OPTIONAL = 4; - const VALUE_IS_ARRAY = 8; - const VALUE_NEGATABLE = 16; - const VALUE_HIDDEN = 32; - const VALUE_BINARY = (self::VALUE_NONE | self::VALUE_NEGATABLE); - - private $name; - private $shortcut; - private $mode; - private $default; - private $description; - - /** - * @param string $name The option name - * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int $mode The option mode: One of the VALUE_* constants - * @param string $description A description text - * @param mixed $default The default value (must be null for self::VALUE_NONE) - * - * @throws InvalidArgumentException If option mode is invalid or incompatible - */ - public function __construct(InputOption $primaryOption) - { - $this->primaryOption = $primaryOption; - - /* string $name, $shortcut = null, int $mode = null, string $description = '', $default = null */ - $name = 'no-' . $primaryOption->getName(); - $shortcut = null; - $mode = self::VALUE_HIDDEN; - $description = $primaryOption->getDescription(); - $default = $this->negate($primaryOption->getDefault()); - - parent::__construct($name, $shortcut, $mode, $description, $default); - } - - public function effectiveName() - { - return $this->primaryOption->getName(); - } - - /** - * Sets the default value. - * - * @param mixed $default The default value - * - * @throws LogicException When incorrect default value is given - */ - public function setDefault($default = null) - { - $this->primaryOption->setDefault($this->negate($default)); - } - - /** - * Returns the default value. - * - * @return mixed The default value - */ - public function getDefault() - { - return $this->negate($this->primaryOption->getDefault()); - } - - /** - * @inheritdoc - */ - public function checkValue($value) - { - return false; - } - - /** - * Negate a value if it is provided. - * - * @param mixed $value - * - * @return mixed - */ - private function negate($value) - { - if ($value === null) { - return $value; - } - return !$value; - } -} diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 4141920965ee8..f115f140ecac0 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -1257,7 +1257,8 @@ public function testGetDefaultInputDefinitionReturnsDefaultValues() $this->assertTrue($inputDefinition->hasOption('verbose')); $this->assertTrue($inputDefinition->hasOption('version')); $this->assertTrue($inputDefinition->hasOption('ansi')); - $this->assertTrue($inputDefinition->hasOption('no-ansi')); + $this->assertTrue($inputDefinition->hasNegation('no-ansi')); + $this->assertFalse($inputDefinition->hasOption('no-ansi')); $this->assertTrue($inputDefinition->hasOption('no-interaction')); } @@ -1277,7 +1278,7 @@ public function testOverwritingDefaultInputDefinitionOverwritesDefaultValues() $this->assertFalse($inputDefinition->hasOption('verbose')); $this->assertFalse($inputDefinition->hasOption('version')); $this->assertFalse($inputDefinition->hasOption('ansi')); - $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasNegation('no-ansi')); $this->assertFalse($inputDefinition->hasOption('no-interaction')); $this->assertTrue($inputDefinition->hasOption('custom')); @@ -1301,7 +1302,7 @@ public function testSettingCustomInputDefinitionOverwritesDefaultValues() $this->assertFalse($inputDefinition->hasOption('verbose')); $this->assertFalse($inputDefinition->hasOption('version')); $this->assertFalse($inputDefinition->hasOption('ansi')); - $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasNegation('no-ansi')); $this->assertFalse($inputDefinition->hasOption('no-interaction')); $this->assertTrue($inputDefinition->hasOption('custom')); diff --git a/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php b/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php index a5d6653ad8186..a7c113565e311 100644 --- a/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php @@ -80,8 +80,7 @@ public function testExecuteListsCommandsOrder() -h, --help Display help for the given command. When no command is given display help for the list command -q, --quiet Do not output any message -V, --version Display this application version - --ansi Force ANSI output - --no-ansi Disable ANSI output + --ansi|--no-ansi Force (or disable --no-ansi) ANSI output -n, --no-interaction Do not ask any interactive question -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_1.json b/src/Symfony/Component/Console/Tests/Fixtures/application_1.json index bb1eab28ed4c1..9e9cf1b52c3a0 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_1.json +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_1.json @@ -79,7 +79,7 @@ "accept_value": false, "is_value_required": false, "is_multiple": false, - "description": "Force ANSI output", + "description": "Force (or disable --no-ansi) ANSI output", "default": false }, "no-ansi": { @@ -88,7 +88,7 @@ "accept_value": false, "is_value_required": false, "is_multiple": false, - "description": "Disable ANSI output", + "description": "Negate the \"--ansi\" option", "default": false }, "no-interaction": { @@ -182,7 +182,7 @@ "accept_value": false, "is_value_required": false, "is_multiple": false, - "description": "Force ANSI output", + "description": "Force (or disable --no-ansi) ANSI output", "default": false }, "no-ansi": { @@ -191,7 +191,7 @@ "accept_value": false, "is_value_required": false, "is_multiple": false, - "description": "Disable ANSI output", + "description": "Negate the \"--ansi\" option", "default": false }, "no-interaction": { diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_1.md b/src/Symfony/Component/Console/Tests/Fixtures/application_1.md index 38c84e4e232b9..990f750f5b04a 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_1.md +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_1.md @@ -42,6 +42,7 @@ The output format (txt, xml, json, or md) * Accept value: yes * Is value required: yes * Is multiple: no +* Is negatable: no * Default: `'txt'` #### `--raw` @@ -51,6 +52,7 @@ To output raw command help * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--help|-h` @@ -60,6 +62,7 @@ Display help for the given command. When no command is given display help for th * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--quiet|-q` @@ -69,6 +72,7 @@ Do not output any message * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--verbose|-v|-vv|-vvv` @@ -78,6 +82,7 @@ Increase the verbosity of messages: 1 for normal output, 2 for more verbose outp * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--version|-V` @@ -87,24 +92,17 @@ Display this application version * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` -#### `--ansi` +#### `--ansi|--no-ansi` -Force ANSI output - -* Accept value: no -* Is value required: no -* Is multiple: no -* Default: `false` - -#### `--no-ansi` - -Disable ANSI output +Force (or disable --no-ansi) ANSI output * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: yes * Default: `false` #### `--no-interaction|-n` @@ -114,6 +112,7 @@ Do not ask any interactive question * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` `list` @@ -160,6 +159,7 @@ To output raw command list * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--format` @@ -169,6 +169,7 @@ The output format (txt, xml, json, or md) * Accept value: yes * Is value required: yes * Is multiple: no +* Is negatable: no * Default: `'txt'` #### `--help|-h` @@ -178,6 +179,7 @@ Display help for the given command. When no command is given display help for th * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--quiet|-q` @@ -187,6 +189,7 @@ Do not output any message * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--verbose|-v|-vv|-vvv` @@ -196,6 +199,7 @@ Increase the verbosity of messages: 1 for normal output, 2 for more verbose outp * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` #### `--version|-V` @@ -205,24 +209,17 @@ Display this application version * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` -#### `--ansi` - -Force ANSI output - -* Accept value: no -* Is value required: no -* Is multiple: no -* Default: `false` - -#### `--no-ansi` +#### `--ansi|--no-ansi` -Disable ANSI output +Force (or disable --no-ansi) ANSI output * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: yes * Default: `false` #### `--no-interaction|-n` @@ -232,4 +229,5 @@ Do not ask any interactive question * Accept value: no * Is value required: no * Is multiple: no +* Is negatable: no * Default: `false` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_1.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_1.txt index cc55447241368..47b6524904031 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_1.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_1.txt @@ -7,8 +7,7 @@ Console Tool -h, --help Display help for the given command. When no command is given display help for the list command -q, --quiet Do not output any message -V, --version Display this application version - --ansi Force ANSI output - --no-ansi Disable ANSI output + --ansi|--no-ansi Force (or disable --no-ansi) ANSI output -n, --no-interaction Do not ask any interactive question -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml b/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml index 5b6a906c5a2a2..313b0d1cc3b5d 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml @@ -46,10 +46,10 @@ Display this application version + diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_2.json b/src/Symfony/Component/Console/Tests/Fixtures/application_2.json index 4b6d85c2bcfe5..4969191142f31 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_2.json +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_2.json @@ -111,7 +111,7 @@ "name": "list", "hidden": false, "usage": [ - "list [--raw] [--format FORMAT] [--] []" + "list [--raw] [--format FORMAT] [--short] [--] []" ], "description": "Lists commands", "help": "The list<\/info> command lists all commands:\n\n app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n app\/console list test<\/info>\n\nYou can also output the information in other formats by using the --format<\/comment> option:\n\n app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n app\/console list --raw<\/info>", @@ -206,6 +206,15 @@ "is_multiple": false, "description": "Do not ask any interactive question", "default": false + }, + "short": { + "name": "--short", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To skip describing commands' arguments", + "default": false } } } diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_2.md b/src/Symfony/Component/Console/Tests/Fixtures/application_2.md index 5d453df997cb5..eb23fa80c9315 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_2.md +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_2.md @@ -135,7 +135,7 @@ Lists commands ### Usage -* `list [--raw] [--format FORMAT] [--] []` +* `list [--raw] [--format FORMAT] [--short] [--] []` The list command lists all commands: @@ -185,6 +185,16 @@ The output format (txt, xml, json, or md) * Is negatable: no * Default: `'txt'` +#### `--short` + +To skip describing commands' arguments + +* Accept value: no +* Is value required: no +* Is multiple: no +* Is negatable: no +* Default: `false` + #### `--help|-h` Display help for the given command. When no command is given display help for the list command diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_2.xml b/src/Symfony/Component/Console/Tests/Fixtures/application_2.xml index e745ddd0d637e..0a523a6565c9d 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_2.xml +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_2.xml @@ -58,7 +58,7 @@ command hashs passwords according to your +security configuration. This command is mainly used to generate passwords for +the in_memory user provider type and for changing passwords +in the database while developing the application. + +Suppose that you have the following security configuration in your application: + + +# app/config/security.yml +security: + password_hashers: + Symfony\Component\Security\Core\User\User: plaintext + App\Entity\User: auto + + +If you execute the command non-interactively, the first available configured +user class under the security.password_hashers key is used and a random salt is +generated to hash the password: + + php %command.full_name% --no-interaction [password] + +Pass the full user class path as the second argument to hash passwords for +your own entities: + + php %command.full_name% --no-interaction [password] 'App\Entity\User' + +Executing the command interactively allows you to generate a random salt for +hashing the password: + + php %command.full_name% [password] 'App\Entity\User' + +In case your hasher doesn't require a salt, add the empty-salt option: + + php %command.full_name% --empty-salt [password] 'App\Entity\User' + +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io; + + $input->isInteractive() ? $errorIo->title('Symfony Password Hash Utility') : $errorIo->newLine(); + + $password = $input->getArgument('password'); + $userClass = $this->getUserClass($input, $io); + $emptySalt = $input->getOption('empty-salt'); + + $hasher = $this->hasherFactory->getPasswordHasher($userClass); + $saltlessWithoutEmptySalt = !$emptySalt && !$hasher instanceof LegacyPasswordHasherInterface; + + if ($saltlessWithoutEmptySalt) { + $emptySalt = true; + } + + if (!$password) { + if (!$input->isInteractive()) { + $errorIo->error('The password must not be empty.'); + + return 1; + } + $passwordQuestion = $this->createPasswordQuestion(); + $password = $errorIo->askQuestion($passwordQuestion); + } + + $salt = null; + + if ($input->isInteractive() && !$emptySalt) { + $emptySalt = true; + + $errorIo->note('The command will take care of generating a salt for you. Be aware that some hashers advise to let them generate their own salt. If you\'re using one of those hashers, please answer \'no\' to the question below. '.\PHP_EOL.'Provide the \'empty-salt\' option in order to let the hasher handle the generation itself.'); + + if ($errorIo->confirm('Confirm salt generation ?')) { + $salt = $this->generateSalt(); + $emptySalt = false; + } + } elseif (!$emptySalt) { + $salt = $this->generateSalt(); + } + + $hashedPassword = $hasher->hash($password, $salt); + + $rows = [ + ['Hasher used', \get_class($hasher)], + ['Password hash', $hashedPassword], + ]; + if (!$emptySalt) { + $rows[] = ['Generated salt', $salt]; + } + $io->table(['Key', 'Value'], $rows); + + if (!$emptySalt) { + $errorIo->note(sprintf('Make sure that your salt storage field fits the salt length: %s chars', \strlen($salt))); + } elseif ($saltlessWithoutEmptySalt) { + $errorIo->note('Self-salting hasher used: the hasher generated its own built-in salt.'); + } + + $errorIo->success('Password hashing succeeded'); + + return 0; + } + + /** + * Create the password question to ask the user for the password to be hashed. + */ + private function createPasswordQuestion(): Question + { + $passwordQuestion = new Question('Type in your password to be hashed'); + + return $passwordQuestion->setValidator(function ($value) { + if ('' === trim($value)) { + throw new InvalidArgumentException('The password must not be empty.'); + } + + return $value; + })->setHidden(true)->setMaxAttempts(20); + } + + private function generateSalt(): string + { + return base64_encode(random_bytes(30)); + } + + private function getUserClass(InputInterface $input, SymfonyStyle $io): string + { + if (null !== $userClass = $input->getArgument('user-class')) { + return $userClass; + } + + if (!$this->userClasses) { + throw new RuntimeException('There are no configured password hashers for the "security" extension.'); + } + + if (!$input->isInteractive() || 1 === \count($this->userClasses)) { + return reset($this->userClasses); + } + + $userClasses = $this->userClasses; + natcasesort($userClasses); + $userClasses = array_values($userClasses); + + return $io->choice('For which user class would you like to hash a password?', $userClasses, reset($userClasses)); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Exception/ExceptionInterface.php b/src/Symfony/Component/PasswordHasher/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000..2d80d8a78f8ee --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Exception/ExceptionInterface.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\PasswordHasher\Exception; + +/** + * Interface for exceptions thrown by the password-hasher component. + * + * @author Robin Chalas + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php b/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php new file mode 100644 index 0000000000000..dea9109baeb48 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.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\PasswordHasher\Exception; + +/** + * @author Robin Chalas +*/ +class InvalidPasswordException extends \RuntimeException implements ExceptionInterface +{ + public function __construct(string $message = 'Invalid password.', int $code = 0, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Exception/LogicException.php b/src/Symfony/Component/PasswordHasher/Exception/LogicException.php new file mode 100644 index 0000000000000..a0c425fa6fa57 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Exception; + +/** + * @author Robin Chalas +*/ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/CheckPasswordLengthTrait.php b/src/Symfony/Component/PasswordHasher/Hasher/CheckPasswordLengthTrait.php new file mode 100644 index 0000000000000..2dce065ff8191 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/CheckPasswordLengthTrait.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\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * @author Robin Chalas + */ +trait CheckPasswordLengthTrait +{ + private function isPasswordTooLong(string $password): bool + { + return PasswordHasherInterface::MAX_PASSWORD_LENGTH < \strlen($password); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/MessageDigestPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/MessageDigestPasswordHasher.php new file mode 100644 index 0000000000000..0dd18b276bdde --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/MessageDigestPasswordHasher.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\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * MessageDigestPasswordHasher uses a message digest algorithm. + * + * @author Fabien Potencier + */ +class MessageDigestPasswordHasher implements LegacyPasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private $algorithm; + private $encodeHashAsBase64; + private $iterations = 1; + private $hashLength = -1; + + /** + * @param string $algorithm The digest algorithm to use + * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash + * @param int $iterations The number of iterations to use to stretch the password hash + */ + public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 5000) + { + $this->algorithm = $algorithm; + $this->encodeHashAsBase64 = $encodeHashAsBase64; + + try { + $this->hashLength = \strlen($this->hash('', 'salt')); + } catch (\LogicException $e) { + // ignore algorithm not supported + } + + $this->iterations = $iterations; + } + + public function hash(string $plainPassword, ?string $salt = null): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + if (!\in_array($this->algorithm, hash_algos(), true)) { + throw new LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); + } + + $salted = $this->mergePasswordAndSalt($plainPassword, $salt); + $digest = hash($this->algorithm, $salted, true); + + // "stretch" hash + for ($i = 1; $i < $this->iterations; ++$i) { + $digest = hash($this->algorithm, $digest.$salted, true); + } + + return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); + } + + public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool + { + if (\strlen($hashedPassword) !== $this->hashLength || false !== strpos($hashedPassword, '$')) { + return false; + } + + return !$this->isPasswordTooLong($plainPassword) && hash_equals($hashedPassword, $this->hash($plainPassword, $salt)); + } + + public function needsRehash(string $hashedPassword): bool + { + return false; + } + + private function mergePasswordAndSalt(string $password, ?string $salt): string + { + if (!$salt) { + return $password; + } + + if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) { + throw new \InvalidArgumentException('Cannot use { or } in salt.'); + } + + return $password.'{'.$salt.'}'; + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php new file mode 100644 index 0000000000000..f48373c6e9693 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.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\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * Hashes passwords using the best available hasher. + * Verifies them using a chain of hashers. + * + * /!\ Don't put a PlaintextPasswordHasher in the list as that'd mean a leaked hash + * could be used to authenticate successfully without knowing the cleartext password. + * + * @author Nicolas Grekas + */ +final class MigratingPasswordHasher implements PasswordHasherInterface +{ + private $bestHasher; + private $extraHashers; + + public function __construct(PasswordHasherInterface $bestHasher, PasswordHasherInterface ...$extraHashers) + { + $this->bestHasher = $bestHasher; + $this->extraHashers = $extraHashers; + } + + public function hash(string $plainPassword, ?string $salt = null): string + { + return $this->bestHasher->hash($plainPassword, $salt); + } + + public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool + { + if ($this->bestHasher->verify($hashedPassword, $plainPassword, $salt)) { + return true; + } + + if (!$this->bestHasher->needsRehash($hashedPassword)) { + return false; + } + + foreach ($this->extraHashers as $hasher) { + if ($hasher->verify($hashedPassword, $plainPassword, $salt)) { + return true; + } + } + + return false; + } + + public function needsRehash(string $hashedPassword): bool + { + return $this->bestHasher->needsRehash($hashedPassword); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php new file mode 100644 index 0000000000000..f147868ad8dfc --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.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\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * Hashes passwords using password_hash(). + * + * @author Elnur Abdurrakhimov + * @author Terje Bråten + * @author Nicolas Grekas + */ +final class NativePasswordHasher implements PasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private $algo = \PASSWORD_BCRYPT; + private $options; + + /** + * @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm + */ + public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, ?string $algo = null) + { + $cost = $cost ?? 13; + $opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); + $memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); + + if (3 > $opsLimit) { + throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); + } + + if (10 * 1024 > $memLimit) { + throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); + } + + if ($cost < 4 || 31 < $cost) { + throw new \InvalidArgumentException('$cost must be in the range of 4-31.'); + } + + $algos = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; + + if (\defined('PASSWORD_ARGON2I')) { + $this->algo = $algos[2] = $algos['argon2i'] = (string) \PASSWORD_ARGON2I; + } + + if (\defined('PASSWORD_ARGON2ID')) { + $this->algo = $algos[3] = $algos['argon2id'] = (string) \PASSWORD_ARGON2ID; + } + + if (null !== $algo) { + $this->algo = $algos[$algo] ?? $algo; + } + + $this->options = [ + 'cost' => $cost, + 'time_cost' => $opsLimit, + 'memory_cost' => $memLimit >> 10, + 'threads' => 1, + ]; + } + + public function hash(string $plainPassword): string + { + if ($this->isPasswordTooLong($plainPassword) || ((string) \PASSWORD_BCRYPT === $this->algo && 72 < \strlen($plainPassword))) { + throw new InvalidPasswordException(); + } + + return password_hash($plainPassword, $this->algo, $this->options); + } + + public function verify(string $hashedPassword, string $plainPassword): bool + { + if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) { + return false; + } + + if (0 !== strpos($hashedPassword, '$argon')) { + // BCrypt encodes only the first 72 chars + return (72 >= \strlen($plainPassword) || 0 !== strpos($hashedPassword, '$2')) && password_verify($plainPassword, $hashedPassword); + } + + if (\extension_loaded('sodium') && version_compare(\SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) { + return sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + if (\extension_loaded('libsodium') && version_compare(phpversion('libsodium'), '1.0.14', '>=')) { + return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + return password_verify($plainPassword, $hashedPassword); + } + + /** + * {@inheritdoc} + */ + public function needsRehash(string $hashedPassword): bool + { + return password_needs_rehash($hashedPassword, $this->algo, $this->options); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherAwareInterface.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherAwareInterface.php new file mode 100644 index 0000000000000..58046bc56c60c --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherAwareInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +/** + * @author Christophe Coevoet + */ +interface PasswordHasherAwareInterface +{ + /** + * Gets the name of the password hasher used to hash the password. + * + * If the method returns null, the standard way to retrieve the password hasher + * will be used instead. + */ + public function getPasswordHasherName(): ?string; +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php new file mode 100644 index 0000000000000..03bbe7350c2cf --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * A generic hasher factory implementation. + * + * @author Nicolas Grekas + * @author Robin Chalas + */ +class PasswordHasherFactory implements PasswordHasherFactoryInterface +{ + private $passwordHashers; + + public function __construct(array $passwordHashers) + { + $this->passwordHashers = $passwordHashers; + } + + /** + * {@inheritdoc} + */ + public function getPasswordHasher($user): PasswordHasherInterface + { + $hasherKey = null; + + if (($user instanceof PasswordHasherAwareInterface && null !== $hasherName = $user->getPasswordHasherName()) || ($user instanceof EncoderAwareInterface && null !== $hasherName = $user->getEncoderName())) { + if (!\array_key_exists($hasherName, $this->passwordHashers)) { + throw new \RuntimeException(sprintf('The password hasher "%s" was not configured.', $hasherName)); + } + + $hasherKey = $hasherName; + } else { + foreach ($this->passwordHashers as $class => $hasher) { + if ((\is_object($user) && $user instanceof $class) || (!\is_object($user) && (is_subclass_of($user, $class) || $user == $class))) { + $hasherKey = $class; + break; + } + } + } + + if (null === $hasherKey) { + throw new \RuntimeException(sprintf('No password hasher has been configured for account "%s".', \is_object($user) ? get_debug_type($user) : $user)); + } + + if (!$this->passwordHashers[$hasherKey] instanceof PasswordHasherInterface) { + $this->passwordHashers[$hasherKey] = $this->createHasher($this->passwordHashers[$hasherKey]); + } + + return $this->passwordHashers[$hasherKey]; + } + + /** + * Creates the actual hasher instance. + * + * @throws \InvalidArgumentException + */ + private function createHasher(array $config, bool $isExtra = false): PasswordHasherInterface + { + if (isset($config['algorithm'])) { + $rawConfig = $config; + $config = $this->getHasherConfigFromAlgorithm($config); + } + if (!isset($config['class'])) { + throw new \InvalidArgumentException('"class" must be set in '.json_encode($config)); + } + if (!isset($config['arguments'])) { + throw new \InvalidArgumentException('"arguments" must be set in '.json_encode($config)); + } + + $hasher = new $config['class'](...$config['arguments']); + + if ($isExtra || !\in_array($config['class'], [NativePasswordHasher::class, SodiumPasswordHasher::class], true)) { + return $hasher; + } + + if ($rawConfig ?? null) { + $extrapasswordHashers = array_map(function (string $algo) use ($rawConfig): PasswordHasherInterface { + $rawConfig['algorithm'] = $algo; + + return $this->createHasher($rawConfig); + }, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']); + } else { + $extrapasswordHashers = [new Pbkdf2PasswordHasher(), new MessageDigestPasswordHasher()]; + } + + return new MigratingPasswordHasher($hasher, ...$extrapasswordHashers); + } + + private function getHasherConfigFromAlgorithm(array $config): array + { + if ('auto' === $config['algorithm']) { + $hasherChain = []; + // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly + foreach ([SodiumPasswordHasher::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm']] as $algo) { + $config['algorithm'] = $algo; + $hasherChain[] = $this->createHasher($config, true); + } + + return [ + 'class' => MigratingPasswordHasher::class, + 'arguments' => $hasherChain, + ]; + } + + if ($frompasswordHashers = ($config['migrate_from'] ?? false)) { + unset($config['migrate_from']); + $hasherChain = [$this->createHasher($config, true)]; + + foreach ($frompasswordHashers as $name) { + if ($hasher = $this->passwordHashers[$name] ?? false) { + $hasher = $hasher instanceof PasswordHasherInterface ? $hasher : $this->createHasher($hasher, true); + } else { + $hasher = $this->createHasher(['algorithm' => $name], true); + } + + $hasherChain[] = $hasher; + } + + return [ + 'class' => MigratingPasswordHasher::class, + 'arguments' => $hasherChain, + ]; + } + + switch ($config['algorithm']) { + case 'plaintext': + return [ + 'class' => PlaintextPasswordHasher::class, + 'arguments' => [$config['ignore_case'] ?? false], + ]; + + case 'pbkdf2': + return [ + 'class' => Pbkdf2PasswordHasher::class, + 'arguments' => [ + $config['hash_algorithm'] ?? 'sha512', + $config['encode_as_base64'] ?? true, + $config['iterations'] ?? 1000, + $config['key_length'] ?? 40, + ], + ]; + + case 'bcrypt': + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_BCRYPT; + + return $this->getHasherConfigFromAlgorithm($config); + + case 'native': + return [ + 'class' => NativePasswordHasher::class, + 'arguments' => [ + $config['time_cost'] ?? null, + (($config['memory_cost'] ?? 0) << 10) ?: null, + $config['cost'] ?? null, + ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []), + ]; + + case 'sodium': + return [ + 'class' => SodiumPasswordHasher::class, + 'arguments' => [ + $config['time_cost'] ?? null, + (($config['memory_cost'] ?? 0) << 10) ?: null, + ], + ]; + + case 'argon2i': + if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + $config['algorithm'] = 'sodium'; + } elseif (\defined('PASSWORD_ARGON2I')) { + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_ARGON2I; + } else { + throw new LogicException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : '')); + } + + return $this->getHasherConfigFromAlgorithm($config); + + case 'argon2id': + if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + $config['algorithm'] = 'sodium'; + } elseif (\defined('PASSWORD_ARGON2ID')) { + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_ARGON2ID; + } else { + throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : '')); + } + + return $this->getHasherConfigFromAlgorithm($config); + } + + return [ + 'class' => MessageDigestPasswordHasher::class, + 'arguments' => [ + $config['algorithm'], + $config['encode_as_base64'] ?? true, + $config['iterations'] ?? 5000, + ], + ]; + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactoryInterface.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactoryInterface.php new file mode 100644 index 0000000000000..943a4003da5e8 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactoryInterface.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\PasswordHasher\Hasher; + +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * PasswordHasherFactoryInterface to support different password hashers for different user accounts. + * + * @author Robin Chalas + * @author Johannes M. Schmitt + */ +interface PasswordHasherFactoryInterface +{ + /** + * Returns the password hasher to use for the given user. + * + * @param UserInterface|string $user A UserInterface instance or a class name + * + * @throws \RuntimeException When no password hasher could be found for the user + */ + public function getPasswordHasher($user): PasswordHasherInterface; +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php new file mode 100644 index 0000000000000..dd2e742db79fe --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * Pbkdf2PasswordHasher uses the PBKDF2 (Password-Based Key Derivation Function 2). + * + * Providing a high level of Cryptographic security, + * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST). + * + * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process. + * PBKDF2 should be used with caution and care. + * + * @author Sebastiaan Stok + * @author Andrew Johnson + * @author Fabien Potencier + */ +final class Pbkdf2PasswordHasher implements LegacyPasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private $algorithm; + private $encodeHashAsBase64; + private $iterations = 1; + private $length; + private $encodedLength = -1; + + /** + * @param string $algorithm The digest algorithm to use + * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash + * @param int $iterations The number of iterations to use to stretch the password hash + * @param int $length Length of derived key to create + */ + public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 1000, int $length = 40) + { + $this->algorithm = $algorithm; + $this->encodeHashAsBase64 = $encodeHashAsBase64; + $this->length = $length; + + try { + $this->encodedLength = \strlen($this->hash('', 'salt')); + } catch (\LogicException $e) { + // ignore unsupported algorithm + } + + $this->iterations = $iterations; + } + + public function hash(string $plainPassword, ?string $salt = null): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + if (!\in_array($this->algorithm, hash_algos(), true)) { + throw new LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); + } + + $digest = hash_pbkdf2($this->algorithm, $plainPassword, $salt, $this->iterations, $this->length, true); + + return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); + } + + public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool + { + if (\strlen($hashedPassword) !== $this->encodedLength || false !== strpos($hashedPassword, '$')) { + return false; + } + + return !$this->isPasswordTooLong($plainPassword) && hash_equals($hashedPassword, $this->hash($plainPassword, $salt)); + } + + public function needsRehash(string $hashedPassword): bool + { + return false; + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PlaintextPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/PlaintextPasswordHasher.php new file mode 100644 index 0000000000000..bafe6bce898c7 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/PlaintextPasswordHasher.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\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * PlaintextPasswordHasher does not do any hashing but is useful in testing environments. + * + * As this hasher is not cryptographically secure, usage of it in production environments is discouraged. + * + * @author Fabien Potencier + */ +class PlaintextPasswordHasher implements LegacyPasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private $ignorePasswordCase; + + /** + * @param bool $ignorePasswordCase Compare password case-insensitive + */ + public function __construct(bool $ignorePasswordCase = false) + { + $this->ignorePasswordCase = $ignorePasswordCase; + } + + /** + * {@inheritdoc} + */ + public function hash(string $plainPassword, ?string $salt = null): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + return $this->mergePasswordAndSalt($plainPassword, $salt); + } + + public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool + { + if ($this->isPasswordTooLong($plainPassword)) { + return false; + } + + $pass2 = $this->mergePasswordAndSalt($plainPassword, $salt); + + if (!$this->ignorePasswordCase) { + return hash_equals($hashedPassword, $pass2); + } + + return hash_equals(strtolower($hashedPassword), strtolower($pass2)); + } + + public function needsRehash(string $hashedPassword): bool + { + return false; + } + + private function mergePasswordAndSalt(string $password, ?string $salt): string + { + if (empty($salt)) { + return $password; + } + + if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) { + throw new \InvalidArgumentException('Cannot use { or } in salt.'); + } + + return $password.'{'.$salt.'}'; + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php new file mode 100644 index 0000000000000..613cccd03763d --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * Hashes passwords using libsodium. + * + * @author Robin Chalas + * @author Zan Baldwin + * @author Dominik Müller + */ +final class SodiumPasswordHasher implements PasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private $opsLimit; + private $memLimit; + + public function __construct(int $opsLimit = null, int $memLimit = null) + { + if (!self::isSupported()) { + throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); + } + + $this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); + $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); + + if (3 > $this->opsLimit) { + throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); + } + + if (10 * 1024 > $this->memLimit) { + throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); + } + } + + public static function isSupported(): bool + { + return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>='); + } + + public function hash(string $plainPassword): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + if (\function_exists('sodium_crypto_pwhash_str')) { + return sodium_crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit); + } + + if (\extension_loaded('libsodium')) { + return \Sodium\crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit); + } + + throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); + } + + public function verify(string $hashedPassword, string $plainPassword): bool + { + if ('' === $plainPassword) { + return false; + } + + if ($this->isPasswordTooLong($plainPassword)) { + return false; + } + + if (0 !== strpos($hashedPassword, '$argon')) { + // Accept validating non-argon passwords for seamless migrations + return (72 >= \strlen($plainPassword) || 0 !== strpos($hashedPassword, '$2')) && password_verify($plainPassword, $hashedPassword); + } + + if (\function_exists('sodium_crypto_pwhash_str_verify')) { + return sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + if (\extension_loaded('libsodium')) { + return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + return false; + } + + public function needsRehash(string $hashedPassword): bool + { + if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) { + return sodium_crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit); + } + + if (\extension_loaded('libsodium')) { + return \Sodium\crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit); + } + + throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/UserPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/UserPasswordHasher.php new file mode 100644 index 0000000000000..bb11680665003 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/UserPasswordHasher.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\PasswordHasher\Hasher; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Hashes passwords based on the user and the PasswordHasherFactory. + * + * @author Ariel Ferrandini + */ +class UserPasswordHasher implements UserPasswordHasherInterface +{ + private $hasherFactory; + + public function __construct(PasswordHasherFactoryInterface $hasherFactory) + { + $this->hasherFactory = $hasherFactory; + } + + public function hashPassword(UserInterface $user, string $plainPassword): string + { + $hasher = $this->hasherFactory->getPasswordHasher($user); + + return $hasher->hash($plainPassword, $user->getSalt()); + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid(UserInterface $user, string $plainPassword): bool + { + if (null === $user->getPassword()) { + return false; + } + + $hasher = $this->hasherFactory->getPasswordHasher($user); + + return $hasher->verify($user->getPassword(), $plainPassword, $user->getSalt()); + } + + /** + * {@inheritdoc} + */ + public function needsRehash(UserInterface $user): bool + { + if (null === $user->getPassword()) { + return false; + } + + $hasher = $this->hasherFactory->getPasswordHasher($user); + + return $hasher->needsRehash($user->getPassword()); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Hasher/UserPasswordHasherInterface.php b/src/Symfony/Component/PasswordHasher/Hasher/UserPasswordHasherInterface.php new file mode 100644 index 0000000000000..13a5b51a8a119 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Hasher/UserPasswordHasherInterface.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\PasswordHasher\Hasher; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Interface for the user password hasher service. + * + * @author Ariel Ferrandini + */ +interface UserPasswordHasherInterface +{ + /** + * Hashes the plain password for the given user. + */ + public function hashPassword(UserInterface $user, string $plainPassword): string; + + /** + * Checks if the plaintext password matches the user's password. + */ + public function isPasswordValid(UserInterface $user, string $plainPassword): bool; + + /** + * Checks if a password hash would benefit from rehashing. + */ + public function needsRehash(UserInterface $user): bool; +} diff --git a/src/Symfony/Component/PasswordHasher/LICENSE b/src/Symfony/Component/PasswordHasher/LICENSE new file mode 100644 index 0000000000000..9ff2d0d6306da --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2021 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.php b/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.php new file mode 100644 index 0000000000000..3faf96d2f4d27 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.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\PasswordHasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; + +/** + * Provides password hashing and verification capabilities for "legacy" hashers that require external salts. + * + * @author Fabien Potencier + * @author Nicolas Grekas + * @author Robin Chalas + */ +interface LegacyPasswordHasherInterface extends PasswordHasherInterface +{ + /** + * Hashes a plain password. + * + * @return string The hashed password + * + * @throws InvalidPasswordException If the plain password is invalid, e.g. excessively long + */ + public function hash(string $plainPassword, ?string $salt = null): string; + + /** + * Checks that a plain password and a salt match a password hash. + */ + public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool; +} diff --git a/src/Symfony/Component/PasswordHasher/PasswordHasherInterface.php b/src/Symfony/Component/PasswordHasher/PasswordHasherInterface.php new file mode 100644 index 0000000000000..6b3575783891f --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/PasswordHasherInterface.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\PasswordHasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; + +/** + * Provides password hashing capabilities. + * + * @author Robin Chalas + * @author Fabien Potencier + * @author Nicolas Grekas + */ +interface PasswordHasherInterface +{ + public const MAX_PASSWORD_LENGTH = 4096; + + /** + * Hashes a plain password. + * + * @throws InvalidPasswordException When the plain password is invalid, e.g. excessively long + */ + public function hash(string $plainPassword): string; + + /** + * Verifies a plain password against a hash. + */ + public function verify(string $hashedPassword, string $plainPassword): bool; + + /** + * Checks if a password hash would benefit from rehashing. + */ + public function needsRehash(string $hashedPassword): bool; +} diff --git a/src/Symfony/Component/PasswordHasher/README.md b/src/Symfony/Component/PasswordHasher/README.md new file mode 100644 index 0000000000000..6a54ecb3355bf --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/README.md @@ -0,0 +1,40 @@ +PasswordHasher Component +======================== + +The PasswordHasher component provides secure password hashing utilities. + +Getting Started +--------------- + +``` +$ composer require symfony/password-hasher +``` + +```php +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; + +// Configure different password hashers via the factory +$factory = new PasswordHasherFactory([ + 'common' => ['algorithm' => 'bcrypt'], + 'memory-hard' => ['algorithm' => 'sodium'], +]); + +// Retrieve the right password hasher by its name +$passwordHasher = $factory->getPasswordHasher('common'); + +// Hash a plain password +$hash = $passwordHasher->hash('plain'); // returns a bcrypt hash + +// Verify that a given plain password matches the hash +$passwordHasher->verify($hash, 'wrong'); // returns false +$passwordHasher->verify($hash, 'plain'); // returns true (valid) +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/password-hasher.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [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) diff --git a/src/Symfony/Component/PasswordHasher/Tests/Command/UserPasswordHashCommandTest.php b/src/Symfony/Component/PasswordHasher/Tests/Command/UserPasswordHashCommandTest.php new file mode 100644 index 0000000000000..03cca7acd9ec6 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Command/UserPasswordHashCommandTest.php @@ -0,0 +1,362 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\Command\UserPasswordHasherCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; + +class UserPasswordHashCommandTest extends TestCase +{ + /** @var CommandTester */ + private $passwordHasherCommandTester; + + public function testEncodePasswordEmptySalt() + { + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Symfony\Component\Security\Core\User\User', + '--empty-salt' => true, + ], ['decorated' => false]); + + $this->assertStringContainsString(' Password hash password', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testEncodeNoPasswordNoInteraction() + { + $statusCode = $this->passwordHasherCommandTester->execute([ + ], ['interactive' => false]); + + $this->assertStringContainsString('[ERROR] The password must not be empty.', $this->passwordHasherCommandTester->getDisplay()); + $this->assertEquals(1, $statusCode); + } + + public function testEncodePasswordBcrypt() + { + $this->setupBcrypt(); + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Custom\Class\Bcrypt\User', + ], ['interactive' => false]); + + $output = $this->passwordHasherCommandTester->getDisplay(); + $this->assertStringContainsString('Password hashing succeeded', $output); + + $hasher = new NativePasswordHasher(null, null, 17, \PASSWORD_BCRYPT); + preg_match('# Password hash\s{1,}([\w+\/$.]+={0,2})\s+#', $output, $matches); + $hash = $matches[1]; + $this->assertTrue($hasher->verify($hash, 'password', null)); + } + + public function testEncodePasswordArgon2i() + { + if (!($sodium = SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) { + $this->markTestSkipped('Argon2i algorithm not available.'); + } + $this->setupArgon2i(); + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Custom\Class\Argon2i\User', + ], ['interactive' => false]); + + $output = $this->passwordHasherCommandTester->getDisplay(); + $this->assertStringContainsString('Password hashing succeeded', $output); + + $hasher = $sodium ? new SodiumPasswordHasher() : new NativePasswordHasher(null, null, null, \PASSWORD_ARGON2I); + preg_match('# Password hash\s+(\$argon2i?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches); + $hash = $matches[1]; + $this->assertTrue($hasher->verify($hash, 'password', null)); + } + + public function testEncodePasswordArgon2id() + { + if (!($sodium = (SodiumPasswordHasher::isSupported() && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13'))) && !\defined('PASSWORD_ARGON2ID')) { + $this->markTestSkipped('Argon2id algorithm not available.'); + } + $this->setupArgon2id(); + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Custom\Class\Argon2id\User', + ], ['interactive' => false]); + + $output = $this->passwordHasherCommandTester->getDisplay(); + $this->assertStringContainsString('Password hashing succeeded', $output); + + $hasher = $sodium ? new SodiumPasswordHasher() : new NativePasswordHasher(null, null, null, \PASSWORD_ARGON2ID); + preg_match('# Password hash\s+(\$argon2id?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches); + $hash = $matches[1]; + $this->assertTrue($hasher->verify($hash, 'password', null)); + } + + public function testEncodePasswordNative() + { + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Custom\Class\Native\User', + ], ['interactive' => false]); + + $output = $this->passwordHasherCommandTester->getDisplay(); + $this->assertStringContainsString('Password hashing succeeded', $output); + + $hasher = new NativePasswordHasher(); + preg_match('# Password hash\s{1,}([\w+\/$.,=]+={0,2})\s+#', $output, $matches); + $hash = $matches[1]; + $this->assertTrue($hasher->verify($hash, 'password', null)); + } + + public function testEncodePasswordSodium() + { + if (!SodiumPasswordHasher::isSupported()) { + $this->markTestSkipped('Libsodium is not available.'); + } + $this->setupSodium(); + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Custom\Class\Sodium\User', + ], ['interactive' => false]); + + $output = $this->passwordHasherCommandTester->getDisplay(); + $this->assertStringContainsString('Password hashing succeeded', $output); + + preg_match('# Password hash\s+(\$?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches); + $hash = $matches[1]; + $this->assertTrue((new SodiumPasswordHasher())->verify($hash, 'password', null)); + } + + public function testEncodePasswordPbkdf2() + { + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Custom\Class\Pbkdf2\User', + ], ['interactive' => false]); + + $output = $this->passwordHasherCommandTester->getDisplay(); + $this->assertStringContainsString('Password hashing succeeded', $output); + + $hasher = new Pbkdf2PasswordHasher('sha512', true, 1000); + preg_match('# Password hash\s{1,}([\w+\/]+={0,2})\s+#', $output, $matches); + $hash = $matches[1]; + preg_match('# Generated salt\s{1,}([\w+\/]+={0,2})\s+#', $output, $matches); + $salt = $matches[1]; + $this->assertTrue($hasher->verify($hash, 'password', $salt)); + } + + public function testEncodePasswordOutput() + { + $this->passwordHasherCommandTester->execute( + [ + 'password' => 'p@ssw0rd', + ], ['interactive' => false] + ); + + $this->assertStringContainsString('Password hashing succeeded', $this->passwordHasherCommandTester->getDisplay()); + $this->assertStringContainsString(' Password hash p@ssw0rd', $this->passwordHasherCommandTester->getDisplay()); + $this->assertStringContainsString(' Generated salt ', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testEncodePasswordEmptySaltOutput() + { + $this->passwordHasherCommandTester->execute([ + 'password' => 'p@ssw0rd', + 'user-class' => 'Symfony\Component\Security\Core\User\User', + '--empty-salt' => true, + ]); + + $this->assertStringContainsString('Password hashing succeeded', $this->passwordHasherCommandTester->getDisplay()); + $this->assertStringContainsString(' Password hash p@ssw0rd', $this->passwordHasherCommandTester->getDisplay()); + $this->assertStringNotContainsString(' Generated salt ', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testEncodePasswordNativeOutput() + { + $this->passwordHasherCommandTester->execute([ + 'password' => 'p@ssw0rd', + 'user-class' => 'Custom\Class\Native\User', + ], ['interactive' => false]); + + $this->assertStringNotContainsString(' Generated salt ', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testEncodePasswordArgon2iOutput() + { + if (!(SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) { + $this->markTestSkipped('Argon2i algorithm not available.'); + } + + $this->setupArgon2i(); + $this->passwordHasherCommandTester->execute([ + 'password' => 'p@ssw0rd', + 'user-class' => 'Custom\Class\Argon2i\User', + ], ['interactive' => false]); + + $this->assertStringNotContainsString(' Generated salt ', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testEncodePasswordArgon2idOutput() + { + if (!(SodiumPasswordHasher::isSupported() && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2ID')) { + $this->markTestSkipped('Argon2id algorithm not available.'); + } + + $this->setupArgon2id(); + $this->passwordHasherCommandTester->execute([ + 'password' => 'p@ssw0rd', + 'user-class' => 'Custom\Class\Argon2id\User', + ], ['interactive' => false]); + + $this->assertStringNotContainsString(' Generated salt ', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testEncodePasswordSodiumOutput() + { + if (!SodiumPasswordHasher::isSupported()) { + $this->markTestSkipped('Libsodium is not available.'); + } + + $this->setupSodium(); + $this->passwordHasherCommandTester->execute([ + 'password' => 'p@ssw0rd', + 'user-class' => 'Custom\Class\Sodium\User', + ], ['interactive' => false]); + + $this->assertStringNotContainsString(' Generated salt ', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testEncodePasswordNoConfigForGivenUserClass() + { + $this->expectException('\RuntimeException'); + $this->expectExceptionMessage('No password hasher has been configured for account "Foo\Bar\User".'); + + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + 'user-class' => 'Foo\Bar\User', + ], ['interactive' => false]); + } + + public function testEncodePasswordAsksNonProvidedUserClass() + { + $this->passwordHasherCommandTester->setInputs(['Custom\Class\Pbkdf2\User', "\n"]); + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + ], ['decorated' => false]); + + $this->assertStringContainsString(<<passwordHasherCommandTester->getDisplay(true)); + } + + public function testNonInteractiveEncodePasswordUsesFirstUserClass() + { + $this->passwordHasherCommandTester->execute([ + 'password' => 'password', + ], ['interactive' => false]); + + $this->assertStringContainsString('Hasher used Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher', $this->passwordHasherCommandTester->getDisplay()); + } + + public function testThrowsExceptionOnNoConfiguredHashers() + { + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('There are no configured password hashers for the "security" extension.'); + + $tester = new CommandTester(new UserPasswordHashCommand($this->getMockBuilder(PasswordHasherFactoryInterface::class)->getMock(), [])); + $tester->execute([ + 'password' => 'password', + ], ['interactive' => false]); + } + + protected function setUp(): void + { + putenv('COLUMNS='.(119 + \strlen(\PHP_EOL))); + $hasherFactory = new PasswordHasherFactory([ + User::class => ['algorithm' => 'plaintext'], + 'Custom\Class\Native\User' => ['algorithm' => 'native', 'cost' => 10], + 'Custom\Class\Pbkdf2\User' => ['algorithm' => 'pbkdf2', 'hash_algorithm' => 'sha512', 'iterations' => 1000, 'encode_as_base64' => true], + 'Custom\Class\Test\User' => ['algorithm' => 'test'], + ]); + + $this->passwordHasherCommandTester = new CommandTester(new UserPasswordHashCommand( + $hasherFactory, + [User::class, 'Custom\Class\Native\User', 'Custom\Class\Pbkdf2\User', 'Custom\Class\Test\User'] + )); + } + + protected function tearDown(): void + { + $this->passwordHasherCommandTester = null; + } + + private function setupArgon2i() + { + putenv('COLUMNS='.(119 + \strlen(\PHP_EOL))); + + $hasherFactory = new PasswordHasherFactory([ + 'Custom\Class\Argon2i\User' => ['algorithm' => 'argon2i'], + ]); + + $this->passwordHasherCommandTester = new CommandTester( + new UserPasswordHashCommand($hasherFactory, ['Custom\Class\Argon2i\User']) + ); + } + + private function setupArgon2id() + { + putenv('COLUMNS='.(119 + \strlen(\PHP_EOL))); + + $hasherFactory = new PasswordHasherFactory([ + 'Custom\Class\Argon2id\User' => ['algorithm' => 'argon2id'], + ]); + + $this->passwordHasherCommandTester = new CommandTester( + new UserPasswordHashCommand($hasherFactory, ['Custom\Class\Argon2id\User']) + ); + } + + private function setupBcrypt() + { + putenv('COLUMNS='.(119 + \strlen(\PHP_EOL))); + + $hasherFactory = new PasswordHasherFactory([ + 'Custom\Class\Bcrypt\User' => ['algorithm' => 'bcrypt'], + ]); + + $this->passwordHasherCommandTester = new CommandTester(new UserPasswordHashCommand( + $hasherFactory, + [User::class, 'Custom\Class\Pbkdf2\User', 'Custom\Class\Test\User'] + )); + } + + private function setupSodium() + { + putenv('COLUMNS='.(119 + \strlen(\PHP_EOL))); + + $hasherFactory = new PasswordHasherFactory([ + 'Custom\Class\Sodium\User' => ['algorithm' => 'sodium'], + ]); + + $this->passwordHasherCommandTester = new CommandTester( + new UserPasswordHashCommand($hasherFactory, ['Custom\Class\Sodium\User']) + ); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php new file mode 100644 index 0000000000000..a6c66aee213ec --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; + +class MessageDigestPasswordHasherTest extends TestCase +{ + public function testVerify() + { + $hasher = new MessageDigestPasswordHasher('sha256', false, 1); + + $this->assertTrue($hasher->verify(hash('sha256', 'password'), 'password', '')); + } + + public function testHash() + { + $hasher = new MessageDigestPasswordHasher('sha256', false, 1); + $this->assertSame(hash('sha256', 'password'), $hasher->hash('password', '')); + + $hasher = new MessageDigestPasswordHasher('sha256', true, 1); + $this->assertSame(base64_encode(hash('sha256', 'password', true)), $hasher->hash('password', '')); + + $hasher = new MessageDigestPasswordHasher('sha256', false, 2); + $this->assertSame(hash('sha256', hash('sha256', 'password', true).'password'), $hasher->hash('password', '')); + } + + public function testHashAlgorithmDoesNotExist() + { + $this->expectException('LogicException'); + $hasher = new MessageDigestPasswordHasher('foobar'); + $hasher->hash('password', ''); + } + + public function testHashLength() + { + $this->expectException(InvalidPasswordException::class); + $hasher = new MessageDigestPasswordHasher(); + + $hasher->hash(str_repeat('a', 5000), 'salt'); + } + + public function testCheckPasswordLength() + { + $hasher = new MessageDigestPasswordHasher(); + + $this->assertFalse($hasher->verify('encoded', str_repeat('a', 5000), 'salt')); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/MigratingPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MigratingPasswordHasherTest.php new file mode 100644 index 0000000000000..145a5cc34e33d --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MigratingPasswordHasherTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +class MigratingPasswordHasherTest extends TestCase +{ + public function testValidation() + { + $bestHasher = new NativePasswordHasher(4, 12000, 4); + + $extraHasher = $this->createMock(PasswordHasherInterface::class); + $extraHasher->expects($this->never())->method('hash'); + $extraHasher->expects($this->never())->method('verify'); + $extraHasher->expects($this->never())->method('needsRehash'); + + $hasher = new MigratingPasswordHasher($bestHasher, $extraHasher); + + $this->assertTrue($hasher->needsRehash('foo')); + + $hash = $hasher->hash('foo', 'salt'); + $this->assertFalse($hasher->needsRehash($hash)); + + $this->assertTrue($hasher->verify($hash, 'foo', 'salt')); + $this->assertFalse($hasher->verify($hash, 'bar', 'salt')); + } + + public function testFallback() + { + $bestHasher = new NativePasswordHasher(4, 12000, 4); + + $extraHasher1 = $this->createMock(PasswordHasherInterface::class); + $extraHasher1->expects($this->any()) + ->method('verify') + ->with('abc', 'foo', 'salt') + ->willReturn(true); + + $hasher = new MigratingPasswordHasher($bestHasher, $extraHasher1); + + $this->assertTrue($hasher->verify('abc', 'foo', 'salt')); + + $extraHasher2 = $this->createMock(PasswordHasherInterface::class); + $extraHasher2->expects($this->any()) + ->method('verify') + ->willReturn(false); + + $hasher = new MigratingPasswordHasher($bestHasher, $extraHasher2); + + $this->assertFalse($hasher->verify('abc', 'foo', 'salt')); + + $hasher = new MigratingPasswordHasher($bestHasher, $extraHasher2, $extraHasher1); + + $this->assertTrue($hasher->verify('abc', 'foo', 'salt')); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php new file mode 100644 index 0000000000000..90f48267ce36f --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; + +/** + * @author Elnur Abdurrakhimov + */ +class NativePasswordHasherTest extends TestCase +{ + public function testCostBelowRange() + { + $this->expectException('InvalidArgumentException'); + new NativePasswordHasher(null, null, 3); + } + + public function testCostAboveRange() + { + $this->expectException('InvalidArgumentException'); + new NativePasswordHasher(null, null, 32); + } + + /** + * @dataProvider validRangeData + */ + public function testCostInRange($cost) + { + $this->assertInstanceOf(NativePasswordHasher::class, new NativePasswordHasher(null, null, $cost)); + } + + public function validRangeData() + { + $costs = range(4, 31); + array_walk($costs, function (&$cost) { $cost = [$cost]; }); + + return $costs; + } + + public function testValidation() + { + $hasher = new NativePasswordHasher(); + $result = $hasher->hash('password', null); + $this->assertTrue($hasher->verify($result, 'password', null)); + $this->assertFalse($hasher->verify($result, 'anotherPassword', null)); + $this->assertFalse($hasher->verify($result, '', null)); + } + + public function testNonArgonValidation() + { + $hasher = new NativePasswordHasher(); + $this->assertTrue($hasher->verify('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'password', null)); + $this->assertFalse($hasher->verify('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'anotherPassword', null)); + $this->assertTrue($hasher->verify('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'password', null)); + $this->assertFalse($hasher->verify('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'anotherPassword', null)); + } + + public function testConfiguredAlgorithm() + { + $hasher = new NativePasswordHasher(null, null, null, \PASSWORD_BCRYPT); + $result = $hasher->hash('password', null); + $this->assertTrue($hasher->verify($result, 'password', null)); + $this->assertStringStartsWith('$2', $result); + } + + public function testConfiguredAlgorithmWithLegacyConstValue() + { + $hasher = new NativePasswordHasher(null, null, null, '1'); + $result = $hasher->hash('password', null); + $this->assertTrue($hasher->verify($result, 'password', null)); + $this->assertStringStartsWith('$2', $result); + } + + public function testCheckPasswordLength() + { + $hasher = new NativePasswordHasher(null, null, 4); + $result = password_hash(str_repeat('a', 72), \PASSWORD_BCRYPT, ['cost' => 4]); + + $this->assertFalse($hasher->verify($result, str_repeat('a', 73), 'salt')); + $this->assertTrue($hasher->verify($result, str_repeat('a', 72), 'salt')); + } + + public function testNeedsRehash() + { + $hasher = new NativePasswordHasher(4, 11000, 4); + + $this->assertTrue($hasher->needsRehash('dummyhash')); + + $hash = $hasher->hash('foo', 'salt'); + $this->assertFalse($hasher->needsRehash($hash)); + + $hasher = new NativePasswordHasher(5, 11000, 5); + $this->assertTrue($hasher->needsRehash($hash)); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php new file mode 100644 index 0000000000000..6c5205e37465f --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; +use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\Security\Core\User\UserInterface; + +class PasswordHasherFactoryTest extends TestCase +{ + public function testGetHasherWithMessageDigestHasher() + { + $factory = new PasswordHasherFactory([UserInterface::class => [ + 'class' => MessageDigestPasswordHasher::class, + 'arguments' => ['sha512', true, 5], + ]]); + + $hasher = $factory->getPasswordHasher($this->createMock(UserInterface::class)); + $expectedHasher = new MessageDigestPasswordHasher('sha512', true, 5); + + $this->assertEquals($expectedHasher->hash('foo', 'moo'), $hasher->hash('foo', 'moo')); + } + + public function testGetHasherWithService() + { + $factory = new PasswordHasherFactory([ + UserInterface::class => new MessageDigestPasswordHasher('sha1'), + ]); + + $hasher = $factory->getPasswordHasher($this->createMock(UserInterface::class)); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + + $hasher = $factory->getPasswordHasher(new User('user', 'pass')); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testGetHasherWithClassName() + { + $factory = new PasswordHasherFactory([ + UserInterface::class => new MessageDigestPasswordHasher('sha1'), + ]); + + $hasher = $factory->getPasswordHasher(SomeChildUser::class); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testGetHasherConfiguredForConcreteClassWithService() + { + $factory = new PasswordHasherFactory([ + 'Symfony\Component\Security\Core\User\User' => new MessageDigestPasswordHasher('sha1'), + ]); + + $hasher = $factory->getPasswordHasher(new User('user', 'pass')); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testGetHasherConfiguredForConcreteClassWithClassName() + { + $factory = new PasswordHasherFactory([ + 'Symfony\Component\PasswordHasher\Tests\Hasher\SomeUser' => new MessageDigestPasswordHasher('sha1'), + ]); + + $hasher = $factory->getPasswordHasher(SomeChildUser::class); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testGetNamedHasherForHasherAware() + { + $factory = new PasswordHasherFactory([ + HasherAwareUser::class => new MessageDigestPasswordHasher('sha256'), + 'hasher_name' => new MessageDigestPasswordHasher('sha1'), + ]); + + $hasher = $factory->getPasswordHasher(new HasherAwareUser('user', 'pass')); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testGetNullNamedHasherForHasherAware() + { + $factory = new PasswordHasherFactory([ + HasherAwareUser::class => new MessageDigestPasswordHasher('sha1'), + 'hasher_name' => new MessageDigestPasswordHasher('sha256'), + ]); + + $user = new HasherAwareUser('mathilde', 'krogulec'); + $user->hasherName = null; + $hasher = $factory->getPasswordHasher($user); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testGetInvalidNamedHasherForHasherAware() + { + $this->expectException('RuntimeException'); + $factory = new PasswordHasherFactory([ + HasherAwareUser::class => new MessageDigestPasswordHasher('sha1'), + 'hasher_name' => new MessageDigestPasswordHasher('sha256'), + ]); + + $user = new HasherAwareUser('user', 'pass'); + $user->hasherName = 'invalid_hasher_name'; + $factory->getPasswordHasher($user); + } + + public function testGetHasherForHasherAwareWithClassName() + { + $factory = new PasswordHasherFactory([ + HasherAwareUser::class => new MessageDigestPasswordHasher('sha1'), + 'hasher_name' => new MessageDigestPasswordHasher('sha256'), + ]); + + $hasher = $factory->getPasswordHasher(HasherAwareUser::class); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testMigrateFrom() + { + if (!SodiumPasswordHasher::isSupported()) { + $this->markTestSkipped('Sodium is not available'); + } + + $factory = new PasswordHasherFactory([ + 'digest_hasher' => $digest = new MessageDigestPasswordHasher('sha256'), + SomeUser::class => ['algorithm' => 'sodium', 'migrate_from' => ['bcrypt', 'digest_hasher']], + ]); + + $hasher = $factory->getPasswordHasher(SomeUser::class); + $this->assertInstanceOf(MigratingPasswordHasher::class, $hasher); + + $this->assertTrue($hasher->verify((new SodiumPasswordHasher())->hash('foo', null), 'foo', null)); + $this->assertTrue($hasher->verify((new NativePasswordHasher(null, null, null, \PASSWORD_BCRYPT))->hash('foo', null), 'foo', null)); + $this->assertTrue($hasher->verify($digest->hash('foo', null), 'foo', null)); + $this->assertStringStartsWith(\SODIUM_CRYPTO_PWHASH_STRPREFIX, $hasher->hash('foo', null)); + } + + public function testDefaultMigratingHashers() + { + $this->assertInstanceOf( + MigratingPasswordHasher::class, + (new PasswordHasherFactory([SomeUser::class => ['class' => NativePasswordHasher::class, 'arguments' => []]]))->getPasswordHasher(SomeUser::class) + ); + + $this->assertInstanceOf( + MigratingPasswordHasher::class, + (new PasswordHasherFactory([SomeUser::class => ['algorithm' => 'bcrypt', 'cost' => 11]]))->getPasswordHasher(SomeUser::class) + ); + + if (!SodiumPasswordHasher::isSupported()) { + return; + } + + $this->assertInstanceOf( + MigratingPasswordHasher::class, + (new PasswordHasherFactory([SomeUser::class => ['class' => SodiumPasswordHasher::class, 'arguments' => []]]))->getPasswordHasher(SomeUser::class) + ); + } +} + +class SomeUser implements UserInterface +{ + public function getRoles(): array + { + } + + public function getPassword(): ?string + { + } + + public function getSalt(): ?string + { + } + + public function getUsername(): string + { + } + + public function eraseCredentials() + { + } +} + +class SomeChildUser extends SomeUser +{ +} + +class HasherAwareUser extends SomeUser implements PasswordHasherAwareInterface +{ + public $hasherName = 'hasher_name'; + + public function getPasswordHasherName(): ?string + { + return $this->hasherName; + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/Pbkdf2PasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/Pbkdf2PasswordHasherTest.php new file mode 100644 index 0000000000000..50f9c8d13e864 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/Pbkdf2PasswordHasherTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; + +class Pbkdf2PasswordHasherTest extends TestCase +{ + public function testVerify() + { + $hasher = new Pbkdf2PasswordHasher('sha256', false, 1, 40); + + $this->assertTrue($hasher->verify('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', 'password', '')); + } + + public function testHash() + { + $hasher = new Pbkdf2PasswordHasher('sha256', false, 1, 40); + $this->assertSame('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', $hasher->hash('password', '')); + + $hasher = new Pbkdf2PasswordHasher('sha256', true, 1, 40); + $this->assertSame('wSMvEPYnFf2gaufAogN8oZszzxA7cnulbYcMEfKQoqsQaXTHVgfIow==', $hasher->hash('password', '')); + + $hasher = new Pbkdf2PasswordHasher('sha256', false, 2, 40); + $this->assertSame('8bc2f9167a81cdcfad1235cd9047f1136271c1f978fcfcb35e22dbeafa4634f6fd2214218ed63ebb', $hasher->hash('password', '')); + } + + public function testHashAlgorithmDoesNotExist() + { + $this->expectException('LogicException'); + $hasher = new Pbkdf2PasswordHasher('foobar'); + $hasher->hash('password', ''); + } + + public function testHashLength() + { + $this->expectException(InvalidPasswordException::class); + $hasher = new Pbkdf2PasswordHasher('foobar'); + + $hasher->hash(str_repeat('a', 5000), 'salt'); + } + + public function testCheckPasswordLength() + { + $hasher = new Pbkdf2PasswordHasher('foobar'); + + $this->assertFalse($hasher->verify('encoded', str_repeat('a', 5000), 'salt')); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PlaintextPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PlaintextPasswordHasherTest.php new file mode 100644 index 0000000000000..dc24db632ab16 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PlaintextPasswordHasherTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; + +class PlaintextPasswordHasherTest extends TestCase +{ + public function testVerify() + { + $hasher = new PlaintextPasswordHasher(); + + $this->assertTrue($hasher->verify('foo', 'foo', '')); + $this->assertFalse($hasher->verify('bar', 'foo', '')); + $this->assertFalse($hasher->verify('FOO', 'foo', '')); + + $hasher = new PlaintextPasswordHasher(true); + + $this->assertTrue($hasher->verify('foo', 'foo', '')); + $this->assertFalse($hasher->verify('bar', 'foo', '')); + $this->assertTrue($hasher->verify('FOO', 'foo', '')); + } + + public function testHash() + { + $hasher = new PlaintextPasswordHasher(); + + $this->assertSame('foo', $hasher->hash('foo', '')); + } + + public function testHashLength() + { + $this->expectException(InvalidPasswordException::class); + $hasher = new PlaintextPasswordHasher(); + + $hasher->hash(str_repeat('a', 5000), 'salt'); + } + + public function testCheckPasswordLength() + { + $hasher = new PlaintextPasswordHasher(); + + $this->assertFalse($hasher->verify('encoded', str_repeat('a', 5000), 'salt')); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/SodiumPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/SodiumPasswordHasherTest.php new file mode 100644 index 0000000000000..2da309ae92dea --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/SodiumPasswordHasherTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; + +class SodiumPasswordHasherTest extends TestCase +{ + protected function setUp(): void + { + if (!SodiumPasswordHasher::isSupported()) { + $this->markTestSkipped('Libsodium is not available.'); + } + } + + public function testValidation() + { + $hasher = new SodiumPasswordHasher(); + $result = $hasher->hash('password', null); + $this->assertTrue($hasher->verify($result, 'password', null)); + $this->assertFalse($hasher->verify($result, 'anotherPassword', null)); + $this->assertFalse($hasher->verify($result, '', null)); + } + + public function testBCryptValidation() + { + $hasher = new SodiumPasswordHasher(); + $this->assertTrue($hasher->verify('$2y$04$M8GDODMoGQLQRpkYCdoJh.lbiZPee3SZI32RcYK49XYTolDGwoRMm', 'abc', null)); + } + + public function testNonArgonValidation() + { + $hasher = new SodiumPasswordHasher(); + $this->assertTrue($hasher->verify('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'password', null)); + $this->assertFalse($hasher->verify('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'anotherPassword', null)); + $this->assertTrue($hasher->verify('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'password', null)); + $this->assertFalse($hasher->verify('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'anotherPassword', null)); + } + + public function testHashLength() + { + $this->expectException(InvalidPasswordException::class); + $hasher = new SodiumPasswordHasher(); + $hasher->hash(str_repeat('a', 4097), 'salt'); + } + + public function testCheckPasswordLength() + { + $hasher = new SodiumPasswordHasher(); + $result = $hasher->hash(str_repeat('a', 4096), null); + $this->assertFalse($hasher->verify($result, str_repeat('a', 4097), null)); + $this->assertTrue($hasher->verify($result, str_repeat('a', 4096), null)); + } + + public function testUserProvidedSaltIsNotUsed() + { + $hasher = new SodiumPasswordHasher(); + $result = $hasher->hash('password', 'salt'); + $this->assertTrue($hasher->verify($result, 'password', 'anotherSalt')); + } + + public function testNeedsRehash() + { + $hasher = new SodiumPasswordHasher(4, 11000); + + $this->assertTrue($hasher->needsRehash('dummyhash')); + + $hash = $hasher->hash('foo', 'salt'); + $this->assertFalse($hasher->needsRehash($hash)); + + $hasher = new SodiumPasswordHasher(5, 11000); + $this->assertTrue($hasher->needsRehash($hash)); + } +} diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/UserPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/UserPasswordHasherTest.php new file mode 100644 index 0000000000000..899723f2e45cb --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/UserPasswordHasherTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Tests\Hasher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +class UserPasswordHasherTest extends TestCase +{ + public function testHash() + { + $userMock = $this->createMock('Symfony\Component\Security\Core\User\UserInterface'); + $userMock->expects($this->any()) + ->method('getSalt') + ->willReturn('userSalt'); + + $mockHasher = $this->createMock(PasswordHasherInterface::class); + $mockHasher->expects($this->any()) + ->method('hash') + ->with($this->equalTo('plainPassword'), $this->equalTo('userSalt')) + ->willReturn('hash'); + + $mockPasswordHasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); + $mockPasswordHasherFactory->expects($this->any()) + ->method('getPasswordHasher') + ->with($this->equalTo($userMock)) + ->willReturn($mockHasher); + + $passwordHasher = new UserPasswordHasher($mockPasswordHasherFactory); + + $encoded = $passwordHasher->hashPassword($userMock, 'plainPassword'); + $this->assertEquals('hash', $encoded); + } + + public function testVerify() + { + $userMock = $this->createMock(UserInterface::class); + $userMock->expects($this->any()) + ->method('getSalt') + ->willReturn('userSalt'); + $userMock->expects($this->any()) + ->method('getPassword') + ->willReturn('hash'); + + $mockHasher = $this->createMock(PasswordHasherInterface::class); + $mockHasher->expects($this->any()) + ->method('verify') + ->with($this->equalTo('hash'), $this->equalTo('plainPassword'), $this->equalTo('userSalt')) + ->willReturn(true); + + $mockPasswordHasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); + $mockPasswordHasherFactory->expects($this->any()) + ->method('getPasswordHasher') + ->with($this->equalTo($userMock)) + ->willReturn($mockHasher); + + $passwordHasher = new UserPasswordHasher($mockPasswordHasherFactory); + + $isValid = $passwordHasher->isPasswordValid($userMock, 'plainPassword'); + $this->assertTrue($isValid); + } + + public function testNeedsRehash() + { + $user = new User('username', null); + $hasher = new NativePasswordHasher(4, 20000, 4); + + $mockPasswordHasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); + $mockPasswordHasherFactory->expects($this->any()) + ->method('getPasswordHasher') + ->with($user) + ->will($this->onConsecutiveCalls($hasher, $hasher, new NativePasswordHasher(5, 20000, 5), $hasher)); + + $passwordHasher = new UserPasswordHasher($mockPasswordHasherFactory); + + $user->setPassword($passwordHasher->hashPassword($user, 'foo', 'salt')); + $this->assertFalse($passwordHasher->needsRehash($user)); + $this->assertTrue($passwordHasher->needsRehash($user)); + $this->assertFalse($passwordHasher->needsRehash($user)); + } +} diff --git a/src/Symfony/Component/PasswordHasher/composer.json b/src/Symfony/Component/PasswordHasher/composer.json new file mode 100644 index 0000000000000..2ed22ee7066d5 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/password-hasher", + "type": "library", + "description": "Provides password hashing utilities", + "keywords": ["password", "hashing"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "symfony/security-core": "^5.3", + "symfony/console": "^5" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\PasswordHasher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/PasswordHasher/phpunit.xml.dist b/src/Symfony/Component/PasswordHasher/phpunit.xml.dist new file mode 100644 index 0000000000000..ee4c67f3058c7 --- /dev/null +++ b/src/Symfony/Component/PasswordHasher/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 43efcebec4136..7e0b6b2a337eb 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3 --- + * Deprecate all classes in the `Core\Encoder\` sub-namespace, use the `PasswordHasher` component instead * Deprecate the `SessionInterface $session` constructor argument of `SessionTokenStorage`, inject a `\Symfony\Component\HttpFoundation\RequestStack $requestStack` instead * Deprecate the `session` service provided by the ServiceLocator injected in `UsageTrackingTokenStorage`, provide a `request_stack` service instead * Deprecate using `SessionTokenStorage` outside a request context, it will throw a `SessionNotFoundException` in Symfony 6.0 diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php index e91c5d8144f6c..c4099603ef59f 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Authentication; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\AuthenticationEvents; @@ -18,6 +19,7 @@ use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use Symfony\Component\Security\Core\Exception\AccountStatusException; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\ProviderNotFoundException; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -89,6 +91,8 @@ public function authenticate(TokenInterface $token) break; } catch (AuthenticationException $e) { $lastException = $e; + } catch (InvalidPasswordException $e) { + $lastException = new BadCredentialsException('Bad credentials.', 0, $e); } } diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php index c65a9505526f7..26beb6b945ee5 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php @@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; /** * DaoAuthenticationProvider uses a UserProviderInterface to retrieve the user @@ -29,14 +30,21 @@ */ class DaoAuthenticationProvider extends UserAuthenticationProvider { - private $encoderFactory; + private $hasherFactory; private $userProvider; - public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, EncoderFactoryInterface $encoderFactory, bool $hideUserNotFoundExceptions = true) + /** + * @param PasswordHasherFactoryInterface $hasherFactory + */ + public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, $hasherFactory, bool $hideUserNotFoundExceptions = true) { parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); - $this->encoderFactory = $encoderFactory; + if ($hasherFactory instanceof EncoderFactoryInterface) { + trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); + } + + $this->hasherFactory = $hasherFactory; $this->userProvider = $userProvider; } @@ -59,14 +67,29 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke throw new BadCredentialsException('The presented password is invalid.'); } - $encoder = $this->encoderFactory->getEncoder($user); + // deprecated since Symfony 5.3 + if ($this->hasherFactory instanceof EncoderFactoryInterface) { + $encoder = $this->hasherFactory->getEncoder($user); + + if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) { + throw new BadCredentialsException('The presented password is invalid.'); + } + + if ($this->userProvider instanceof PasswordUpgraderInterface && method_exists($encoder, 'needsRehash') && $encoder->needsRehash($user->getPassword())) { + $this->userProvider->upgradePassword($user, $encoder->encodePassword($presentedPassword, $user->getSalt())); + } + + return; + } + + $hasher = $this->hasherFactory->getPasswordHasher($user); - if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) { + if (!$hasher->verify($user->getPassword(), $presentedPassword, $user->getSalt())) { throw new BadCredentialsException('The presented password is invalid.'); } - if ($this->userProvider instanceof PasswordUpgraderInterface && method_exists($encoder, 'needsRehash') && $encoder->needsRehash($user->getPassword())) { - $this->userProvider->upgradePassword($user, $encoder->encodePassword($presentedPassword, $user->getSalt())); + if ($this->userProvider instanceof PasswordUpgraderInterface && $hasher->needsRehash($user->getPassword())) { + $this->userProvider->upgradePassword($user, $hasher->hash($presentedPassword, $user->getSalt())); } } } diff --git a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php index e067a48a37be3..9c014d9ee3f4a 100644 --- a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php @@ -11,10 +11,16 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', BasePasswordEncoder::class, CheckPasswordLengthTrait::class)); + +use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; + /** * BasePasswordEncoder is the base class for all password encoders. * * @author Fabien Potencier + * + * @deprecated since Symfony 5.3, use CheckPasswordLengthTrait instead */ abstract class BasePasswordEncoder implements PasswordEncoderInterface { diff --git a/src/Symfony/Component/Security/Core/Encoder/EncoderAwareInterface.php b/src/Symfony/Component/Security/Core/Encoder/EncoderAwareInterface.php index 546f4f7337ab5..70231e2ce3de0 100644 --- a/src/Symfony/Component/Security/Core/Encoder/EncoderAwareInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/EncoderAwareInterface.php @@ -11,8 +11,12 @@ namespace Symfony\Component\Security\Core\Encoder; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; + /** * @author Christophe Coevoet + * + * @deprecated since Symfony 5.3, use {@link PasswordHasherAwareInterface} instead. */ interface EncoderAwareInterface { diff --git a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php index d07891bf77290..e90498a3dab46 100644 --- a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php +++ b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php @@ -11,12 +11,18 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class)); + use Symfony\Component\Security\Core\Exception\LogicException; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; /** * A generic encoder factory implementation. * * @author Johannes M. Schmitt + * + * @deprecated since Symfony 5.3, use {@link PasswordHasherFactory} instead */ class EncoderFactory implements EncoderFactoryInterface { @@ -34,7 +40,7 @@ public function getEncoder($user) { $encoderKey = null; - if ($user instanceof EncoderAwareInterface && (null !== $encoderName = $user->getEncoderName())) { + if (($user instanceof PasswordHasherAwareInterface && null !== $encoderName = $user->getPasswordHasherName()) || ($user instanceof EncoderAwareInterface && null !== $encoderName = $user->getEncoderName())) { if (!\array_key_exists($encoderName, $this->encoders)) { throw new \RuntimeException(sprintf('The encoder "%s" was not configured.', $encoderName)); } diff --git a/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php b/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php index 2b9834b6a041c..65fd12d81eb51 100644 --- a/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php @@ -11,12 +11,17 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactoryInterface::class, PasswordHasherFactoryInterface::class)); + use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; /** * EncoderFactoryInterface to support different encoders for different accounts. * * @author Johannes M. Schmitt + * + * @deprecated since Symfony 5.3, use {@link PasswordHasherFactoryInterface} instead */ interface EncoderFactoryInterface { diff --git a/src/Symfony/Component/Security/Core/Encoder/LegacyEncoderTrait.php b/src/Symfony/Component/Security/Core/Encoder/LegacyEncoderTrait.php new file mode 100644 index 0000000000000..d1263213fe309 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Encoder/LegacyEncoderTrait.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; + +/** + * @internal + */ +trait LegacyEncoderTrait +{ + /** + * @var PasswordHasherInterface|LegacyPasswordHasherInterface + */ + private $hasher; + + /** + * {@inheritdoc} + */ + public function encodePassword(string $raw, ?string $salt): string + { + try { + return $this->hasher->hash($raw, $salt); + } catch (InvalidPasswordException $e) { + throw new BadCredentialsException('Bad credentials.'); + } + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool + { + return $this->hasher->verify($encoded, $raw, $salt); + } + + /** + * {@inheritdoc} + */ + public function needsRehash(string $encoded): bool + { + return $this->hasher->needsRehash($encoded); + } +} diff --git a/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php index d769f2f470275..d4b1fb54b3da2 100644 --- a/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php @@ -11,19 +11,20 @@ namespace Symfony\Component\Security\Core\Encoder; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MessageDigestPasswordEncoder::class, MessageDigestPasswordHasher::class)); + +use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; /** * MessageDigestPasswordEncoder uses a message digest algorithm. * * @author Fabien Potencier + * + * @deprecated since Symfony 5.3, use {@link MessageDigestPasswordHasher} instead */ class MessageDigestPasswordEncoder extends BasePasswordEncoder { - private $algorithm; - private $encodeHashAsBase64; - private $iterations = 1; - private $encodedLength = -1; + use LegacyEncoderTrait; /** * @param string $algorithm The digest algorithm to use @@ -32,51 +33,6 @@ class MessageDigestPasswordEncoder extends BasePasswordEncoder */ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 5000) { - $this->algorithm = $algorithm; - $this->encodeHashAsBase64 = $encodeHashAsBase64; - - try { - $this->encodedLength = \strlen($this->encodePassword('', 'salt')); - } catch (\LogicException $e) { - // ignore algorithm not supported - } - - $this->iterations = $iterations; - } - - /** - * {@inheritdoc} - */ - public function encodePassword(string $raw, ?string $salt) - { - if ($this->isPasswordTooLong($raw)) { - throw new BadCredentialsException('Invalid password.'); - } - - if (!\in_array($this->algorithm, hash_algos(), true)) { - throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); - } - - $salted = $this->mergePasswordAndSalt($raw, $salt); - $digest = hash($this->algorithm, $salted, true); - - // "stretch" hash - for ($i = 1; $i < $this->iterations; ++$i) { - $digest = hash($this->algorithm, $digest.$salted, true); - } - - return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt) - { - if (\strlen($encoded) !== $this->encodedLength || false !== strpos($encoded, '$')) { - return false; - } - - return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); + $this->hasher = new MessageDigestPasswordHasher($algorithm, $encodeHashAsBase64, $iterations); } } diff --git a/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php index cd10b32bf733f..be178731e1148 100644 --- a/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MigratingPasswordEncoder::class, MigratingPasswordHasher::class)); + +use Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher; + /** * Hashes passwords using the best available encoder. * Validates them using a chain of encoders. @@ -19,12 +23,11 @@ * could be used to authenticate successfully without knowing the cleartext password. * * @author Nicolas Grekas + * + * @deprecated since Symfony 5.3, use {@link MigratingPasswordHasher} instead */ final class MigratingPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface { - private $bestEncoder; - private $extraEncoders; - public function __construct(PasswordEncoderInterface $bestEncoder, PasswordEncoderInterface ...$extraEncoders) { $this->bestEncoder = $bestEncoder; diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php index 83b7f3f1e89b5..b3bd4b54a285f 100644 --- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php @@ -11,7 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', NativePasswordEncoder::class, NativePasswordHasher::class)); + use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; /** * Hashes passwords using password_hash(). @@ -19,105 +22,18 @@ * @author Elnur Abdurrakhimov * @author Terje Bråten * @author Nicolas Grekas + * + * @deprecated since Symfony 5.3, use {@link NativePasswordHasher} instead */ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface { - private const MAX_PASSWORD_LENGTH = 4096; - - private $algo = \PASSWORD_BCRYPT; - private $options; + use LegacyEncoderTrait; /** * @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm */ public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, string $algo = null) { - $cost = $cost ?? 13; - $opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); - $memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); - - if (3 > $opsLimit) { - throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); - } - - if (10 * 1024 > $memLimit) { - throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); - } - - if ($cost < 4 || 31 < $cost) { - throw new \InvalidArgumentException('$cost must be in the range of 4-31.'); - } - - $algos = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; - - if (\defined('PASSWORD_ARGON2I')) { - $this->algo = $algos[2] = $algos['argon2i'] = (string) \PASSWORD_ARGON2I; - } - - if (\defined('PASSWORD_ARGON2ID')) { - $this->algo = $algos[3] = $algos['argon2id'] = (string) \PASSWORD_ARGON2ID; - } - - if (null !== $algo) { - $this->algo = $algos[$algo] ?? $algo; - } - - $this->options = [ - 'cost' => $cost, - 'time_cost' => $opsLimit, - 'memory_cost' => $memLimit >> 10, - 'threads' => 1, - ]; - } - - /** - * {@inheritdoc} - */ - public function encodePassword(string $raw, ?string $salt): string - { - if (\strlen($raw) > self::MAX_PASSWORD_LENGTH || ((string) \PASSWORD_BCRYPT === $this->algo && 72 < \strlen($raw))) { - throw new BadCredentialsException('Invalid password.'); - } - - // Ignore $salt, the auto-generated one is always the best - - return password_hash($raw, $this->algo, $this->options); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool - { - if ('' === $raw) { - return false; - } - - if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { - return false; - } - - if (0 !== strpos($encoded, '$argon')) { - // BCrypt encodes only the first 72 chars - return (72 >= \strlen($raw) || 0 !== strpos($encoded, '$2')) && password_verify($raw, $encoded); - } - - if (\extension_loaded('sodium') && version_compare(\SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) { - return sodium_crypto_pwhash_str_verify($encoded, $raw); - } - - if (\extension_loaded('libsodium') && version_compare(phpversion('libsodium'), '1.0.14', '>=')) { - return \Sodium\crypto_pwhash_str_verify($encoded, $raw); - } - - return password_verify($raw, $encoded); - } - - /** - * {@inheritdoc} - */ - public function needsRehash(string $encoded): bool - { - return password_needs_rehash($encoded, $this->algo, $this->options); + $this->hasher = new NativePasswordHasher($opsLimit, $memLimit, $cost, $algo); } } diff --git a/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php b/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php index 9d8d48f8db43a..ba9216ebe5160 100644 --- a/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php @@ -11,12 +11,17 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', PasswordEncoderInterface::class, PasswordHasherInterface::class)); + use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; /** * PasswordEncoderInterface is the interface for all encoders. * * @author Fabien Potencier + * + * @deprecated since Symfony 5.3, use {@link PasswordHasherInterface} instead */ interface PasswordEncoderInterface { diff --git a/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php index ab5e1a5340d84..a50ad01ea1c6b 100644 --- a/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php @@ -11,7 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', Pbkdf2PasswordEncoder::class, Pbkdf2PasswordHasher::class)); + use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; /** * Pbkdf2PasswordEncoder uses the PBKDF2 (Password-Based Key Derivation Function 2). @@ -25,14 +28,12 @@ * @author Sebastiaan Stok * @author Andrew Johnson * @author Fabien Potencier + * + * @deprecated since Symfony 5.3, use {@link Pbkdf2PasswordHasher} instead */ class Pbkdf2PasswordEncoder extends BasePasswordEncoder { - private $algorithm; - private $encodeHashAsBase64; - private $iterations = 1; - private $length; - private $encodedLength = -1; + use LegacyEncoderTrait; /** * @param string $algorithm The digest algorithm to use @@ -42,48 +43,6 @@ class Pbkdf2PasswordEncoder extends BasePasswordEncoder */ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 1000, int $length = 40) { - $this->algorithm = $algorithm; - $this->encodeHashAsBase64 = $encodeHashAsBase64; - $this->length = $length; - - try { - $this->encodedLength = \strlen($this->encodePassword('', 'salt')); - } catch (\LogicException $e) { - // ignore algorithm not supported - } - - $this->iterations = $iterations; - } - - /** - * {@inheritdoc} - * - * @throws \LogicException when the algorithm is not supported - */ - public function encodePassword(string $raw, ?string $salt) - { - if ($this->isPasswordTooLong($raw)) { - throw new BadCredentialsException('Invalid password.'); - } - - if (!\in_array($this->algorithm, hash_algos(), true)) { - throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); - } - - $digest = hash_pbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length, true); - - return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt) - { - if (\strlen($encoded) !== $this->encodedLength || false !== strpos($encoded, '$')) { - return false; - } - - return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); + $this->hasher = new Pbkdf2PasswordHasher($algorithm, $encodeHashAsBase64, $iterations, $length); } } diff --git a/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php index 90e7e3d5be69e..65fc8502791b5 100644 --- a/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Security\Core\Encoder; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', PlaintextPasswordEncoder::class, PlaintextPasswordHasher::class)); + +use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; /** * PlaintextPasswordEncoder does not do any encoding but is useful in testing environments. @@ -19,46 +21,18 @@ * As this encoder is not cryptographically secure, usage of it in production environments is discouraged. * * @author Fabien Potencier + * + * @deprecated since Symfony 5.3, use {@link PlaintextPasswordHasher} instead */ class PlaintextPasswordEncoder extends BasePasswordEncoder { - private $ignorePasswordCase; + use LegacyEncoderTrait; /** * @param bool $ignorePasswordCase Compare password case-insensitive */ public function __construct(bool $ignorePasswordCase = false) { - $this->ignorePasswordCase = $ignorePasswordCase; - } - - /** - * {@inheritdoc} - */ - public function encodePassword(string $raw, ?string $salt) - { - if ($this->isPasswordTooLong($raw)) { - throw new BadCredentialsException('Invalid password.'); - } - - return $this->mergePasswordAndSalt($raw, $salt); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt) - { - if ($this->isPasswordTooLong($raw)) { - return false; - } - - $pass2 = $this->mergePasswordAndSalt($raw, $salt); - - if (!$this->ignorePasswordCase) { - return $this->comparePasswords($encoded, $pass2); - } - - return $this->comparePasswords(strtolower($encoded), strtolower($pass2)); + $this->hasher = new PlaintextPasswordHasher($ignorePasswordCase); } } diff --git a/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php b/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php index 37855b60cff83..6bb983dd14e69 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php @@ -11,11 +11,17 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" on hasher implementations that deal with salts instead.', SelfSaltingEncoderInterface::class, LegacyPasswordHasherInterface::class)); + +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + /** * SelfSaltingEncoderInterface is a marker interface for encoders that do not * require a user-generated salt. * * @author Zan Baldwin + * + * @deprecated since Symfony 5.3, use {@link LegacyPasswordHasherInterface} instead */ interface SelfSaltingEncoderInterface { diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index 53c6660014a78..480adb4a14f34 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -11,8 +11,9 @@ namespace Symfony\Component\Security\Core\Encoder; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\LogicException; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', SodiumPasswordEncoder::class, SodiumPasswordHasher::class)); + +use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; /** * Hashes passwords using libsodium. @@ -20,99 +21,20 @@ * @author Robin Chalas * @author Zan Baldwin * @author Dominik Müller + * + * @deprecated since Symfony 5.3, use {@link SodiumPasswordHasher} instead */ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface { - private const MAX_PASSWORD_LENGTH = 4096; - - private $opsLimit; - private $memLimit; + use LegacyEncoderTrait; public function __construct(int $opsLimit = null, int $memLimit = null) { - if (!self::isSupported()) { - throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.'); - } - - $this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); - $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); - - if (3 > $this->opsLimit) { - throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); - } - - if (10 * 1024 > $this->memLimit) { - throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); - } + $this->hasher = new SodiumPasswordHasher($opsLimit, $memLimit); } public static function isSupported(): bool { - return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>='); - } - - /** - * {@inheritdoc} - */ - public function encodePassword(string $raw, ?string $salt): string - { - if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { - throw new BadCredentialsException('Invalid password.'); - } - - if (\function_exists('sodium_crypto_pwhash_str')) { - return sodium_crypto_pwhash_str($raw, $this->opsLimit, $this->memLimit); - } - - if (\extension_loaded('libsodium')) { - return \Sodium\crypto_pwhash_str($raw, $this->opsLimit, $this->memLimit); - } - - throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.'); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool - { - if ('' === $raw) { - return false; - } - - if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { - return false; - } - - if (0 !== strpos($encoded, '$argon')) { - // Accept validating non-argon passwords for seamless migrations - return (72 >= \strlen($raw) || 0 !== strpos($encoded, '$2')) && password_verify($raw, $encoded); - } - - if (\function_exists('sodium_crypto_pwhash_str_verify')) { - return sodium_crypto_pwhash_str_verify($encoded, $raw); - } - - if (\extension_loaded('libsodium')) { - return \Sodium\crypto_pwhash_str_verify($encoded, $raw); - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function needsRehash(string $encoded): bool - { - if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) { - return sodium_crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit); - } - - if (\extension_loaded('libsodium')) { - return \Sodium\crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit); - } - - throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.'); + return SodiumPasswordHasher::isSupported(); } } diff --git a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php index aeb29956469d7..bfe31a4a0faa2 100644 --- a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php @@ -11,12 +11,17 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', UserPasswordEncoder::class, UserPasswordHasher::class)); + use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; /** * A generic password encoder. * * @author Ariel Ferrandini + * + * @deprecated since Symfony 5.3, use {@link UserPasswordHasher} instead */ class UserPasswordEncoder implements UserPasswordEncoderInterface { diff --git a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php index 522ec0b02300c..858e83676835e 100644 --- a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php @@ -11,12 +11,17 @@ namespace Symfony\Component\Security\Core\Encoder; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" on hasher implementations that deal with salts instead.', UserPasswordEncoderInterface::class, UserPasswordHasherInterface::class)); + use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; /** * UserPasswordEncoderInterface is the interface for the password encoder service. * * @author Ariel Ferrandini + * + * @deprecated since Symfony 5.3, use {@link UserPasswordHasherInterface} instead */ interface UserPasswordEncoderInterface { diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php index 57ed2d0bf786f..20e75b80760df 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php @@ -15,17 +15,17 @@ use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; -use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; -use Symfony\Component\Security\Core\Tests\Encoder\TestPasswordEncoderInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; class DaoAuthenticationProviderTest extends TestCase { @@ -39,7 +39,10 @@ public function testRetrieveUserWhenProviderDoesNotReturnAnUserInterface() $method->invoke($provider, 'fabien', $this->getSupportedToken()); } - public function testRetrieveUserWhenUsernameIsNotFound() + /** + * @group legacy + */ + public function testRetrieveUserWhenUsernameIsNotFoundWithLegacyEncoderFactory() { $this->expectException(UsernameNotFoundException::class); $userProvider = $this->createMock(UserProviderInterface::class); @@ -55,6 +58,22 @@ public function testRetrieveUserWhenUsernameIsNotFound() $method->invoke($provider, 'fabien', $this->getSupportedToken()); } + public function testRetrieveUserWhenUsernameIsNotFound() + { + $this->expectException(UsernameNotFoundException::class); + $userProvider = $this->createMock(UserProviderInterface::class); + $userProvider->expects($this->once()) + ->method('loadUserByUsername') + ->willThrowException(new UsernameNotFoundException()) + ; + + $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); + $method = new \ReflectionMethod($provider, 'retrieveUser'); + $method->setAccessible(true); + + $method->invoke($provider, 'fabien', $this->getSupportedToken()); + } + public function testRetrieveUserWhenAnExceptionOccurs() { $this->expectException(AuthenticationServiceException::class); @@ -64,7 +83,7 @@ public function testRetrieveUserWhenAnExceptionOccurs() ->willThrowException(new \RuntimeException()) ; - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(EncoderFactoryInterface::class)); + $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); $method = new \ReflectionMethod($provider, 'retrieveUser'); $method->setAccessible(true); @@ -85,7 +104,7 @@ public function testRetrieveUserReturnsUserFromTokenOnReauthentication() ->willReturn($user) ; - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(EncoderFactoryInterface::class)); + $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); $reflection = new \ReflectionMethod($provider, 'retrieveUser'); $reflection->setAccessible(true); $result = $reflection->invoke($provider, 'someUser', $token); @@ -103,7 +122,7 @@ public function testRetrieveUser() ->willReturn($user) ; - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(EncoderFactoryInterface::class)); + $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); $method = new \ReflectionMethod($provider, 'retrieveUser'); $method->setAccessible(true); @@ -113,13 +132,13 @@ public function testRetrieveUser() public function testCheckAuthenticationWhenCredentialsAreEmpty() { $this->expectException(BadCredentialsException::class); - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder + $hasher = $this->getMockBuilder(PasswordHasherInterface::class)->getMock(); + $hasher ->expects($this->never()) - ->method('isPasswordValid') + ->method('verify') ; - $provider = $this->getProvider(null, null, $encoder); + $provider = $this->getProvider(null, null, $hasher); $method = new \ReflectionMethod($provider, 'checkAuthentication'); $method->setAccessible(true); @@ -135,14 +154,14 @@ public function testCheckAuthenticationWhenCredentialsAreEmpty() public function testCheckAuthenticationWhenCredentialsAre0() { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher ->expects($this->once()) - ->method('isPasswordValid') + ->method('verify') ->willReturn(true) ; - $provider = $this->getProvider(null, null, $encoder); + $provider = $this->getProvider(null, null, $hasher); $method = new \ReflectionMethod($provider, 'checkAuthentication'); $method->setAccessible(true); @@ -163,13 +182,13 @@ public function testCheckAuthenticationWhenCredentialsAre0() public function testCheckAuthenticationWhenCredentialsAreNotValid() { $this->expectException(BadCredentialsException::class); - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->once()) - ->method('isPasswordValid') + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher->expects($this->once()) + ->method('verify') ->willReturn(false) ; - $provider = $this->getProvider(null, null, $encoder); + $provider = $this->getProvider(null, null, $hasher); $method = new \ReflectionMethod($provider, 'checkAuthentication'); $method->setAccessible(true); @@ -235,13 +254,13 @@ public function testCheckAuthenticationWhenTokenNeedsReauthenticationWorksWithou public function testCheckAuthentication() { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->once()) - ->method('isPasswordValid') + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher->expects($this->once()) + ->method('verify') ->willReturn(true) ; - $provider = $this->getProvider(null, null, $encoder); + $provider = $this->getProvider(null, null, $hasher); $method = new \ReflectionMethod($provider, 'checkAuthentication'); $method->setAccessible(true); @@ -258,21 +277,21 @@ public function testPasswordUpgrades() { $user = new User('user', 'pwd'); - $encoder = $this->createMock(TestPasswordEncoderInterface::class); - $encoder->expects($this->once()) - ->method('isPasswordValid') + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher->expects($this->once()) + ->method('verify') ->willReturn(true) ; - $encoder->expects($this->once()) - ->method('encodePassword') + $hasher->expects($this->once()) + ->method('hash') ->willReturn('foobar') ; - $encoder->expects($this->once()) + $hasher->expects($this->once()) ->method('needsRehash') ->willReturn(true) ; - $provider = $this->getProvider(null, null, $encoder); + $provider = $this->getProvider(null, null, $hasher); $userProvider = ((array) $provider)[sprintf("\0%s\0userProvider", DaoAuthenticationProvider::class)]; $userProvider->expects($this->once()) @@ -304,7 +323,7 @@ protected function getSupportedToken() return $mock; } - protected function getProvider($user = null, $userChecker = null, $passwordEncoder = null) + protected function getProvider($user = null, $userChecker = null, $passwordHasher = null) { $userProvider = $this->createMock(PasswordUpgraderProvider::class); if (null !== $user) { @@ -318,18 +337,18 @@ protected function getProvider($user = null, $userChecker = null, $passwordEncod $userChecker = $this->createMock(UserCheckerInterface::class); } - if (null === $passwordEncoder) { - $passwordEncoder = new PlaintextPasswordEncoder(); + if (null === $passwordHasher) { + $passwordHasher = new PlaintextPasswordHasher(); } - $encoderFactory = $this->createMock(EncoderFactoryInterface::class); - $encoderFactory + $hasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); + $hasherFactory ->expects($this->any()) - ->method('getEncoder') - ->willReturn($passwordEncoder) + ->method('getPasswordHasher') + ->willReturn($passwordHasher) ; - return new DaoAuthenticationProvider($userProvider, $userChecker, 'key', $encoderFactory); + return new DaoAuthenticationProvider($userProvider, $userChecker, 'key', $hasherFactory); } } diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php index a6999991393c4..7b79986b826a6 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php @@ -20,7 +20,13 @@ use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; +use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; +/** + * @group legacy + */ class EncoderFactoryTest extends TestCase { public function testGetEncoderWithMessageDigestEncoder() @@ -176,6 +182,17 @@ public function testDefaultMigratingEncoders() (new EncoderFactory([SomeUser::class => ['class' => SodiumPasswordEncoder::class, 'arguments' => []]]))->getEncoder(SomeUser::class) ); } + + public function testHasherAwareCompat() + { + $factory = new PasswordHasherFactory([ + 'encoder_name' => new MessageDigestPasswordHasher('sha1'), + ]); + + $encoder = $factory->getPasswordHasher(new HasherAwareUser('user', 'pass')); + $expectedEncoder = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedEncoder->hash('foo', ''), $encoder->hash('foo', '')); + } } class SomeUser implements UserInterface @@ -214,3 +231,14 @@ public function getEncoderName(): ?string return $this->encoderName; } } + + +class HasherAwareUser extends SomeUser implements PasswordHasherAwareInterface +{ + public $hasherName = 'encoder_name'; + + public function getPasswordHasherName(): ?string + { + return $this->hasherName; + } +} diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/MessageDigestPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/MessageDigestPasswordEncoderTest.php index c2b514bb6b0af..a354b0dbf25a8 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/MessageDigestPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/MessageDigestPasswordEncoderTest.php @@ -15,6 +15,9 @@ use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +/** + * @group legacy + */ class MessageDigestPasswordEncoderTest extends TestCase { public function testIsPasswordValid() diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/MigratingPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/MigratingPasswordEncoderTest.php index efa360ecb2cf1..fbaf89b0b1b1a 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/MigratingPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/MigratingPasswordEncoderTest.php @@ -15,6 +15,9 @@ use Symfony\Component\Security\Core\Encoder\MigratingPasswordEncoder; use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; +/** + * @group legacy + */ class MigratingPasswordEncoderTest extends TestCase { public function testValidation() diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php index c67bf8668b4dd..9d864dfce038e 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php @@ -16,6 +16,7 @@ /** * @author Elnur Abdurrakhimov + * @group legacy */ class NativePasswordEncoderTest extends TestCase { diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/Pbkdf2PasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/Pbkdf2PasswordEncoderTest.php index db274716bd834..000e07d659113 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/Pbkdf2PasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/Pbkdf2PasswordEncoderTest.php @@ -15,6 +15,9 @@ use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +/** + * @group legacy + */ class Pbkdf2PasswordEncoderTest extends TestCase { public function testIsPasswordValid() diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/PlaintextPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/PlaintextPasswordEncoderTest.php index fb5e674567d1b..398044035eb61 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/PlaintextPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/PlaintextPasswordEncoderTest.php @@ -15,6 +15,9 @@ use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +/** + * @group legacy + */ class PlaintextPasswordEncoderTest extends TestCase { public function testIsPasswordValid() diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php index b4073a1cfba53..4bae5f89f35bc 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php @@ -15,6 +15,9 @@ use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +/** + * @group legacy + */ class SodiumPasswordEncoderTest extends TestCase { protected function setUp(): void diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/TestPasswordEncoderInterface.php b/src/Symfony/Component/Security/Core/Tests/Encoder/TestPasswordEncoderInterface.php index 13e2d0d3b36ea..3764038e9a9d3 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/TestPasswordEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/TestPasswordEncoderInterface.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; +/** + * @group legacy + */ interface TestPasswordEncoderInterface extends PasswordEncoderInterface { public function needsRehash(string $encoded): bool; diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/UserPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/UserPasswordEncoderTest.php index 0d72919abc40a..6f52fbf1b22d9 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/UserPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/UserPasswordEncoderTest.php @@ -19,6 +19,9 @@ use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @group legacy + */ class UserPasswordEncoderTest extends TestCase { public function testEncodePassword() diff --git a/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php b/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php index 9c65298b07f56..ef62023d53e45 100644 --- a/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php +++ b/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php @@ -17,11 +17,11 @@ interface PasswordUpgraderInterface { /** - * Upgrades the encoded password of a user, typically for using a better hash algorithm. + * Upgrades the hashed password of a user, typically for using a better hash algorithm. * * This method should persist the new password in the user storage and update the $user object accordingly. * Because you don't want your users not being able to log in, this method should be opportunistic: * it's fine if it does nothing or if it fails without throwing any exception. */ - public function upgradePassword(UserInterface $user, string $newEncodedPassword): void; + public function upgradePassword(UserInterface $user, string $newHashedPassword): void; } diff --git a/src/Symfony/Component/Security/Core/User/UserInterface.php b/src/Symfony/Component/Security/Core/User/UserInterface.php index 239eb0ed00c6c..c005e3ca9c9b7 100644 --- a/src/Symfony/Component/Security/Core/User/UserInterface.php +++ b/src/Symfony/Component/Security/Core/User/UserInterface.php @@ -15,7 +15,7 @@ * Represents the interface that all user classes must implement. * * This interface is useful because the authentication layer can deal with - * the object through its lifecycle, using the object to get the encoded + * the object through its lifecycle, using the object to get the hashed * password (for checking against a submitted password), assigning roles * and so on. * @@ -49,17 +49,17 @@ public function getRoles(); /** * Returns the password used to authenticate the user. * - * This should be the encoded password. On authentication, a plain-text - * password will be salted, encoded, and then compared to this value. + * This should be the hashed password. On authentication, a plain-text + * password will be hashed, and then compared to this value. * - * @return string|null The encoded password if any + * @return string|null The hashed password if any */ public function getPassword(); /** - * Returns the salt that was originally used to encode the password. + * Returns the salt that was originally used to hash the password. * - * This can return null if the password was not encoded using a salt. + * This can return null if the password was not hashed using a salt. * * @return string|null The salt */ diff --git a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php index 24b032484fa1a..0181ccbcbb935 100644 --- a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php +++ b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php @@ -13,7 +13,9 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; +use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; @@ -22,12 +24,19 @@ class UserPasswordValidator extends ConstraintValidator { private $tokenStorage; - private $encoderFactory; + private $hasherFactory; - public function __construct(TokenStorageInterface $tokenStorage, EncoderFactoryInterface $encoderFactory) + /** + * @param PasswordHasherFactoryInterface $hasherFactory + */ + public function __construct(TokenStorageInterface $tokenStorage, $hasherFactory) { + if ($hasherFactory instanceof EncoderFactoryInterface) { + trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); + } + $this->tokenStorage = $tokenStorage; - $this->encoderFactory = $encoderFactory; + $this->hasherFactory = $hasherFactory; } /** @@ -51,9 +60,9 @@ public function validate($password, Constraint $constraint) throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.'); } - $encoder = $this->encoderFactory->getEncoder($user); + $hasher = $this->hasherFactory instanceof EncoderFactoryInterface ? $this->hasherFactory->getEncoder($user) : $this->hasherFactory->getPasswordHasher($user); - if (null === $user->getPassword() || !$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) { + if (null === $user->getPassword() || !($hasher instanceof PasswordEncoderInterface ? $hasher->isPasswordValid($user->getPassword(), $password, $user->getSalt()) : $hasher->verify($user->getPassword(), $password, $user->getSalt()))) { $this->context->addViolation($constraint->message); } } diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json index 48a6a46ec4e77..424c077569d51 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -20,7 +20,8 @@ "symfony/event-dispatcher-contracts": "^1.1|^2", "symfony/polyfill-php80": "^1.15", "symfony/service-contracts": "^1.1.6|^2", - "symfony/deprecation-contracts": "^2.1" + "symfony/deprecation-contracts": "^2.1", + "symfony/password-hasher": "^5.3" }, "require-dev": { "psr/container": "^1.0", diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 49244680ad737..4d2ec108999a3 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -13,6 +13,7 @@ use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationExpiredException; @@ -26,6 +27,7 @@ use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface; use Symfony\Component\Security\Guard\Token\GuardTokenInterface; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; /** * Responsible for accepting the PreAuthenticationGuardToken and calling @@ -42,19 +44,24 @@ class GuardAuthenticationProvider implements AuthenticationProviderInterface private $userProvider; private $providerKey; private $userChecker; - private $passwordEncoder; + private $passwordHasher; /** * @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener * @param string $providerKey The provider (i.e. firewall) key + * @param UserPasswordHasherInterface $passwordHasher */ - public function __construct(iterable $guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker, UserPasswordEncoderInterface $passwordEncoder = null) + public function __construct(iterable $guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker, $passwordHasher = null) { $this->guardAuthenticators = $guardAuthenticators; $this->userProvider = $userProvider; $this->providerKey = $providerKey; $this->userChecker = $userChecker; - $this->passwordEncoder = $passwordEncoder; + $this->passwordHasher = $passwordHasher; + + if ($passwordHasher instanceof UserPasswordEncoderInterface) { + trigger_deprecation('symfony/security-core', '5.3', sprintf('Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', UserPasswordEncoderInterface::class, __CLASS__, UserPasswordHasherInterface::class)); + } } /** @@ -123,8 +130,13 @@ private function authenticateViaGuard(AuthenticatorInterface $guardAuthenticator throw new BadCredentialsException(sprintf('Authentication failed because "%s::checkCredentials()" did not return true.', get_debug_type($guardAuthenticator))); } - if ($this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordEncoder && (null !== $password = $guardAuthenticator->getPassword($token->getCredentials())) && method_exists($this->passwordEncoder, 'needsRehash') && $this->passwordEncoder->needsRehash($user)) { - $this->userProvider->upgradePassword($user, $this->passwordEncoder->encodePassword($user, $password)); + if ($this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordHasher && (null !== $password = $guardAuthenticator->getPassword($token->getCredentials())) && method_exists($this->passwordHasher, 'needsRehash') && $this->passwordHasher->needsRehash($user)) { + if ($this->passwordHasher instanceof PasswordEncoderInterface) { + // @deprecated since Symfony 5.3 + $this->userProvider->upgradePassword($user, $this->passwordHasher->encodePassword($user, $password)); + } else { + $this->userProvider->upgradePassword($user, $this->passwordHasher->hashPassword($user, $password)); + } } $this->userChecker->checkPostAuth($user); diff --git a/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php b/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php index bd0d85fd7a2eb..95f9c88542189 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php @@ -19,6 +19,7 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; /** * This listeners uses the interfaces of authenticators to @@ -31,18 +32,25 @@ */ class CheckCredentialsListener implements EventSubscriberInterface { - private $encoderFactory; + private $hasherFactory; - public function __construct(EncoderFactoryInterface $encoderFactory) + /** + * @param PasswordHasherFactoryInterface $hasherFactory + */ + public function __construct($hasherFactory) { - $this->encoderFactory = $encoderFactory; + if ($hasherFactory instanceof EncoderFactoryInterface) { + trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); + } + + $this->hasherFactory = $hasherFactory; } public function checkPassport(CheckPassportEvent $event): void { $passport = $event->getPassport(); if ($passport instanceof UserPassportInterface && $passport->hasBadge(PasswordCredentials::class)) { - // Use the password encoder to validate the credentials + // Use the password hasher to validate the credentials $user = $passport->getUser(); /** @var PasswordCredentials $badge */ $badge = $passport->getBadge(PasswordCredentials::class); @@ -60,8 +68,15 @@ public function checkPassport(CheckPassportEvent $event): void throw new BadCredentialsException('The presented password is invalid.'); } - if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) { - throw new BadCredentialsException('The presented password is invalid.'); + // @deprecated since Symfony 5.3 + if ($this->hasherFactory instanceof EncoderFactoryInterface) { + if (!$this->hasherFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) { + throw new BadCredentialsException('The presented password is invalid.'); + } + } else { + if (!$this->hasherFactory->getPasswordHasher($user)->verify($user->getPassword(), $presentedPassword, $user->getSalt())) { + throw new BadCredentialsException('The presented password is invalid.'); + } } $badge->markResolved(); diff --git a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php index fb1f229d6f106..a3755ca3ab405 100644 --- a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php @@ -12,12 +12,15 @@ namespace Symfony\Component\Security\Http\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; /** * @author Wouter de Jong @@ -27,11 +30,18 @@ */ class PasswordMigratingListener implements EventSubscriberInterface { - private $encoderFactory; + private $hasherFactory; - public function __construct(EncoderFactoryInterface $encoderFactory) + /** + * @param PasswordHasherFactoryInterface $hasherFactory + */ + public function __construct($hasherFactory) { - $this->encoderFactory = $encoderFactory; + if ($hasherFactory instanceof EncoderFactoryInterface) { + trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); + } + + $this->hasherFactory = $hasherFactory; } public function onLoginSuccess(LoginSuccessEvent $event): void @@ -50,8 +60,8 @@ public function onLoginSuccess(LoginSuccessEvent $event): void } $user = $passport->getUser(); - $passwordEncoder = $this->encoderFactory->getEncoder($user); - if (!$passwordEncoder->needsRehash($user->getPassword())) { + $passwordHasher = $this->hasherFactory instanceof EncoderFactoryInterface ? $this->hasherFactory->getEncoder($user) : $this->hasherFactory->getPasswordHasher($user); + if (!$passwordHasher->needsRehash($user->getPassword())) { return; } @@ -72,7 +82,7 @@ public function onLoginSuccess(LoginSuccessEvent $event): void } } - $passwordUpgrader->upgradePassword($user, $passwordEncoder->encodePassword($plaintextPassword, $user->getSalt())); + $passwordUpgrader->upgradePassword($user, $passwordHasher->hash($plaintextPassword, $user->getSalt())); } public static function getSubscribedEvents(): array diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/HttpBasicAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/HttpBasicAuthenticatorTest.php index 79e914965ab9e..27e20917d0bc6 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/HttpBasicAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/HttpBasicAuthenticatorTest.php @@ -4,31 +4,31 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\Tests\Authenticator\Fixtures\PasswordUpgraderProvider; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; class HttpBasicAuthenticatorTest extends TestCase { private $userProvider; - private $encoderFactory; - private $encoder; + private $hasherFactory; + private $hasher; private $authenticator; protected function setUp(): void { $this->userProvider = $this->createMock(UserProviderInterface::class); - $this->encoderFactory = $this->createMock(EncoderFactoryInterface::class); - $this->encoder = $this->createMock(PasswordEncoderInterface::class); - $this->encoderFactory + $this->hasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); + $this->hasher = $this->createMock(PasswordHasherInterface::class); + $this->hasherFactory ->expects($this->any()) - ->method('getEncoder') - ->willReturn($this->encoder); + ->method('getPasswordHasher') + ->willReturn($this->hasher); $this->authenticator = new HttpBasicAuthenticator('test', $this->userProvider); } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php index e903dcd22cbf6..315d7ccce4585 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; @@ -25,18 +24,20 @@ use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Event\CheckPassportEvent; use Symfony\Component\Security\Http\EventListener\CheckCredentialsListener; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; class CheckCredentialsListenerTest extends TestCase { - private $encoderFactory; + private $hasherFactory; private $listener; private $user; protected function setUp(): void { - $this->encoderFactory = $this->createMock(EncoderFactoryInterface::class); - $this->listener = new CheckCredentialsListener($this->encoderFactory); - $this->user = new User('wouter', 'encoded-password'); + $this->hasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); + $this->listener = new CheckCredentialsListener($this->hasherFactory); + $this->user = new User('wouter', 'password-hash'); } /** @@ -44,10 +45,10 @@ protected function setUp(): void */ public function testPasswordAuthenticated($password, $passwordValid, $result) { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', $password)->willReturn($passwordValid); + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher->expects($this->any())->method('verify')->with('password-hash', $password)->willReturn($passwordValid); - $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder); + $this->hasherFactory->expects($this->any())->method('getPasswordHasher')->with($this->identicalTo($this->user))->willReturn($hasher); if (false === $result) { $this->expectException(BadCredentialsException::class); @@ -73,7 +74,7 @@ public function testEmptyPassword() $this->expectException(BadCredentialsException::class); $this->expectExceptionMessage('The presented password cannot be empty.'); - $this->encoderFactory->expects($this->never())->method('getEncoder'); + $this->hasherFactory->expects($this->never())->method('getPasswordHasher'); $event = $this->createEvent(new Passport(new UserBadge('wouter', function () { return $this->user; }), new PasswordCredentials(''))); $this->listener->checkPassport($event); @@ -84,7 +85,7 @@ public function testEmptyPassword() */ public function testCustomAuthenticated($result) { - $this->encoderFactory->expects($this->never())->method('getEncoder'); + $this->hasherFactory->expects($this->never())->method('getPasswordHasher'); if (false === $result) { $this->expectException(BadCredentialsException::class); @@ -108,7 +109,7 @@ public function provideCustomAuthenticatedResults() public function testNoCredentialsBadgeProvided() { - $this->encoderFactory->expects($this->never())->method('getEncoder'); + $this->hasherFactory->expects($this->never())->method('getPasswordHasher'); $event = $this->createEvent(new SelfValidatingPassport(new UserBadge('wouter', function () { return $this->user; }))); $this->listener->checkPassport($event); @@ -116,10 +117,10 @@ public function testNoCredentialsBadgeProvided() public function testAddsPasswordUpgradeBadge() { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', 'ThePa$$word')->willReturn(true); + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher->expects($this->any())->method('verify')->with('password-hash', 'ThePa$$word')->willReturn(true); - $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder); + $this->hasherFactory->expects($this->any())->method('getPasswordHasher')->with($this->identicalTo($this->user))->willReturn($hasher); $passport = new Passport(new UserBadge('wouter', function () { return $this->user; }), new PasswordCredentials('ThePa$$word')); $this->listener->checkPassport($this->createEvent($passport)); @@ -130,10 +131,10 @@ public function testAddsPasswordUpgradeBadge() public function testAddsNoPasswordUpgradeBadgeIfItAlreadyExists() { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', 'ThePa$$word')->willReturn(true); + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher->expects($this->any())->method('verify')->with('password-hash', 'ThePa$$word')->willReturn(true); - $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder); + $this->hasherFactory->expects($this->any())->method('getPasswordHasher')->with($this->identicalTo($this->user))->willReturn($hasher); $passport = $this->getMockBuilder(Passport::class) ->setMethods(['addBadge']) @@ -147,10 +148,10 @@ public function testAddsNoPasswordUpgradeBadgeIfItAlreadyExists() public function testAddsNoPasswordUpgradeBadgeIfPasswordIsInvalid() { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', 'ThePa$$word')->willReturn(false); + $hasher = $this->createMock(PasswordHasherInterface::class); + $hasher->expects($this->any())->method('verify')->with('password-hash', 'ThePa$$word')->willReturn(false); - $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder); + $this->hasherFactory->expects($this->any())->method('getPasswordHasher')->with($this->identicalTo($this->user))->willReturn($hasher); $passport = $this->getMockBuilder(Passport::class) ->setMethods(['addBadge']) diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php index 39a62e0f69d68..0e32433c5b251 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php @@ -14,8 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -27,23 +25,25 @@ use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; class PasswordMigratingListenerTest extends TestCase { - private $encoderFactory; + private $hasherFactory; private $listener; private $user; protected function setUp(): void { $this->user = $this->createMock(UserInterface::class); - $this->user->expects($this->any())->method('getPassword')->willReturn('old-encoded-password'); - $encoder = $this->createMock(PasswordEncoderInterface::class); + $this->user->expects($this->any())->method('getPassword')->willReturn('old-hash'); + $encoder = $this->createMock(PasswordHasherInterface::class); $encoder->expects($this->any())->method('needsRehash')->willReturn(true); - $encoder->expects($this->any())->method('encodePassword')->with('pa$$word', null)->willReturn('new-encoded-password'); - $this->encoderFactory = $this->createMock(EncoderFactoryInterface::class); - $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->user)->willReturn($encoder); - $this->listener = new PasswordMigratingListener($this->encoderFactory); + $encoder->expects($this->any())->method('hash')->with('pa$$word', null)->willReturn('new-hash'); + $this->hasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); + $this->hasherFactory->expects($this->any())->method('getPasswordHasher')->with($this->user)->willReturn($encoder); + $this->listener = new PasswordMigratingListener($this->hasherFactory); } /** @@ -51,7 +51,7 @@ protected function setUp(): void */ public function testUnsupportedEvents($event) { - $this->encoderFactory->expects($this->never())->method('getEncoder'); + $this->hasherFactory->expects($this->never())->method('getPasswordHasher'); $this->listener->onLoginSuccess($event); } @@ -87,7 +87,7 @@ public function testUpgradeWithUpgrader() $passwordUpgrader = $this->createPasswordUpgrader(); $passwordUpgrader->expects($this->once()) ->method('upgradePassword') - ->with($this->user, 'new-encoded-password') + ->with($this->user, 'new-hash') ; $event = $this->createEvent(new SelfValidatingPassport(new UserBadge('test', function () { return $this->user; }), [new PasswordUpgradeBadge('pa$$word', $passwordUpgrader)])); @@ -101,7 +101,7 @@ public function testUpgradeWithoutUpgrader() $userLoader->expects($this->once()) ->method('upgradePassword') - ->with($this->user, 'new-encoded-password') + ->with($this->user, 'new-hash') ; $event = $this->createEvent(new SelfValidatingPassport(new UserBadge('test', [$userLoader, 'loadUserByUsername']), [new PasswordUpgradeBadge('pa$$word')])); diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index f4d31854a87fb..c2297fc0535b4 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", - "symfony/security-core": "^5.2", + "symfony/security-core": "^5.3", "symfony/http-foundation": "^5.2", "symfony/http-kernel": "^5.2", "symfony/polyfill-php80": "^1.15", From 37c591516a47d470759528edec0fc9cf166cca8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sun, 31 Jan 2021 20:45:01 +0100 Subject: [PATCH 148/188] Deprecate session.storage --- UPGRADE-5.3.md | 2 + UPGRADE-6.0.md | 2 + .../Bundle/FrameworkBundle/CHANGELOG.md | 2 + .../Compiler/SessionPass.php | 2 +- .../DependencyInjection/Configuration.php | 7 +++ .../FrameworkExtension.php | 41 +++++++++++--- .../Bundle/FrameworkBundle/KernelBrowser.php | 3 +- .../Resources/config/schema/symfony-1.0.xsd | 1 + .../Resources/config/session.php | 56 ++++++++++++++++++- .../Compiler/SessionPassTest.php | 6 +- .../DependencyInjection/ConfigurationTest.php | 1 + .../DependencyInjection/Fixtures/php/csrf.php | 1 + .../DependencyInjection/Fixtures/php/full.php | 2 +- .../Fixtures/php/session.php | 1 + .../php/session_cookie_secure_auto.php | 1 + .../php/session_cookie_secure_auto_legacy.php | 9 +++ .../Fixtures/php/session_legacy.php | 8 +++ .../DependencyInjection/Fixtures/xml/csrf.xml | 2 +- .../xml/form_csrf_sets_field_name.xml | 2 +- .../form_csrf_under_form_sets_field_name.xml | 2 +- .../DependencyInjection/Fixtures/xml/full.xml | 2 +- .../Fixtures/xml/session.xml | 2 +- .../xml/session_cookie_secure_auto.xml | 2 +- .../xml/session_cookie_secure_auto_legacy.xml | 12 ++++ .../Fixtures/xml/session_legacy.xml | 12 ++++ .../DependencyInjection/Fixtures/yml/csrf.yml | 3 +- .../DependencyInjection/Fixtures/yml/full.yml | 2 +- .../Fixtures/yml/session.yml | 1 + .../yml/session_cookie_secure_auto.yml | 1 + .../yml/session_cookie_secure_auto_legacy.yml | 5 ++ .../Fixtures/yml/session_legacy.yml | 4 ++ .../FrameworkExtensionTest.php | 38 ++++++++++++- .../Functional/app/ConfigDump/config.yml | 1 + .../Functional/app/ContainerDump/config.yml | 3 +- .../Tests/Functional/app/config/framework.yml | 2 +- .../RegisterTokenUsageTrackingPass.php | 2 +- .../RegisterTokenUsageTrackingPassTest.php | 4 +- .../SecurityExtensionTest.php | 1 + .../Tests/Functional/app/Anonymous/config.yml | 2 +- .../Functional/app/Authenticator/config.yml | 2 +- .../app/FirewallEntryPoint/config.yml | 2 +- .../Tests/Functional/app/Guarded/config.yml | 2 +- .../app/RememberMeLogout/config.yml | 1 + .../Tests/Functional/app/config/framework.yml | 2 +- .../Bundle/SecurityBundle/composer.json | 2 +- .../Functional/WebProfilerBundleKernel.php | 2 +- .../Bundle/WebProfilerBundle/composer.json | 2 +- .../Component/HttpFoundation/CHANGELOG.md | 1 + .../HttpFoundation/Session/SessionFactory.php | 40 +++++++++++++ .../Storage/MockFileSessionStorageFactory.php | 42 ++++++++++++++ .../Storage/NativeSessionStorageFactory.php | 49 ++++++++++++++++ .../PhpBridgeSessionStorageFactory.php | 47 ++++++++++++++++ .../Session/Storage/ServiceSessionFactory.php | 38 +++++++++++++ .../SessionStorageFactoryInterface.php | 25 +++++++++ 54 files changed, 468 insertions(+), 39 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto_legacy.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_legacy.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto_legacy.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_legacy.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto_legacy.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_legacy.yml create mode 100644 src/Symfony/Component/HttpFoundation/Session/SessionFactory.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/ServiceSessionFactory.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageFactoryInterface.php diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index d4b8bebe179ad..9ede8f6e8120f 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -31,6 +31,8 @@ Form FrameworkBundle --------------- + * Deprecate the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead + * Deprecate the `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead * Deprecate the `session` service and the `SessionInterface` alias, use the `\Symfony\Component\HttpFoundation\Request::getSession()` or the new `\Symfony\Component\HttpFoundation\RequestStack::getSession()` methods instead HttpFoundation diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index a6ee83505cf9e..85209e010e11f 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -69,6 +69,8 @@ Form FrameworkBundle --------------- + * 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 * `MicroKernelTrait::configureRoutes()` is now always called with a `RoutingConfigurator` * The "framework.router.utf8" configuration option defaults to `true` diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 61edb7de0e37b..fd572dc16c969 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 5.3 --- + * Deprecate the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead + * Deprecate the `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead * Deprecate the `session` service and the `SessionInterface` alias, use the `Request::getSession()` or the new `RequestStack::getSession()` methods instead * Added `AbstractController::renderForm()` to render a form and set the appropriate HTTP status code * Added support for configuring PHP error level to log levels diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SessionPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SessionPass.php index df820767021b2..c0c9dd6758999 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SessionPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SessionPass.php @@ -22,7 +22,7 @@ class SessionPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->has('session.storage')) { + if (!$container->has('session.factory')) { return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index f5e2678e4038f..5c34fd696f860 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -600,8 +600,15 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->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('name') ->validate() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ac528cbc3c29b..d69e5668712f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -70,6 +70,7 @@ use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; @@ -1028,7 +1029,20 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $loader->load('session.php'); // session storage - $container->setAlias('session.storage', $config['storage_id']); + 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->removeAlias('session.storage.filesystem'); + } + $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) { if (isset($config[$key])) { @@ -1037,11 +1051,16 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c } if ('auto' === ($options['cookie_secure'] ?? null)) { - $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'), - ]); + 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->setParameter('session.storage.options', $options); @@ -1049,8 +1068,14 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c // session handler (the internal callback registered with PHP session management) if (null === $config['handler_id']) { // Set the handler class to be null - $container->getDefinition('session.storage.native')->replaceArgument(1, null); - $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, 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'); } else { $container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs); diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 19a500515720f..cba7b829bb5cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -18,7 +18,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpKernel\HttpKernelBrowser; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\HttpKernel\Profiler\Profile as HttpProfile; @@ -123,7 +122,7 @@ public function loginUser($user, string $firewallContext = 'main'): self $token = new TestBrowserToken($user->getRoles(), $user); $token->setAuthenticated(true); - $session = new Session($this->getContainer()->get('test.service_container')->get('session.storage')); + $session = $this->getContainer()->get('test.service_container')->get('session.factory')->createSession(); $session->set('_security_'.$firewallContext, serialize($token)); $session->save(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 05675e19b3180..061647649a015 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -107,6 +107,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php index fcaca3ffc4c65..bef83c3c0162d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php @@ -16,6 +16,7 @@ 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; @@ -25,8 +26,12 @@ 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\ServiceSessionFactory; use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; use Symfony\Component\HttpKernel\EventListener\SessionListener; @@ -35,17 +40,57 @@ $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('session.storage'), - null, // AttributeBagInterface - null, // FlashBagInterface + service('request_stack'), + service('session.storage.factory'), [service('session_listener'), 'onSessionUsage'], ]) + + ->set('session.storage.factory.native', NativeSessionStorageFactory::class) + ->args([ + param('session.storage.options'), + service('session.handler'), + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + false, + ]) + ->set('session.storage.factory.php_bridge', PhpBridgeSessionStorageFactory::class) + ->args([ + service('session.handler'), + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + false, + ]) + ->set('session.storage.factory.mock_file', MockFileSessionStorageFactory::class) + ->args([ + param('kernel.cache_dir').'/sessions', + 'MOCKSESSID', + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + 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%" alias is 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) @@ -53,6 +98,7 @@ param('session.metadata.storage_key'), param('session.metadata.update_threshold'), ]) + ->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([ @@ -60,12 +106,14 @@ 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']) @@ -83,6 +131,7 @@ '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([ @@ -108,6 +157,7 @@ // 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) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/SessionPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/SessionPassTest.php index 4b3601969d3cf..7cbb3262f131f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/SessionPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/SessionPassTest.php @@ -22,7 +22,7 @@ public function testProcess() { $container = new ContainerBuilder(); $container - ->register('session.storage'); // marker service + ->register('session.factory'); // marker service $container ->register('.session.do-not-use'); @@ -41,7 +41,7 @@ public function testProcessUserDefinedSession() ]; $container = new ContainerBuilder(); $container - ->register('session.storage'); // marker service + ->register('session.factory'); // marker service $container ->register('session') ->setArguments($arguments); @@ -70,7 +70,7 @@ public function testProcessUserDefinedAlias() ]; $container = new ContainerBuilder(); $container - ->register('session.storage'); // marker service + ->register('session.factory'); // marker service $container ->register('trueSession') ->setArguments($arguments); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 73dcb2c4baa28..bab22e1d23769 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -463,6 +463,7 @@ protected static function getBundleDefaultConfig() 'session' => [ 'enabled' => false, 'storage_id' => 'session.storage.native', + 'storage_factory_id' => null, 'handler_id' => 'session.handler.native_file', 'cookie_httponly' => true, 'cookie_samesite' => null, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf.php index e3f3577c1b430..41a3e2ee84ec7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf.php @@ -6,6 +6,7 @@ 'legacy_error_messages' => false, ], 'session' => [ + 'storage_factory_id' => 'session.storage.factory.native', 'handler_id' => null, ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 32f174118c98a..7aa6c50135b80 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -27,7 +27,7 @@ 'utf8' => true, ], 'session' => [ - 'storage_id' => 'session.storage.native', + 'storage_factory_id' => 'session.storage.factory.native', 'handler_id' => 'session.handler.native_file', 'name' => '_SYMFONY', 'cookie_lifetime' => 86400, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session.php index 375008c7db468..8b4c6e6e4c3b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session.php @@ -2,6 +2,7 @@ $container->loadFromExtension('framework', [ 'session' => [ + 'storage_factory_id' => 'session.storage.factory.native', 'handler_id' => null, ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto.php index 7259b07f92615..b52935c726a0f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto.php @@ -2,6 +2,7 @@ $container->loadFromExtension('framework', [ 'session' => [ + 'storage_factory_id' => 'session.storage.factory.native', 'handler_id' => null, 'cookie_secure' => 'auto', ], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto_legacy.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto_legacy.php new file mode 100644 index 0000000000000..23cd73767bd88 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_cookie_secure_auto_legacy.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', [ + 'session' => [ + 'handler_id' => null, + 'cookie_secure' => 'auto', + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_legacy.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_legacy.php new file mode 100644 index 0000000000000..e453305799971 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_legacy.php @@ -0,0 +1,8 @@ +loadFromExtension('framework', [ + 'session' => [ + 'handler_id' => null, + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml index 4686d9ffc046d..24acb3e32707c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml @@ -9,6 +9,6 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml index 1552a3ceb6e42..30fcf6b7f3929 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml @@ -8,7 +8,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml index dda2e724cc664..1e89bca965ea2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml @@ -9,6 +9,6 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 9207066f1c183..4641e702677cb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -15,7 +15,7 @@ - + text/csv diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session.xml index 599cbdee1ccc0..e91d51955e6fa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session.xml @@ -7,6 +7,6 @@ http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto.xml index 1fff3e090e88f..3023c43fc13ad 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto.xml @@ -7,6 +7,6 @@ http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto_legacy.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto_legacy.xml new file mode 100644 index 0000000000000..6893400865a8b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_cookie_secure_auto_legacy.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_legacy.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_legacy.xml new file mode 100644 index 0000000000000..326cf268d967f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_legacy.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml index d29019cf48f6d..26d1d832fcf47 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml @@ -3,4 +3,5 @@ framework: csrf_protection: ~ form: legacy_error_messages: false - session: ~ + session: + storage_factory_id: session.storage.factory.native diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 2206585863baa..67a3f1db00fef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -19,7 +19,7 @@ framework: type: xml utf8: true session: - storage_id: session.storage.native + storage_factory_id: session.storage.factory.native handler_id: session.handler.native_file name: _SYMFONY cookie_lifetime: 86400 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session.yml index d91b0c3147dfd..eb0df8d01c76c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session.yml @@ -1,3 +1,4 @@ framework: session: + storage_factory_id: session.storage.factory.native handler_id: null diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto.yml index 17fe2f5a02c03..739b49b1e6ab9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto.yml @@ -1,4 +1,5 @@ framework: session: + storage_factory_id: session.storage.factory.native handler_id: ~ cookie_secure: auto diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto_legacy.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto_legacy.yml new file mode 100644 index 0000000000000..bac546c371b19 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_cookie_secure_auto_legacy.yml @@ -0,0 +1,5 @@ +# to be removed in Symfony 6.0 +framework: + session: + handler_id: ~ + cookie_secure: auto diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_legacy.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_legacy.yml new file mode 100644 index 0000000000000..171fadd07601a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_legacy.yml @@ -0,0 +1,4 @@ +# to be removed in Symfony 6.0 +framework: + session: + handler_id: null diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 7cd3f083685a4..fd0521f98baad 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -544,7 +544,10 @@ public function testSession() $this->assertTrue($container->hasAlias(SessionInterface::class), '->registerSessionConfiguration() loads session.xml'); $this->assertEquals('fr', $container->getParameter('kernel.default_locale')); - $this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage')); + $this->assertEquals('session.storage.factory.native', (string) $container->getAlias('session.storage.factory')); + $this->assertFalse($container->has('session.storage')); + $this->assertFalse($container->has('session.storage.native')); + $this->assertFalse($container->has('session.storage.php_bridge')); $this->assertEquals('session.handler.native_file', (string) $container->getAlias('session.handler')); $options = $container->getParameter('session.storage.options'); @@ -568,6 +571,25 @@ public function testNullSessionHandler() { $container = $this->createContainerFromFile('session'); + $this->assertTrue($container->hasAlias(SessionInterface::class), '->registerSessionConfiguration() loads session.xml'); + $this->assertNull($container->getDefinition('session.storage.factory.native')->getArgument(1)); + $this->assertNull($container->getDefinition('session.storage.factory.php_bridge')->getArgument(0)); + $this->assertSame('session.handler.native_file', (string) $container->getAlias('session.handler')); + + $expected = ['session', 'initialized_session', 'logger', 'session_collector']; + $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); + $this->assertSame(false, $container->getDefinition('session.storage.factory.native')->getArgument(3)); + } + + /** + * @group legacy + */ + public function testNullSessionHandlerLegacy() + { + $this->expectDeprecation('Since 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 = $this->createContainerFromFile('session_legacy'); + $this->assertTrue($container->hasAlias(SessionInterface::class), '->registerSessionConfiguration() loads session.xml'); $this->assertNull($container->getDefinition('session.storage.native')->getArgument(1)); $this->assertNull($container->getDefinition('session.storage.php_bridge')->getArgument(0)); @@ -575,6 +597,7 @@ public function testNullSessionHandler() $expected = ['session', 'initialized_session', 'logger', 'session_collector']; $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); + $this->assertSame(false, $container->getDefinition('session.storage.factory.native')->getArgument(3)); } public function testRequest() @@ -1479,6 +1502,19 @@ public function testSessionCookieSecureAuto() { $container = $this->createContainerFromFile('session_cookie_secure_auto'); + $expected = ['session', 'initialized_session', 'logger', 'session_collector']; + $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); + } + + /** + * @group legacy + */ + public function testSessionCookieSecureAutoLegacy() + { + $this->expectDeprecation('Since 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 = $this->createContainerFromFile('session_cookie_secure_auto_legacy'); + $expected = ['session', 'initialized_session', 'logger', 'session_collector', 'session_storage', 'request_stack']; $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml index 432e35bd2f24d..1ec484a7f5208 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml @@ -5,6 +5,7 @@ framework: secret: '%secret%' default_locale: '%env(LOCALE)%' session: + storage_factory_id: session.storage.factory.native cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%' parameters: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDump/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDump/config.yml index 5754ba969365b..be0eab4d5645e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDump/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDump/config.yml @@ -7,7 +7,8 @@ framework: fragments: true profiler: true router: true - session: true + session: + storage_factory_id: session.storage.factory.native request: true assets: true translator: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml index 50078d4fd59c4..bfe7e24b338d7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml @@ -9,7 +9,7 @@ framework: test: true default_locale: en session: - storage_id: session.storage.mock_file + storage_factory_id: session.storage.factory.mock_file services: logger: { class: Psr\Log\NullLogger } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php index cedfe3a8d7f48..5ba017f51e386 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php @@ -41,7 +41,7 @@ public function process(ContainerBuilder $container) TokenStorageInterface::class => new BoundArgument(new Reference('security.untracked_token_storage'), false), ]); - if (!$container->has('session.storage')) { + if (!$container->has('session.factory') && !$container->has('session.storage')) { $container->setAlias('security.token_storage', 'security.untracked_token_storage')->setPublic(true); $container->getDefinition('security.untracked_token_storage')->addTag('kernel.reset', ['method' => 'reset']); } elseif ($container->hasDefinition('security.context_listener')) { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterTokenUsageTrackingPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterTokenUsageTrackingPassTest.php index f997a62cd5cf1..993601ee8e43e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterTokenUsageTrackingPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterTokenUsageTrackingPassTest.php @@ -18,7 +18,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionFactory; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorageFactory; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage; use Symfony\Component\Security\Http\Firewall\ContextListener; @@ -66,7 +68,7 @@ public function testContextListenerEnablesUsageTrackingIfSupportedByTokenStorage $container = new ContainerBuilder(); $container->setParameter('security.token_storage.class', UsageTrackingTokenStorage::class); - $container->register('session.storage', NativeSessionStorage::class); + $container->register('session.factory', SessionFactory::class); $container->register('security.context_listener', ContextListener::class) ->setArguments([ new Reference('security.untracked_token_storage'), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 3ffa6cb015bc7..03db66cc98a6d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -398,6 +398,7 @@ public function sessionConfigurationProvider() ], [ [ + 'storage_factory_id' => 'session.storage.factory.native', 'cookie_secure' => true, 'cookie_samesite' => 'lax', 'save_path' => null, diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Anonymous/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Anonymous/config.yml index c0f9a7c19115f..9d804818d8885 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Anonymous/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Anonymous/config.yml @@ -7,7 +7,7 @@ framework: test: ~ default_locale: en session: - storage_id: session.storage.mock_file + storage_factory_id: session.storage.factory.mock_file profiler: { only_exceptions: false } services: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/config.yml index 45bde5bda3f22..1beade7dbe6f4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/config.yml @@ -5,7 +5,7 @@ framework: default_locale: en profiler: false session: - storage_id: session.storage.mock_file + storage_factory_id: session.storage.factory.mock_file services: logger: { class: Psr\Log\NullLogger } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml index bcb5d374b002e..b862b04a63bc3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml @@ -9,7 +9,7 @@ framework: test: ~ default_locale: en session: - storage_id: session.storage.mock_file + storage_factory_id: session.storage.factory.mock_file profiler: { only_exceptions: false } services: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml index 75fa554462bad..81ef3399a97ba 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml @@ -5,7 +5,7 @@ framework: default_locale: en profiler: false session: - storage_id: session.storage.mock_file + storage_factory_id: session.storage.factory.mock_file services: logger: { class: Psr\Log\NullLogger } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml index 298574d2b1086..caadeeb7a86e2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml @@ -3,6 +3,7 @@ imports: framework: session: + storage_factory_id: session.storage.factory.mock_file cookie_secure: auto cookie_samesite: lax diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml index e145253080d71..94a00c01fc367 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml @@ -10,7 +10,7 @@ framework: test: ~ default_locale: en session: - storage_id: session.storage.mock_file + storage_factory_id: session.storage.factory.mock_file profiler: { only_exceptions: false } services: diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index f2a710e1cee7c..82b239960200e 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -39,7 +39,7 @@ "symfony/dom-crawler": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", "symfony/form": "^4.4|^5.0", - "symfony/framework-bundle": "^5.2", + "symfony/framework-bundle": "^5.3", "symfony/process": "^4.4|^5.0", "symfony/rate-limiter": "^5.2", "symfony/serializer": "^4.4|^5.0", diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php index 1d3bcf33cdcf1..32e9d936fb8a0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php @@ -43,7 +43,7 @@ protected function configureContainer(ContainerBuilder $containerBuilder, Loader $containerBuilder->loadFromExtension('framework', [ 'secret' => 'foo-secret', 'profiler' => ['only_exceptions' => false], - 'session' => ['storage_id' => 'session.storage.mock_file'], + 'session' => ['storage_factory_id' => 'session.storage.factory.mock_file'], 'router' => ['utf8' => true], ]); diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index 46b7e78567865..277f8095c0686 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/config": "^4.4|^5.0", - "symfony/framework-bundle": "^5.1", + "symfony/framework-bundle": "^5.3", "symfony/http-kernel": "^5.2", "symfony/routing": "^4.4|^5.0", "symfony/twig-bundle": "^4.4|^5.0", diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index b71cc54d5eec4..d6a57aae1400f 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3 --- + * Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes * Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException` * Add the `RequestStack::getSession` method * Deprecate the `NamespacedAttributeBag` class diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionFactory.php b/src/Symfony/Component/HttpFoundation/Session/SessionFactory.php new file mode 100644 index 0000000000000..a9982ed0c3f54 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/SessionFactory.php @@ -0,0 +1,40 @@ + + * + * 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\RequestStack; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Session::class); + +/** + * @author Jérémy Derussé + */ +class SessionFactory +{ + private $requestStack; + private $storageFactory; + private $usageReporter; + + public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null) + { + $this->requestStack = $requestStack; + $this->storageFactory = $storageFactory; + $this->usageReporter = $usageReporter; + } + + public function createSession(): SessionInterface + { + return new Session($this->storageFactory->createStorage($this->requestStack->getMasterRequest()), null, null, $this->usageReporter); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php new file mode 100644 index 0000000000000..d0da1e16922fc --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php @@ -0,0 +1,42 @@ + + * + * 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; + +// Help opcache.preload discover always-needed symbols +class_exists(MockFileSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class MockFileSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $savePath; + private $name; + private $metaBag; + + /** + * @see MockFileSessionStorage constructor. + */ + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->savePath = $savePath; + $this->name = $name; + $this->metaBag = $metaBag; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php new file mode 100644 index 0000000000000..a7d7411ff3fc9 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php @@ -0,0 +1,49 @@ + + * + * 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; + +// Help opcache.preload discover always-needed symbols +class_exists(NativeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class NativeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $options; + private $handler; + private $metaBag; + private $secure; + + /** + * @see NativeSessionStorage constructor. + */ + public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->options = $options; + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.php new file mode 100644 index 0000000000000..173ef71dea424 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.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\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(PhpBridgeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $handler; + private $metaBag; + private $secure; + + /** + * @see PhpBridgeSessionStorage constructor. + */ + public function __construct($handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/ServiceSessionFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/ServiceSessionFactory.php new file mode 100644 index 0000000000000..d17c60aebaac1 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/ServiceSessionFactory.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\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/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageFactoryInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageFactoryInterface.php new file mode 100644 index 0000000000000..f0387b5e12866 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageFactoryInterface.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\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + */ +interface SessionStorageFactoryInterface +{ + /** + * Creates a new instance of SessionStorageInterface + */ + public function createStorage(?Request $request): SessionStorageInterface; +} From 56545fd270fc3f6edc930857184c1049c8f66215 Mon Sep 17 00:00:00 2001 From: Beno!t POLASZEK Date: Fri, 12 Feb 2021 19:12:09 +0100 Subject: [PATCH 149/188] [DependencyInjection] Negated (not:) env var processor --- .../Component/DependencyInjection/CHANGELOG.md | 1 + .../DependencyInjection/EnvVarProcessor.php | 7 +++++-- .../RegisterEnvVarProcessorsPassTest.php | 1 + .../Tests/EnvVarProcessorTest.php | 16 ++++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 05681ad54e27d..4d517c4f1b006 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `ServicesConfigurator::remove()` in the PHP-DSL + * added `%env(not:...)%` processor to negate boolean values 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index a85fc64b04f8d..efa0c526cd4e9 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -41,6 +41,7 @@ public static function getProvidedTypes() return [ 'base64' => 'string', 'bool' => 'bool', + 'not' => 'bool', 'const' => 'bool|int|float|string|array', 'csv' => 'array', 'file' => 'string', @@ -191,8 +192,10 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv) return (string) $env; } - if ('bool' === $prefix) { - return (bool) (filter_var($env, \FILTER_VALIDATE_BOOLEAN) ?: filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)); + 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)); + + return 'not' === $prefix ? !$env : $env; } if ('int' === $prefix) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php index 86c270ebcc521..4c09243a40a84 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php @@ -33,6 +33,7 @@ public function testSimpleProcessor() 'foo' => ['string'], 'base64' => ['string'], 'bool' => ['bool'], + 'not' => ['bool'], 'const' => ['bool', 'int', 'float', 'string', 'array'], 'csv' => ['array'], 'file' => ['string'], diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index ab5f24b2ecba2..6a2460c9f296b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -64,6 +64,22 @@ public function testGetEnvBool($value, $processed) $this->assertSame($processed, $result); } + /** + * @dataProvider validBools + */ + public function testGetEnvNot($value, $processed) + { + $processor = new EnvVarProcessor(new Container()); + + $result = $processor->getEnv('not', 'foo', function ($name) use ($value) { + $this->assertSame('foo', $name); + + return $value; + }); + + $this->assertSame(!$processed, $result); + } + public function validBools() { return [ From 2f0bc30e3826c7efea11a47ec178daaa92a4c4e7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 14 Feb 2021 11:23:24 +0100 Subject: [PATCH 150/188] Fix CS --- src/Symfony/Component/DependencyInjection/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 4d517c4f1b006..c801155694754 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -5,7 +5,7 @@ CHANGELOG --- * Add `ServicesConfigurator::remove()` in the PHP-DSL - * added `%env(not:...)%` processor to negate boolean values + * Add `%env(not:...)%` processor to negate boolean values 5.2.0 ----- From 332817ac291b8344ec77266a4f9e613b6a05c5e2 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sat, 13 Feb 2021 15:26:00 +0100 Subject: [PATCH 151/188] Use bcrypt as default password hash algorithm for "native" and "auto" --- .../Component/PasswordHasher/CHANGELOG.md | 1 + .../Hasher/NativePasswordHasher.php | 6 +++--- .../Hasher/PasswordHasherFactory.php | 16 +++++++++++----- .../Tests/Hasher/NativePasswordHasherTest.php | 12 ++++++++++-- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/PasswordHasher/CHANGELOG.md b/src/Symfony/Component/PasswordHasher/CHANGELOG.md index 22693a3bf9dca..53ea3f1c17b6e 100644 --- a/src/Symfony/Component/PasswordHasher/CHANGELOG.md +++ b/src/Symfony/Component/PasswordHasher/CHANGELOG.md @@ -2,3 +2,4 @@ --- * Add the component + * Use `bcrypt` as default algorithm in `NativePasswordHasher` diff --git a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php index f147868ad8dfc..b6090bc2aca3c 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php @@ -29,7 +29,7 @@ final class NativePasswordHasher implements PasswordHasherInterface private $options; /** - * @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm + * @param string|null $algo An algorithm supported by password_hash() or null to use the best available algorithm */ public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, ?string $algo = null) { @@ -52,11 +52,11 @@ public function __construct(int $opsLimit = null, int $memLimit = null, int $cos $algos = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; if (\defined('PASSWORD_ARGON2I')) { - $this->algo = $algos[2] = $algos['argon2i'] = (string) \PASSWORD_ARGON2I; + $algos[2] = $algos['argon2i'] = (string) \PASSWORD_ARGON2I; } if (\defined('PASSWORD_ARGON2ID')) { - $this->algo = $algos[3] = $algos['argon2id'] = (string) \PASSWORD_ARGON2ID; + $algos[3] = $algos['argon2id'] = (string) \PASSWORD_ARGON2ID; } if (null !== $algo) { diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php index 03bbe7350c2cf..da2e262c82f4e 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php @@ -11,9 +11,9 @@ namespace Symfony\Component\PasswordHasher\Hasher; -use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; use Symfony\Component\PasswordHasher\Exception\LogicException; use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; /** * A generic hasher factory implementation. @@ -103,9 +103,15 @@ private function createHasher(array $config, bool $isExtra = false): PasswordHas private function getHasherConfigFromAlgorithm(array $config): array { if ('auto' === $config['algorithm']) { - $hasherChain = []; // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly - foreach ([SodiumPasswordHasher::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm']] as $algo) { + if (SodiumPasswordHasher::isSupported()) { + $algos = ['native', 'sodium', 'pbkdf2', $config['hash_algorithm']]; + } else { + $algos = ['native', 'pbkdf2', $config['hash_algorithm']]; + } + + $hasherChain = []; + foreach ($algos as $algo) { $config['algorithm'] = $algo; $hasherChain[] = $this->createHasher($config, true); } @@ -186,7 +192,7 @@ private function getHasherConfigFromAlgorithm(array $config): array $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2I; } else { - throw new LogicException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : '')); + throw new LogicException(sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id" or "auto' : 'auto')); } return $this->getHasherConfigFromAlgorithm($config); @@ -198,7 +204,7 @@ private function getHasherConfigFromAlgorithm(array $config): array $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { - throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : '')); + throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->getHasherConfigFromAlgorithm($config); diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php index 90f48267ce36f..8132bc76933f9 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php @@ -21,13 +21,13 @@ class NativePasswordHasherTest extends TestCase { public function testCostBelowRange() { - $this->expectException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new NativePasswordHasher(null, null, 3); } public function testCostAboveRange() { - $this->expectException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new NativePasswordHasher(null, null, 32); } @@ -73,6 +73,14 @@ public function testConfiguredAlgorithm() $this->assertStringStartsWith('$2', $result); } + public function testDefaultAlgorithm() + { + $hasher = new NativePasswordHasher(); + $result = $hasher->hash('password'); + $this->assertTrue($hasher->verify($result, 'password')); + $this->assertStringStartsWith('$2', $result); + } + public function testConfiguredAlgorithmWithLegacyConstValue() { $hasher = new NativePasswordHasher(null, null, null, '1'); From 3fbf7e963d1c34d9fc2e7633658935926ede63ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sun, 14 Feb 2021 14:37:28 +0100 Subject: [PATCH 152/188] Fix: Typo --- .../PasswordHasher/Command/UserPasswordHashCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php b/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php index e4112c9c4e6cb..b74e0e0d26de8 100644 --- a/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php +++ b/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php @@ -61,7 +61,7 @@ protected function configure() ->addOption('empty-salt', null, InputOption::VALUE_NONE, 'Do not generate a salt or let the hasher generate one.') ->setHelp(<<%command.name% command hashs passwords according to your +The %command.name% command hashes passwords according to your security configuration. This command is mainly used to generate passwords for the in_memory user provider type and for changing passwords in the database while developing the application. From 2102170e43f96e5d131a41f391d42520959ecfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sun, 14 Feb 2021 15:05:11 +0100 Subject: [PATCH 153/188] Fix: Run 'php-cs-fixer fix' --- .../PasswordHasher/Command/UserPasswordHashCommand.php | 3 +-- .../Exception/InvalidPasswordException.php | 2 +- .../PasswordHasher/Exception/LogicException.php | 2 +- .../PasswordHasher/Hasher/MigratingPasswordHasher.php | 1 - .../Hasher/PasswordHasherFactoryInterface.php | 2 +- .../PasswordHasher/Hasher/SodiumPasswordHasher.php | 2 +- .../Tests/Command/UserPasswordHashCommandTest.php | 9 ++++----- .../Tests/Hasher/MessageDigestPasswordHasherTest.php | 2 +- .../Tests/Hasher/PasswordHasherFactoryTest.php | 6 +++--- .../Tests/Hasher/Pbkdf2PasswordHasherTest.php | 2 +- .../Tests/Hasher/UserPasswordHasherTest.php | 6 +++--- 11 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php b/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php index b74e0e0d26de8..edee8a2978cc5 100644 --- a/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php +++ b/src/Symfony/Component/PasswordHasher/Command/UserPasswordHashCommand.php @@ -11,7 +11,6 @@ namespace Symfony\Component\PasswordHasher\Command; - use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; @@ -36,7 +35,7 @@ class UserPasswordHashCommand extends Command { protected static $defaultName = 'security:hash-password'; - protected static $defaultDescription = "Hashes a user password"; + protected static $defaultDescription = 'Hashes a user password'; private $hasherFactory; private $userClasses; diff --git a/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php b/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php index dea9109baeb48..c70a4d5561531 100644 --- a/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php +++ b/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php @@ -13,7 +13,7 @@ /** * @author Robin Chalas -*/ + */ class InvalidPasswordException extends \RuntimeException implements ExceptionInterface { public function __construct(string $message = 'Invalid password.', int $code = 0, ?\Throwable $previous = null) diff --git a/src/Symfony/Component/PasswordHasher/Exception/LogicException.php b/src/Symfony/Component/PasswordHasher/Exception/LogicException.php index a0c425fa6fa57..f4d9f31ff5319 100644 --- a/src/Symfony/Component/PasswordHasher/Exception/LogicException.php +++ b/src/Symfony/Component/PasswordHasher/Exception/LogicException.php @@ -13,7 +13,7 @@ /** * @author Robin Chalas -*/ + */ class LogicException extends \LogicException implements ExceptionInterface { } diff --git a/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php index f48373c6e9693..0fb91d047bbc2 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php @@ -12,7 +12,6 @@ namespace Symfony\Component\PasswordHasher\Hasher; use Symfony\Component\PasswordHasher\PasswordHasherInterface; -use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; /** * Hashes passwords using the best available hasher. diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactoryInterface.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactoryInterface.php index 943a4003da5e8..038c34a318174 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactoryInterface.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactoryInterface.php @@ -11,8 +11,8 @@ namespace Symfony\Component\PasswordHasher\Hasher; -use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\User\UserInterface; /** * PasswordHasherFactoryInterface to support different password hashers for different user accounts. diff --git a/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php index 613cccd03763d..626878815b880 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php @@ -11,8 +11,8 @@ namespace Symfony\Component\PasswordHasher\Hasher; -use Symfony\Component\PasswordHasher\Exception\LogicException; use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Exception\LogicException; use Symfony\Component\PasswordHasher\PasswordHasherInterface; /** diff --git a/src/Symfony/Component/PasswordHasher/Tests/Command/UserPasswordHashCommandTest.php b/src/Symfony/Component/PasswordHasher/Tests/Command/UserPasswordHashCommandTest.php index 03cca7acd9ec6..c633e6240f6b6 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Command/UserPasswordHashCommandTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Command/UserPasswordHashCommandTest.php @@ -12,15 +12,14 @@ namespace Symfony\Component\PasswordHasher\Tests\Command; use PHPUnit\Framework\TestCase; -use Symfony\Bundle\SecurityBundle\Command\UserPasswordHasherCommand; use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\Security\Core\User\User; use Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; -use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; +use Symfony\Component\Security\Core\User\User; class UserPasswordHashCommandTest extends TestCase { @@ -240,7 +239,7 @@ public function testEncodePasswordSodiumOutput() public function testEncodePasswordNoConfigForGivenUserClass() { - $this->expectException('\RuntimeException'); + $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('No password hasher has been configured for account "Foo\Bar\User".'); $this->passwordHasherCommandTester->execute([ @@ -277,7 +276,7 @@ public function testNonInteractiveEncodePasswordUsesFirstUserClass() public function testThrowsExceptionOnNoConfiguredHashers() { - $this->expectException('RuntimeException'); + $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('There are no configured password hashers for the "security" extension.'); $tester = new CommandTester(new UserPasswordHashCommand($this->getMockBuilder(PasswordHasherFactoryInterface::class)->getMock(), [])); diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php index a6c66aee213ec..6abcb797b9c27 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php @@ -38,7 +38,7 @@ public function testHash() public function testHashAlgorithmDoesNotExist() { - $this->expectException('LogicException'); + $this->expectException(\LogicException::class); $hasher = new MessageDigestPasswordHasher('foobar'); $hasher->hash('password', ''); } diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php index 6c5205e37465f..61c17f18f24ff 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php @@ -12,11 +12,11 @@ namespace Symfony\Component\PasswordHasher\Tests\Hasher; use PHPUnit\Framework\TestCase; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; use Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserInterface; @@ -112,7 +112,7 @@ public function testGetNullNamedHasherForHasherAware() public function testGetInvalidNamedHasherForHasherAware() { - $this->expectException('RuntimeException'); + $this->expectException(\RuntimeException::class); $factory = new PasswordHasherFactory([ HasherAwareUser::class => new MessageDigestPasswordHasher('sha1'), 'hasher_name' => new MessageDigestPasswordHasher('sha256'), diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/Pbkdf2PasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/Pbkdf2PasswordHasherTest.php index 50f9c8d13e864..05785b2141c46 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/Pbkdf2PasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/Pbkdf2PasswordHasherTest.php @@ -38,7 +38,7 @@ public function testHash() public function testHashAlgorithmDoesNotExist() { - $this->expectException('LogicException'); + $this->expectException(\LogicException::class); $hasher = new Pbkdf2PasswordHasher('foobar'); $hasher->hash('password', ''); } diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/UserPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/UserPasswordHasherTest.php index 899723f2e45cb..5aaee750af1ed 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/UserPasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/UserPasswordHasherTest.php @@ -12,12 +12,12 @@ namespace Symfony\Component\PasswordHasher\Tests\Hasher; use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; -use Symfony\Component\Security\Core\User\User; use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\Security\Core\User\UserInterface; class UserPasswordHasherTest extends TestCase { From a4dd14b478ee908bf9c097b56cfac3f20ff5b23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sun, 14 Feb 2021 15:17:32 +0100 Subject: [PATCH 154/188] Fix: Use algorithm instead of algo --- .../Hasher/NativePasswordHasher.php | 22 +++++++++---------- .../Hasher/PasswordHasherFactory.php | 8 +++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php index b6090bc2aca3c..80483462c7ad1 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php @@ -25,13 +25,13 @@ final class NativePasswordHasher implements PasswordHasherInterface { use CheckPasswordLengthTrait; - private $algo = \PASSWORD_BCRYPT; + private $algorithm = \PASSWORD_BCRYPT; private $options; /** - * @param string|null $algo An algorithm supported by password_hash() or null to use the best available algorithm + * @param string|null $algorithm An algorithm supported by password_hash() or null to use the best available algorithm */ - public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, ?string $algo = null) + public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, ?string $algorithm = null) { $cost = $cost ?? 13; $opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); @@ -49,18 +49,18 @@ public function __construct(int $opsLimit = null, int $memLimit = null, int $cos throw new \InvalidArgumentException('$cost must be in the range of 4-31.'); } - $algos = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; + $algorithms = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; if (\defined('PASSWORD_ARGON2I')) { - $algos[2] = $algos['argon2i'] = (string) \PASSWORD_ARGON2I; + $algorithms[2] = $algorithms['argon2i'] = (string) \PASSWORD_ARGON2I; } if (\defined('PASSWORD_ARGON2ID')) { - $algos[3] = $algos['argon2id'] = (string) \PASSWORD_ARGON2ID; + $algorithms[3] = $algorithms['argon2id'] = (string) \PASSWORD_ARGON2ID; } - if (null !== $algo) { - $this->algo = $algos[$algo] ?? $algo; + if (null !== $algorithm) { + $this->algorithm = $algorithms[$algorithm] ?? $algorithm; } $this->options = [ @@ -73,11 +73,11 @@ public function __construct(int $opsLimit = null, int $memLimit = null, int $cos public function hash(string $plainPassword): string { - if ($this->isPasswordTooLong($plainPassword) || ((string) \PASSWORD_BCRYPT === $this->algo && 72 < \strlen($plainPassword))) { + if ($this->isPasswordTooLong($plainPassword) || ((string) \PASSWORD_BCRYPT === $this->algorithm && 72 < \strlen($plainPassword))) { throw new InvalidPasswordException(); } - return password_hash($plainPassword, $this->algo, $this->options); + return password_hash($plainPassword, $this->algorithm, $this->options); } public function verify(string $hashedPassword, string $plainPassword): bool @@ -107,6 +107,6 @@ public function verify(string $hashedPassword, string $plainPassword): bool */ public function needsRehash(string $hashedPassword): bool { - return password_needs_rehash($hashedPassword, $this->algo, $this->options); + return password_needs_rehash($hashedPassword, $this->algorithm, $this->options); } } diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php index da2e262c82f4e..b75573f746e89 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php @@ -105,14 +105,14 @@ private function getHasherConfigFromAlgorithm(array $config): array if ('auto' === $config['algorithm']) { // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly if (SodiumPasswordHasher::isSupported()) { - $algos = ['native', 'sodium', 'pbkdf2', $config['hash_algorithm']]; + $algorithms = ['native', 'sodium', 'pbkdf2', $config['hash_algorithm']]; } else { - $algos = ['native', 'pbkdf2', $config['hash_algorithm']]; + $algorithms = ['native', 'pbkdf2', $config['hash_algorithm']]; } $hasherChain = []; - foreach ($algos as $algo) { - $config['algorithm'] = $algo; + foreach ($algorithms as $algorithm) { + $config['algorithm'] = $algorithm; $hasherChain[] = $this->createHasher($config, true); } From 5f8c7363032bbc1936515c5f8045bcf519a57326 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 14 Feb 2021 18:34:21 +0100 Subject: [PATCH 155/188] Fix package name --- src/Symfony/Component/Notifier/Bridge/SpotHit/composer.json | 2 +- .../Component/Notifier/Exception/UnsupportedSchemeException.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/SpotHit/composer.json b/src/Symfony/Component/Notifier/Bridge/SpotHit/composer.json index a24735398d4db..32ec5f8b01283 100644 --- a/src/Symfony/Component/Notifier/Bridge/SpotHit/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/SpotHit/composer.json @@ -1,5 +1,5 @@ { - "name": "symfony/spothit-notifier", + "name": "symfony/spot-hit-notifier", "type": "symfony-bridge", "description": "Symfony Spot-Hit Notifier Bridge", "keywords": ["sms", "spot-hit", "notifier", "symfony"], diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index f147e5485cc92..06bb494d5f86c 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -70,7 +70,7 @@ class UnsupportedSchemeException extends LogicException ], 'spothit' => [ 'class' => Bridge\SpotHit\SpotHitTransportFactory::class, - 'package' => 'symfony/spothit-notifier', + 'package' => 'symfony/spot-hit-notifier', ], 'ovhcloud' => [ 'class' => Bridge\OvhCloud\OvhCloudTransportFactory::class, From 6ed759189d20b13c82574bed6703172c03da0665 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 15 Feb 2021 15:46:52 +0100 Subject: [PATCH 156/188] [Workflow] Deprecate InvalidTokenConfigurationException --- UPGRADE-5.3.md | 5 +++++ UPGRADE-6.0.md | 5 +++++ src/Symfony/Component/Workflow/CHANGELOG.md | 5 +++++ .../Exception/InvalidTokenConfigurationException.php | 4 ++++ 4 files changed, 19 insertions(+) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 9ede8f6e8120f..83435825d6d6c 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -93,3 +93,8 @@ Uid --- * Replaced `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()` + +Workflow +-------- + + * Deprecate `InvalidTokenConfigurationException` diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 85209e010e11f..64dc873af4a86 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -263,6 +263,11 @@ Validator ->addDefaultDoctrineAnnotationReader(); ``` +Workflow +-------- + + * Remove `InvalidTokenConfigurationException` + Yaml ---- diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index 3d44a3b6e83f8..553acdb13b8e5 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3 +--- + + * Deprecate `InvalidTokenConfigurationException` + 5.2.0 ----- diff --git a/src/Symfony/Component/Workflow/Exception/InvalidTokenConfigurationException.php b/src/Symfony/Component/Workflow/Exception/InvalidTokenConfigurationException.php index a70fd4c98ddff..b287b4a849cca 100644 --- a/src/Symfony/Component/Workflow/Exception/InvalidTokenConfigurationException.php +++ b/src/Symfony/Component/Workflow/Exception/InvalidTokenConfigurationException.php @@ -11,10 +11,14 @@ namespace Symfony\Component\Workflow\Exception; +trigger_deprecation('symfony/workflow', '5.3', sprintf('The "%s" class is deprecated.', InvalidTokenConfigurationException::class)); + /** * Thrown by GuardListener when there is no token set, but guards are placed on a transition. * * @author Matt Johnson + * + * @deprecated since Symfony 5.3 */ class InvalidTokenConfigurationException extends LogicException { From 7229fa1d8fbd532cd4417db59ce03adb36f0f4eb Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Mon, 7 Dec 2020 17:36:04 +0100 Subject: [PATCH 157/188] [Serializer] Allow to provide (de)normalization context in mapping --- .../Serializer/Annotation/Context.php | 93 ++++++++ src/Symfony/Component/Serializer/CHANGELOG.md | 1 + .../Serializer/Mapping/AttributeMetadata.php | 96 +++++++- .../Mapping/AttributeMetadataInterface.php | 30 +++ .../Mapping/Loader/AnnotationLoader.php | 27 +++ .../Mapping/Loader/XmlFileLoader.php | 44 ++++ .../Mapping/Loader/YamlFileLoader.php | 17 ++ .../serializer-mapping-1.0.xsd | 28 ++- .../Normalizer/AbstractNormalizer.php | 14 +- .../Normalizer/AbstractObjectNormalizer.php | 59 ++++- .../Tests/Annotation/ContextTest.php | 217 ++++++++++++++++++ .../Annotations/BadMethodContextDummy.php | 28 +++ .../Fixtures/Annotations/ContextDummy.php | 50 ++++ .../Annotations/ContextDummyParent.php | 30 +++ .../Attributes/BadMethodContextDummy.php | 26 +++ .../Fixtures/Attributes/ContextDummy.php | 42 ++++ .../Attributes/ContextDummyParent.php | 26 +++ .../Tests/Fixtures/serialization.xml | 55 +++++ .../Tests/Fixtures/serialization.yml | 28 +++ .../Tests/Mapping/AttributeMetadataTest.php | 71 ++++++ .../Mapping/Loader/AnnotationLoaderTest.php | 28 +++ .../Features/ContextMappingTestTrait.php | 78 +++++++ .../Mapping/Loader/XmlFileLoaderTest.php | 9 + .../Mapping/Loader/YamlFileLoaderTest.php | 8 + .../Features/ContextMetadataTestTrait.php | 92 ++++++++ .../Tests/Normalizer/ObjectNormalizerTest.php | 2 + .../Component/Serializer/composer.json | 1 + 27 files changed, 1183 insertions(+), 17 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Annotation/Context.php create mode 100644 src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/BadMethodContextDummy.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummy.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummyParent.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/BadMethodContextDummy.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummy.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummyParent.php create mode 100644 src/Symfony/Component/Serializer/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php create mode 100644 src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php diff --git a/src/Symfony/Component/Serializer/Annotation/Context.php b/src/Symfony/Component/Serializer/Annotation/Context.php new file mode 100644 index 0000000000000..08e1f7cf69a5c --- /dev/null +++ b/src/Symfony/Component/Serializer/Annotation/Context.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\Serializer\Annotation; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @Context(). + * + * @Annotation + * @Target({"PROPERTY", "METHOD"}) + * + * @author Maxime Steinhausser + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class Context +{ + private $context; + private $normalizationContext; + private $denormalizationContext; + private $groups; + + /** + * @throws InvalidArgumentException + */ + public function __construct(array $options = [], array $context = [], array $normalizationContext = [], array $denormalizationContext = [], array $groups = []) + { + if (!$context) { + if (!array_intersect((array_keys($options)), ['normalizationContext', 'groups', 'context', 'value', 'denormalizationContext'])) { + // gracefully supports context as first, unnamed attribute argument if it cannot be confused with Doctrine-style options + $context = $options; + } else { + // If at least one of the options match, it's likely to be Doctrine-style options. Search for the context inside: + $context = $options['value'] ?? $options['context'] ?? []; + } + } + + $normalizationContext = $options['normalizationContext'] ?? $normalizationContext; + $denormalizationContext = $options['denormalizationContext'] ?? $denormalizationContext; + + foreach (compact(['context', 'normalizationContext', 'denormalizationContext']) as $key => $value) { + if (!\is_array($value)) { + throw new InvalidArgumentException(sprintf('Option "%s" of annotation "%s" must be an array.', $key, static::class)); + } + } + + if (!$context && !$normalizationContext && !$denormalizationContext) { + throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options of annotation "%s" must be provided as a non-empty array.', static::class)); + } + + $groups = (array) ($options['groups'] ?? $groups); + + foreach ($groups as $group) { + if (!\is_string($group)) { + throw new InvalidArgumentException(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "%s".', static::class, get_debug_type($group))); + } + } + + $this->context = $context; + $this->normalizationContext = $normalizationContext; + $this->denormalizationContext = $denormalizationContext; + $this->groups = $groups; + } + + public function getContext(): array + { + return $this->context; + } + + public function getNormalizationContext(): array + { + return $this->normalizationContext; + } + + public function getDenormalizationContext(): array + { + return $this->denormalizationContext; + } + + public function getGroups(): array + { + return $this->groups; + } +} diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 8ccc9f4c7db1a..dde0f2cf21bfe 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3 --- + * Add the ability to provide (de)normalization context using metadata (e.g. `@Symfony\Component\Serializer\Annotation\Context`) * deprecated `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead. * added normalization formats to `UidNormalizer` diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 732e0bd5908cc..36d1e92b66f39 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -59,6 +59,24 @@ class AttributeMetadata implements AttributeMetadataInterface */ public $ignore = false; + /** + * @var array[] Normalization contexts per group name ("*" applies to all groups) + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getNormalizationContexts()} instead. + */ + public $normalizationContexts = []; + + /** + * @var array[] Denormalization contexts per group name ("*" applies to all groups) + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getDenormalizationContexts()} instead. + */ + public $denormalizationContexts = []; + public function __construct(string $name) { $this->name = $name; @@ -138,6 +156,76 @@ public function isIgnored(): bool return $this->ignore; } + /** + * {@inheritdoc} + */ + public function getNormalizationContexts(): array + { + return $this->normalizationContexts; + } + + /** + * {@inheritdoc} + */ + public function getNormalizationContextForGroups(array $groups): array + { + $contexts = []; + foreach ($groups as $group) { + $contexts[] = $this->normalizationContexts[$group] ?? []; + } + + return array_merge($this->normalizationContexts['*'] ?? [], ...$contexts); + } + + /** + * {@inheritdoc} + */ + public function setNormalizationContextForGroups(array $context, array $groups = []): void + { + if (!$groups) { + $this->normalizationContexts['*'] = $context; + } + + foreach ($groups as $group) { + $this->normalizationContexts[$group] = $context; + } + } + + /** + * {@inheritdoc} + */ + public function getDenormalizationContexts(): array + { + return $this->denormalizationContexts; + } + + /** + * {@inheritdoc} + */ + public function getDenormalizationContextForGroups(array $groups): array + { + $contexts = []; + foreach ($groups as $group) { + $contexts[] = $this->denormalizationContexts[$group] ?? []; + } + + return array_merge($this->denormalizationContexts['*'] ?? [], ...$contexts); + } + + /** + * {@inheritdoc} + */ + public function setDenormalizationContextForGroups(array $context, array $groups = []): void + { + if (!$groups) { + $this->denormalizationContexts['*'] = $context; + } + + foreach ($groups as $group) { + $this->denormalizationContexts[$group] = $context; + } + } + /** * {@inheritdoc} */ @@ -157,6 +245,12 @@ public function merge(AttributeMetadataInterface $attributeMetadata) $this->serializedName = $attributeMetadata->getSerializedName(); } + // Overwrite only if both contexts are empty + if (!$this->normalizationContexts && !$this->denormalizationContexts) { + $this->normalizationContexts = $attributeMetadata->getNormalizationContexts(); + $this->denormalizationContexts = $attributeMetadata->getDenormalizationContexts(); + } + if ($ignore = $attributeMetadata->isIgnored()) { $this->ignore = $ignore; } @@ -169,6 +263,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata) */ public function __sleep() { - return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore']; + return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore', 'normalizationContexts', 'denormalizationContexts']; } } diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 9e78cf0d31743..9e5a1ae2d1797 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -75,4 +75,34 @@ public function isIgnored(): bool; * Merges an {@see AttributeMetadataInterface} with in the current one. */ public function merge(self $attributeMetadata); + + /** + * Gets all the normalization contexts per group ("*" being the base context applied to all groups). + */ + public function getNormalizationContexts(): array; + + /** + * Gets the computed normalization contexts for given groups. + */ + public function getNormalizationContextForGroups(array $groups): array; + + /** + * Sets the normalization context for given groups. + */ + public function setNormalizationContextForGroups(array $context, array $groups = []): void; + + /** + * Gets all the denormalization contexts per group ("*" being the base context applied to all groups). + */ + public function getDenormalizationContexts(): array; + + /** + * Gets the computed denormalization contexts for given groups. + */ + public function getDenormalizationContextForGroups(array $groups): array; + + /** + * Sets the denormalization context for given groups. + */ + public function setDenormalizationContextForGroups(array $context, array $groups = []): void; } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php index 5b0fec2f62be1..bd0f049c729f1 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Mapping\Loader; use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\Ignore; @@ -19,6 +20,7 @@ use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; +use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; @@ -36,6 +38,7 @@ class AnnotationLoader implements LoaderInterface Ignore::class => true, MaxDepth::class => true, SerializedName::class => true, + Context::class => true, ]; private $reader; @@ -83,6 +86,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); } elseif ($annotation instanceof Ignore) { $attributesMetadata[$property->name]->setIgnore(true); + } elseif ($annotation instanceof Context) { + $this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]); } $loaded = true; @@ -130,6 +135,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) $attributeMetadata->setSerializedName($annotation->getSerializedName()); } elseif ($annotation instanceof Ignore) { $attributeMetadata->setIgnore(true); + } elseif ($annotation instanceof Context) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $this->setAttributeContextsForGroups($annotation, $attributeMetadata); } $loaded = true; @@ -166,4 +177,20 @@ public function loadAnnotations(object $reflector): iterable yield from $this->reader->getPropertyAnnotations($reflector); } } + + private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void + { + if ($annotation->getContext()) { + $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); + $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); + } + + if ($annotation->getNormalizationContext()) { + $attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups()); + } + + if ($annotation->getDenormalizationContext()) { + $attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups()); + } + } } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php index 696007afb83ad..04cd626ab0299 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php @@ -74,6 +74,25 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) if (isset($attribute['ignore'])) { $attributeMetadata->setIgnore((bool) $attribute['ignore']); } + + foreach ($attribute->context as $node) { + $groups = (array) $node->group; + $context = $this->parseContext($node->entry); + $attributeMetadata->setNormalizationContextForGroups($context, $groups); + $attributeMetadata->setDenormalizationContextForGroups($context, $groups); + } + + foreach ($attribute->normalization_context as $node) { + $groups = (array) $node->group; + $context = $this->parseContext($node->entry); + $attributeMetadata->setNormalizationContextForGroups($context, $groups); + } + + foreach ($attribute->denormalization_context as $node) { + $groups = (array) $node->group; + $context = $this->parseContext($node->entry); + $attributeMetadata->setDenormalizationContextForGroups($context, $groups); + } } if (isset($xml->{'discriminator-map'})) { @@ -136,4 +155,29 @@ private function getClassesFromXml(): array return $classes; } + + private function parseContext(\SimpleXMLElement $nodes): array + { + $context = []; + + foreach ($nodes as $node) { + if (\count($node) > 0) { + if (\count($node->entry) > 0) { + $value = $this->parseContext($node->entry); + } else { + $value = []; + } + } else { + $value = XmlUtils::phpize($node); + } + + if (isset($node['name'])) { + $context[(string) $node['name']] = $value; + } else { + $context[] = $value; + } + } + + return $context; + } } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php index ff50e622eeadf..5975fb334d207 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php @@ -101,6 +101,23 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) $attributeMetadata->setIgnore($data['ignore']); } + + foreach ($data['contexts'] ?? [] as $line) { + $groups = $line['groups'] ?? []; + + if ($context = $line['context'] ?? false) { + $attributeMetadata->setNormalizationContextForGroups($context, $groups); + $attributeMetadata->setDenormalizationContextForGroups($context, $groups); + } + + if ($context = $line['normalization_context'] ?? false) { + $attributeMetadata->setNormalizationContextForGroups($context, $groups); + } + + if ($context = $line['denormalization_context'] ?? false) { + $attributeMetadata->setDenormalizationContextForGroups($context, $groups); + } + } } } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd index b427a36e368c1..0228e41ce10d3 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd +++ b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd @@ -60,9 +60,12 @@ Contains serialization groups and max depth for attributes. The name of the attribute should be given in the "name" option. ]]> - + - + + + + @@ -81,4 +84,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 4a03ab851a3a2..9e64e607f6daa 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -237,8 +237,7 @@ protected function getAllowedAttributes($classOrObject, array $context, bool $at return false; } - $tmpGroups = $context[self::GROUPS] ?? $this->defaultContext[self::GROUPS] ?? null; - $groups = (\is_array($tmpGroups) || is_scalar($tmpGroups)) ? (array) $tmpGroups : false; + $groups = $this->getGroups($context); $allowedAttributes = []; $ignoreUsed = false; @@ -250,14 +249,14 @@ protected function getAllowedAttributes($classOrObject, array $context, bool $at // If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties() if ( !$ignore && - (false === $groups || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) && + ([] === $groups || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) && $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context) ) { $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata; } } - if (!$ignoreUsed && false === $groups && $allowExtraAttributes) { + if (!$ignoreUsed && [] === $groups && $allowExtraAttributes) { // Backward Compatibility with the code using this method written before the introduction of @Ignore return false; } @@ -265,6 +264,13 @@ protected function getAllowedAttributes($classOrObject, array $context, bool $at return $allowedAttributes; } + protected function getGroups(array $context): array + { + $groups = $context[self::GROUPS] ?? $this->defaultContext[self::GROUPS] ?? []; + + return is_scalar($groups) ? (array) $groups : $groups; + } + /** * Is this attribute allowed? * diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 36099aa385ab7..6a151c31b7647 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -175,9 +175,10 @@ public function normalize($object, string $format = null, array $context = []) continue; } - $attributeValue = $this->getAttributeValue($object, $attribute, $format, $context); + $attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context); + $attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext); if ($maxDepthReached) { - $attributeValue = $maxDepthHandler($attributeValue, $object, $attribute, $format, $context); + $attributeValue = $maxDepthHandler($attributeValue, $object, $attribute, $format, $attributeContext); } /** @@ -185,14 +186,14 @@ public function normalize($object, string $format = null, array $context = []) */ $callback = $context[self::CALLBACKS][$attribute] ?? $this->defaultContext[self::CALLBACKS][$attribute] ?? null; if ($callback) { - $attributeValue = $callback($attributeValue, $object, $attribute, $format, $context); + $attributeValue = $callback($attributeValue, $object, $attribute, $format, $attributeContext); } if (null !== $attributeValue && !is_scalar($attributeValue)) { $stack[$attribute] = $attributeValue; } - $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $context); + $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $attributeContext); } foreach ($stack as $attribute => $attributeValue) { @@ -200,7 +201,10 @@ public function normalize($object, string $format = null, array $context = []) throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); } - $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute, $format)), $class, $format, $context); + $attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context); + $childContext = $this->createChildContext($attributeContext, $attribute, $format); + + $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $childContext), $class, $format, $attributeContext); } if (isset($context[self::PRESERVE_EMPTY_OBJECTS]) && !\count($data)) { @@ -210,6 +214,39 @@ public function normalize($object, string $format = null, array $context = []) return $data; } + /** + * Computes the normalization context merged with current one. Metadata always wins over global context, as more specific. + */ + private function getAttributeNormalizationContext($object, string $attribute, array $context): array + { + if (null === $metadata = $this->getAttributeMetadata($object, $attribute)) { + return $context; + } + + return array_merge($context, $metadata->getNormalizationContextForGroups($this->getGroups($context))); + } + + /** + * Computes the denormalization context merged with current one. Metadata always wins over global context, as more specific. + */ + private function getAttributeDenormalizationContext(string $class, string $attribute, array $context): array + { + if (null === $metadata = $this->getAttributeMetadata($class, $attribute)) { + return $context; + } + + return array_merge($context, $metadata->getDenormalizationContextForGroups($this->getGroups($context))); + } + + private function getAttributeMetadata($objectOrClass, string $attribute): ?AttributeMetadataInterface + { + if (!$this->classMetadataFactory) { + return null; + } + + return $this->classMetadataFactory->getMetadataFor($objectOrClass)->getAttributesMetadata()[$attribute] ?? null; + } + /** * {@inheritdoc} */ @@ -312,8 +349,10 @@ public function denormalize($data, string $type, string $format = null, array $c $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); foreach ($normalizedData as $attribute => $value) { + $attributeContext = $this->getAttributeDenormalizationContext($resolvedClass, $attribute, $context); + if ($this->nameConverter) { - $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); + $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $attributeContext); } if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($resolvedClass, $attribute, $format, $context)) { @@ -324,16 +363,16 @@ public function denormalize($data, string $type, string $format = null, array $c continue; } - if ($context[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) { + if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) { try { - $context[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $context); + $attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext); } catch (NoSuchPropertyException $e) { } } - $value = $this->validateAndDenormalize($resolvedClass, $attribute, $value, $format, $context); + $value = $this->validateAndDenormalize($resolvedClass, $attribute, $value, $format, $attributeContext); try { - $this->setAttributeValue($object, $attribute, $value, $format, $context); + $this->setAttributeValue($object, $attribute, $value, $format, $attributeContext); } catch (InvalidArgumentException $e) { throw new NotNormalizableValueException(sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $e->getCode(), $e); } diff --git a/src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php b/src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php new file mode 100644 index 0000000000000..a79178f1ba95d --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Annotation; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @author Maxime Steinhausser + */ +class ContextTest extends TestCase +{ + use VarDumperTestTrait; + + protected function setUp(): void + { + $this->setUpVarDumper([], CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_TRAILING_COMMA); + } + + /** + * @dataProvider provideTestThrowsOnEmptyContextData + */ + public function testThrowsOnEmptyContext(callable $factory) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('At least one of the "context", "normalizationContext", or "denormalizationContext" options of annotation "Symfony\Component\Serializer\Annotation\Context" must be provided as a non-empty array.'); + + $factory(); + } + + public function provideTestThrowsOnEmptyContextData(): iterable + { + yield 'constructor: empty args' => [function () { new Context([]); }]; + + yield 'doctrine-style: value option as empty array' => [function () { new Context(['value' => []]); }]; + yield 'doctrine-style: context option as empty array' => [function () { new Context(['context' => []]); }]; + yield 'doctrine-style: context option not provided' => [function () { new Context(['groups' => ['group_1']]); }]; + + if (\PHP_VERSION_ID >= 80000) { + yield 'named args: empty context' => [function () { + eval('return new Symfony\Component\Serializer\Annotation\Context(context: []);'); + }]; + } + } + + /** + * @dataProvider provideTestThrowsOnNonArrayContextData + */ + public function testThrowsOnNonArrayContext(array $options) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Option "%s" of annotation "%s" must be an array.', key($options), Context::class)); + + new Context($options); + } + + public function provideTestThrowsOnNonArrayContextData(): iterable + { + yield 'non-array context' => [['context' => 'not_an_array']]; + yield 'non-array normalization context' => [['normalizationContext' => 'not_an_array']]; + yield 'non-array denormalization context' => [['normalizationContext' => 'not_an_array']]; + } + + public function testInvalidGroupOption() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "stdClass"', Context::class)); + + new Context(['context' => ['foo' => 'bar'], 'groups' => ['fine', new \stdClass()]]); + } + + public function testInvalidGroupArgument() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "stdClass"', Context::class)); + + new Context([], ['foo' => 'bar'], [], [], ['fine', new \stdClass()]); + } + + public function testAsFirstArg() + { + $context = new Context(['foo' => 'bar']); + + self::assertSame(['foo' => 'bar'], $context->getContext()); + self::assertEmpty($context->getNormalizationContext()); + self::assertEmpty($context->getDenormalizationContext()); + self::assertEmpty($context->getGroups()); + } + + public function testAsContextArg() + { + $context = new Context([], ['foo' => 'bar']); + + self::assertSame(['foo' => 'bar'], $context->getContext()); + self::assertEmpty($context->getNormalizationContext()); + self::assertEmpty($context->getDenormalizationContext()); + self::assertEmpty($context->getGroups()); + } + + /** + * @dataProvider provideValidInputs + */ + public function testValidInputs(callable $factory, string $expectedDump) + { + self::assertDumpEquals($expectedDump, $factory()); + } + + public function provideValidInputs(): iterable + { + yield 'doctrine-style: with context option' => [ + function () { return new Context(['context' => ['foo' => 'bar']]); }, + $expected = << "bar", + ] + -normalizationContext: [] + -denormalizationContext: [] + -groups: [] +} +DUMP + ]; + + yield 'constructor: with context arg' => [ + function () { return new Context([], ['foo' => 'bar']); }, + $expected, + ]; + + yield 'doctrine-style: with normalization context option' => [ + function () { return new Context(['normalizationContext' => ['foo' => 'bar']]); }, + $expected = << "bar", + ] + -denormalizationContext: [] + -groups: [] +} +DUMP + ]; + + yield 'constructor: with normalization context arg' => [ + function () { return new Context([], [], ['foo' => 'bar']); }, + $expected, + ]; + + yield 'doctrine-style: with denormalization context option' => [ + function () { return new Context(['denormalizationContext' => ['foo' => 'bar']]); }, + $expected = << "bar", + ] + -groups: [] +} +DUMP + ]; + + yield 'constructor: with denormalization context arg' => [ + function () { return new Context([], [], [], ['foo' => 'bar']); }, + $expected, + ]; + + yield 'doctrine-style: with groups option as string' => [ + function () { return new Context(['context' => ['foo' => 'bar'], 'groups' => 'a']); }, + << "bar", + ] + -normalizationContext: [] + -denormalizationContext: [] + -groups: [ + "a", + ] +} +DUMP + ]; + + yield 'doctrine-style: with groups option as array' => [ + function () { return new Context(['context' => ['foo' => 'bar'], 'groups' => ['a', 'b']]); }, + $expected = << "bar", + ] + -normalizationContext: [] + -denormalizationContext: [] + -groups: [ + "a", + "b", + ] +} +DUMP + ]; + + yield 'constructor: with groups arg' => [ + function () { return new Context([], ['foo' => 'bar'], [], [], ['a', 'b']); }, + $expected, + ]; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/BadMethodContextDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/BadMethodContextDummy.php new file mode 100644 index 0000000000000..77b3884de5cb1 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/BadMethodContextDummy.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\Serializer\Tests\Fixtures\Annotations; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class BadMethodContextDummy extends ContextDummyParent +{ + /** + * @Context({ "foo" = "bar" }) + */ + public function badMethod() + { + return 'bad_method'; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummy.php new file mode 100644 index 0000000000000..804df290f0295 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummy.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\Serializer\Tests\Fixtures\Annotations; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class ContextDummy extends ContextDummyParent +{ + /** + * @Context({ "foo" = "value", "bar" = "value", "nested" = { + * "nested_key" = "nested_value", + * }, "array": { "first", "second" } }) + * @Context({ "bar" = "value_for_group_a" }, groups = "a") + */ + public $foo; + + /** + * @Context( + * normalizationContext = { "format" = "d/m/Y" }, + * denormalizationContext = { "format" = "m-d-Y H:i" }, + * groups = {"a", "b"} + * ) + */ + public $bar; + + /** + * @Context(normalizationContext={ "prop" = "dummy_value" }) + */ + public $overriddenParentProperty; + + /** + * @Context({ "method" = "method_with_context" }) + */ + public function getMethodWithContext() + { + return 'method_with_context'; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummyParent.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummyParent.php new file mode 100644 index 0000000000000..b7b286c372fa3 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/ContextDummyParent.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\Serializer\Tests\Fixtures\Annotations; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class ContextDummyParent +{ + /** + * @Context(normalizationContext={ "prop" = "dummy_parent_value" }) + */ + public $parentProperty; + + /** + * @Context(normalizationContext={ "prop" = "dummy_parent_value" }) + */ + public $overriddenParentProperty; +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/BadMethodContextDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/BadMethodContextDummy.php new file mode 100644 index 0000000000000..5c6c82e653603 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/BadMethodContextDummy.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class BadMethodContextDummy extends ContextDummyParent +{ + #[Context([ "foo" => "bar" ])] + public function badMethod() + { + return 'bad_method'; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummy.php new file mode 100644 index 0000000000000..447b80d6a951f --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummy.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class ContextDummy extends ContextDummyParent +{ + #[Context(['foo' => 'value', 'bar' => 'value', 'nested' => [ + 'nested_key' => 'nested_value' + ], 'array' => ['first', 'second']])] + #[Context(context: ['bar' => 'value_for_group_a'], groups: ['a'])] + public $foo; + + #[Context( + normalizationContext: ['format' => 'd/m/Y'], + denormalizationContext: ['format' => 'm-d-Y H:i'], + groups: ['a', 'b'], + )] + public $bar; + + #[Context(normalizationContext: ['prop' => 'dummy_value'])] + public $overriddenParentProperty; + + #[Context(['method' => 'method_with_context'])] + public function getMethodWithContext() + { + return 'method_with_context'; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummyParent.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummyParent.php new file mode 100644 index 0000000000000..9480c953e78c7 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/ContextDummyParent.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class ContextDummyParent +{ + #[Context(normalizationContext: ['prop' => 'dummy_parent_value'])] + public $parentProperty; + + #[Context(normalizationContext: ['prop' => 'dummy_parent_value'])] + public $overriddenParentProperty; +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml index 635253dd8e805..da61e0acce8dc 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml @@ -39,4 +39,59 @@ + + + + dummy_parent_value + + + + + dummy_parent_value + + + + + + + + value + value + + nested_value + + + first + second + + + + a + value_for_group_a + + + + + a + b + d/m/Y + + + a + b + m-d-Y H:i + + + + + dummy_value + + + + + method_with_context + + + + diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml index 5b212c8914aea..80100e8260622 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml @@ -30,3 +30,31 @@ ignore: true ignored2: ignore: true + +Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent: + attributes: + parentProperty: + contexts: + - { normalization_context: { prop: dummy_parent_value } } + overriddenParentProperty: + contexts: + - { normalization_context: { prop: dummy_parent_value } } + +Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy: + attributes: + foo: + contexts: + - context: { foo: value, bar: value, nested: { nested_key: nested_value }, array: [first, second] } + - context: { bar: value_for_group_a } + groups: [a] + bar: + contexts: + - normalization_context: { format: 'd/m/Y' } + denormalization_context: { format: 'm-d-Y H:i' } + groups: [a, b] + overriddenParentProperty: + contexts: + - normalization_context: { prop: dummy_value } + methodWithContext: + contexts: + - context: { method: method_with_context } diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php index 6b8f5864f23c1..8fc4b8b49865c 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php @@ -66,6 +66,57 @@ public function testIgnore() $this->assertTrue($attributeMetadata->isIgnored()); } + public function testSetContexts() + { + $metadata = new AttributeMetadata('a1'); + $metadata->setNormalizationContextForGroups(['foo' => 'default', 'bar' => 'default'], []); + $metadata->setNormalizationContextForGroups(['foo' => 'overridden'], ['a', 'b']); + $metadata->setNormalizationContextForGroups(['bar' => 'overridden'], ['c']); + + self::assertSame([ + '*' => ['foo' => 'default', 'bar' => 'default'], + 'a' => ['foo' => 'overridden'], + 'b' => ['foo' => 'overridden'], + 'c' => ['bar' => 'overridden'], + ], $metadata->getNormalizationContexts()); + + $metadata->setDenormalizationContextForGroups(['foo' => 'default', 'bar' => 'default'], []); + $metadata->setDenormalizationContextForGroups(['foo' => 'overridden'], ['a', 'b']); + $metadata->setDenormalizationContextForGroups(['bar' => 'overridden'], ['c']); + + self::assertSame([ + '*' => ['foo' => 'default', 'bar' => 'default'], + 'a' => ['foo' => 'overridden'], + 'b' => ['foo' => 'overridden'], + 'c' => ['bar' => 'overridden'], + ], $metadata->getDenormalizationContexts()); + } + + public function testGetContextsForGroups() + { + $metadata = new AttributeMetadata('a1'); + + $metadata->setNormalizationContextForGroups(['foo' => 'default', 'bar' => 'default'], []); + $metadata->setNormalizationContextForGroups(['foo' => 'overridden'], ['a', 'b']); + $metadata->setNormalizationContextForGroups(['bar' => 'overridden'], ['c']); + + self::assertSame(['foo' => 'default', 'bar' => 'default'], $metadata->getNormalizationContextForGroups([])); + self::assertSame(['foo' => 'overridden', 'bar' => 'default'], $metadata->getNormalizationContextForGroups(['a'])); + self::assertSame(['foo' => 'overridden', 'bar' => 'default'], $metadata->getNormalizationContextForGroups(['b'])); + self::assertSame(['foo' => 'default', 'bar' => 'overridden'], $metadata->getNormalizationContextForGroups(['c'])); + self::assertSame(['foo' => 'overridden', 'bar' => 'overridden'], $metadata->getNormalizationContextForGroups(['b', 'c'])); + + $metadata->setDenormalizationContextForGroups(['foo' => 'default', 'bar' => 'default'], []); + $metadata->setDenormalizationContextForGroups(['foo' => 'overridden'], ['a', 'b']); + $metadata->setDenormalizationContextForGroups(['bar' => 'overridden'], ['c']); + + self::assertSame(['foo' => 'default', 'bar' => 'default'], $metadata->getDenormalizationContextForGroups([])); + self::assertSame(['foo' => 'overridden', 'bar' => 'default'], $metadata->getDenormalizationContextForGroups(['a'])); + self::assertSame(['foo' => 'overridden', 'bar' => 'default'], $metadata->getDenormalizationContextForGroups(['b'])); + self::assertSame(['foo' => 'default', 'bar' => 'overridden'], $metadata->getDenormalizationContextForGroups(['c'])); + self::assertSame(['foo' => 'overridden', 'bar' => 'overridden'], $metadata->getDenormalizationContextForGroups(['b', 'c'])); + } + public function testMerge() { $attributeMetadata1 = new AttributeMetadata('a1'); @@ -77,6 +128,8 @@ public function testMerge() $attributeMetadata2->addGroup('c'); $attributeMetadata2->setMaxDepth(2); $attributeMetadata2->setSerializedName('a3'); + $attributeMetadata2->setNormalizationContextForGroups(['foo' => 'bar'], ['a']); + $attributeMetadata2->setDenormalizationContextForGroups(['baz' => 'qux'], ['c']); $attributeMetadata2->setIgnore(true); @@ -85,9 +138,27 @@ public function testMerge() $this->assertEquals(['a', 'b', 'c'], $attributeMetadata1->getGroups()); $this->assertEquals(2, $attributeMetadata1->getMaxDepth()); $this->assertEquals('a3', $attributeMetadata1->getSerializedName()); + $this->assertSame(['a' => ['foo' => 'bar']], $attributeMetadata1->getNormalizationContexts()); + $this->assertSame(['c' => ['baz' => 'qux']], $attributeMetadata1->getDenormalizationContexts()); $this->assertTrue($attributeMetadata1->isIgnored()); } + public function testContextsNotMergedIfAlreadyDefined() + { + $attributeMetadata1 = new AttributeMetadata('a1'); + $attributeMetadata1->setNormalizationContextForGroups(['foo' => 'not overridden'], ['a']); + $attributeMetadata1->setDenormalizationContextForGroups(['baz' => 'not overridden'], ['b']); + + $attributeMetadata2 = new AttributeMetadata('a2'); + $attributeMetadata2->setNormalizationContextForGroups(['foo' => 'override'], ['a']); + $attributeMetadata2->setDenormalizationContextForGroups(['baz' => 'override'], ['b']); + + $attributeMetadata1->merge($attributeMetadata2); + + self::assertSame(['a' => ['foo' => 'not overridden']], $attributeMetadata1->getNormalizationContexts()); + self::assertSame(['b' => ['baz' => 'not overridden']], $attributeMetadata1->getDenormalizationContexts()); + } + public function testSerialize() { $attributeMetadata = new AttributeMetadata('attribute'); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php index b3bbbf812ed0d..a135bfdaab16f 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -12,11 +12,13 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Loader; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; +use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; /** @@ -24,6 +26,8 @@ */ abstract class AnnotationLoaderTest extends TestCase { + use ContextMappingTestTrait; + /** * @var AnnotationLoader */ @@ -114,7 +118,31 @@ public function testLoadIgnore() $this->assertTrue($attributesMetadata['ignored2']->isIgnored()); } + public function testLoadContexts() + { + $this->assertLoadedContexts($this->getNamespace().'\ContextDummy', $this->getNamespace().'\ContextDummyParent'); + } + + public function testThrowsOnContextOnInvalidMethod() + { + $class = $this->getNamespace().'\BadMethodContextDummy'; + + $this->expectException(MappingException::class); + $this->expectExceptionMessage(sprintf('Context on "%s::badMethod()" cannot be added', $class)); + + $loader = $this->getLoaderForContextMapping(); + + $classMetadata = new ClassMetadata($class); + + $loader->loadClassMetadata($classMetadata); + } + abstract protected function createLoader(): AnnotationLoader; abstract protected function getNamespace(): string; + + protected function getLoaderForContextMapping(): LoaderInterface + { + return $this->loader; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php new file mode 100644 index 0000000000000..97c70c1499134 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Mapping\Loader\Features; + +use PHPUnit\Framework\Assert; +use Symfony\Component\Serializer\Mapping\ClassMetadata; +use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; +use Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent; + +/** + * @author Maxime Steinhausser + */ +trait ContextMappingTestTrait +{ + abstract protected function getLoaderForContextMapping(): LoaderInterface; + + public function testLoadContexts() + { + $this->assertLoadedContexts(); + } + + public function assertLoadedContexts(string $dummyClass = ContextDummy::class, string $parentClass = ContextDummyParent::class) + { + $loader = $this->getLoaderForContextMapping(); + + $classMetadata = new ClassMetadata($dummyClass); + $parentClassMetadata = new ClassMetadata($parentClass); + + $loader->loadClassMetadata($parentClassMetadata); + $classMetadata->merge($parentClassMetadata); + + $loader->loadClassMetadata($classMetadata); + + $attributes = $classMetadata->getAttributesMetadata(); + + Assert::assertEquals(['*' => ['prop' => 'dummy_parent_value']], $attributes['parentProperty']->getNormalizationContexts()); + Assert::assertEquals(['*' => ['prop' => 'dummy_value']], $attributes['overriddenParentProperty']->getNormalizationContexts()); + + Assert::assertEquals([ + '*' => [ + 'foo' => 'value', + 'bar' => 'value', + 'nested' => ['nested_key' => 'nested_value'], + 'array' => ['first', 'second'], + ], + 'a' => ['bar' => 'value_for_group_a'], + ], $attributes['foo']->getNormalizationContexts()); + Assert::assertSame( + $attributes['foo']->getNormalizationContexts(), + $attributes['foo']->getDenormalizationContexts() + ); + + Assert::assertEquals([ + 'a' => $c = ['format' => 'd/m/Y'], + 'b' => $c, + ], $attributes['bar']->getNormalizationContexts()); + Assert::assertEquals([ + 'a' => $c = ['format' => 'm-d-Y H:i'], + 'b' => $c, + ], $attributes['bar']->getDenormalizationContexts()); + + Assert::assertEquals(['*' => ['method' => 'method_with_context']], $attributes['methodWithContext']->getNormalizationContexts()); + Assert::assertEquals( + $attributes['methodWithContext']->getNormalizationContexts(), + $attributes['methodWithContext']->getDenormalizationContexts() + ); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index d4ed487a20caa..201cb68ba8ff8 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; /** @@ -28,10 +29,13 @@ */ class XmlFileLoaderTest extends TestCase { + use ContextMappingTestTrait; + /** * @var XmlFileLoader */ private $loader; + /** * @var ClassMetadata */ @@ -104,4 +108,9 @@ public function testLoadIgnore() $this->assertTrue($attributesMetadata['ignored1']->isIgnored()); $this->assertTrue($attributesMetadata['ignored2']->isIgnored()); } + + protected function getLoaderForContextMapping(): LoaderInterface + { + return $this->loader; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index d6fb2fa598ee0..aa235762bdeb5 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; /** @@ -29,6 +30,8 @@ */ class YamlFileLoaderTest extends TestCase { + use ContextMappingTestTrait; + /** * @var YamlFileLoader */ @@ -126,4 +129,9 @@ public function testLoadInvalidIgnore() (new YamlFileLoader(__DIR__.'/../../Fixtures/invalid-ignore.yml'))->loadClassMetadata(new ClassMetadata(IgnoreDummy::class)); } + + protected function getLoaderForContextMapping(): LoaderInterface + { + return $this->loader; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php new file mode 100644 index 0000000000000..374cacaf79d02 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer\Features; + +use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; + +/** + * Test context handling from Serializer metadata. + * + * @author Maxime Steinhausser + */ +trait ContextMetadataTestTrait +{ + public function testContextMetadataNormalize() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); + new Serializer([new DateTimeNormalizer(), $normalizer]); + + $dummy = new ContextMetadataDummy(); + $dummy->date = new \DateTime('2011-07-28T08:44:00.123+00:00'); + + self::assertEquals(['date' => '2011-07-28T08:44:00+00:00'], $normalizer->normalize($dummy)); + + self::assertEquals(['date' => '2011-07-28T08:44:00.123+00:00'], $normalizer->normalize($dummy, null, [ + ObjectNormalizer::GROUPS => 'extended', + ]), 'a specific normalization context is used for this group'); + + self::assertEquals(['date' => '2011-07-28T08:44:00+00:00'], $normalizer->normalize($dummy, null, [ + ObjectNormalizer::GROUPS => 'simple', + ]), 'base denormalization context is unchanged for this group'); + } + + public function testContextMetadataContextDenormalize() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); + new Serializer([new DateTimeNormalizer(), $normalizer]); + + /** @var ContextMetadataDummy $dummy */ + $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], ContextMetadataDummy::class); + self::assertEquals(new \DateTime('2011-07-28T08:44:00+00:00'), $dummy->date); + + /** @var ContextMetadataDummy $dummy */ + $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], ContextMetadataDummy::class, null, [ + ObjectNormalizer::GROUPS => 'extended', + ]); + self::assertEquals(new \DateTime('2011-07-28T08:44:00+00:00'), $dummy->date, 'base denormalization context is unchanged for this group'); + + /** @var ContextMetadataDummy $dummy */ + $dummy = $normalizer->denormalize(['date' => '28/07/2011'], ContextMetadataDummy::class, null, [ + ObjectNormalizer::GROUPS => 'simple', + ]); + self::assertEquals('2011-07-28', $dummy->date->format('Y-m-d'), 'a specific denormalization context is used for this group'); + } +} + +class ContextMetadataDummy +{ + /** + * @var \DateTime + * + * @Groups({ "extended", "simple" }) + * @Context({ DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339 }) + * @Context( + * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339_EXTENDED }, + * groups = {"extended"} + * ) + * @Context( + * denormalizationContext = { DateTimeNormalizer::FORMAT_KEY = "d/m/Y" }, + * groups = {"simple"} + * ) + */ + public $date; +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index f23bedea1fb58..860c16f6036a4 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -42,6 +42,7 @@ use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\ContextMetadataTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait; @@ -59,6 +60,7 @@ class ObjectNormalizerTest extends TestCase use CallbacksTestTrait; use CircularReferenceTestTrait; use ConstructorArgumentsTestTrait; + use ContextMetadataTestTrait; use GroupsTestTrait; use IgnoredAttributesTestTrait; use MaxDepthTestTrait; diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index b13ee21a2b8da..629747f174b33 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -37,6 +37,7 @@ "symfony/property-info": "^5.3", "symfony/uid": "^5.1", "symfony/validator": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0", "symfony/var-exporter": "^4.4|^5.0", "symfony/yaml": "^4.4|^5.0" }, From d7225db7d5fc7b9d061db283b67d38c8d6e06bf7 Mon Sep 17 00:00:00 2001 From: Ilya Chekalsky Date: Sat, 13 Feb 2021 02:05:39 +0100 Subject: [PATCH 158/188] [Mailer] AWS SES transport Source ARN header support --- .../Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php | 2 ++ .../Bridge/Amazon/Tests/Transport/SesApiTransportTest.php | 3 +++ .../Tests/Transport/SesHttpAsyncAwsTransportTest.php | 2 ++ .../Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php | 2 ++ .../Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php | 3 +++ .../Mailer/Bridge/Amazon/Transport/SesApiTransport.php | 7 +++++++ .../Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php | 4 ++++ .../Mailer/Bridge/Amazon/Transport/SesHttpTransport.php | 5 +++++ src/Symfony/Component/Mailer/CHANGELOG.md | 1 + 9 files changed, 29 insertions(+) diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php index 52a7c29f41cb7..b0a52538318d9 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php @@ -70,6 +70,7 @@ public function testSend() $this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Html']['Data']); $this->assertSame(['replyto-1@example.com', 'replyto-2@example.com'], $content['ReplyToAddresses']); $this->assertSame('aws-configuration-set-name', $content['ConfigurationSetName']); + $this->assertSame('aws-source-arn', $content['FromEmailAddressIdentityArn']); $this->assertSame('bounces@example.com', $content['FeedbackForwardingEmailAddress']); $json = '{"MessageId": "foobar"}'; @@ -91,6 +92,7 @@ public function testSend() ->returnPath(new Address('bounces@example.com')); $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $mail->getHeaders()->addTextHeader('X-SES-SOURCE-ARN', 'aws-source-arn'); $message = $transport->send($mail); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php index b4dfa191aea0f..cdbdaa9dfdb87 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php @@ -69,6 +69,7 @@ public function testSend() $this->assertSame('Fabien ', $content['Source']); $this->assertSame('Hello There!', $content['Message_Body_Text_Data']); $this->assertSame('aws-configuration-set-name', $content['ConfigurationSetName']); + $this->assertSame('aws-source-arn', $content['FromEmailAddressIdentityArn']); $xml = ' @@ -90,6 +91,7 @@ public function testSend() ->text('Hello There!'); $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $mail->getHeaders()->addTextHeader('X-SES-SOURCE-ARN', 'aws-source-arn'); $message = $transport->send($mail); @@ -135,6 +137,7 @@ public function testSendWithAttachments() ->attach('attached data'); $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $mail->getHeaders()->addTextHeader('X-SES-SOURCE-ARN', 'aws-source-arn'); $message = $transport->send($mail); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php index 6bdd9b779d58d..981b92d8fb8f4 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php @@ -69,6 +69,7 @@ public function testSend() $this->assertStringContainsString('Fabien ', $content); $this->assertStringContainsString('Hello There!', $content); $this->assertSame('aws-configuration-set-name', $body['ConfigurationSetName']); + $this->assertSame('aws-source-arn', $body['FromEmailAddressIdentityArn']); $json = '{"MessageId": "foobar"}'; @@ -86,6 +87,7 @@ public function testSend() ->text('Hello There!'); $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $mail->getHeaders()->addTextHeader('X-SES-SOURCE-ARN', 'aws-source-arn'); $message = $transport->send($mail); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php index cce8a4792cc64..bd5babdadd0e3 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php @@ -77,6 +77,7 @@ public function testSend() $this->assertStringContainsString('Hello There!', $content); $this->assertSame('aws-configuration-set-name', $body['ConfigurationSetName']); + $this->assertSame('aws-source-arn', $body['FromEmailAddressIdentityArn']); $xml = ' @@ -99,6 +100,7 @@ public function testSend() ->text('Hello There!'); $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $mail->getHeaders()->addTextHeader('X-SES-SOURCE-ARN', 'aws-source-arn'); $message = $transport->send($mail); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php index 0791a64aa270a..a0e2cb286eaca 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php @@ -92,6 +92,9 @@ protected function getRequest(SentMessage $message): SendEmailRequest if ($header = $email->getHeaders()->get('X-SES-CONFIGURATION-SET')) { $request['ConfigurationSetName'] = $header->getBodyAsString(); } + if ($header = $email->getHeaders()->get('X-SES-SOURCE-ARN')) { + $request['FromEmailAddressIdentityArn'] = $header->getBodyAsString(); + } if ($email->getReturnPath()) { $request['FeedbackForwardingEmailAddress'] = $email->getReturnPath()->toString(); } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index 33682615186c4..6f53aa0cccb8a 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -99,6 +99,10 @@ private function getPayload(Email $email, Envelope $envelope): array $payload['ConfigurationSetName'] = $header->getBodyAsString(); } + if ($header = $email->getHeaders()->get('X-SES-SOURCE-ARN')) { + $payload['FromEmailAddressIdentityArn'] = $header->getBodyAsString(); + } + return $payload; } @@ -127,6 +131,9 @@ private function getPayload(Email $email, Envelope $envelope): array if ($header = $email->getHeaders()->get('X-SES-CONFIGURATION-SET')) { $payload['ConfigurationSetName'] = $header->getBodyAsString(); } + if ($header = $email->getHeaders()->get('X-SES-SOURCE-ARN')) { + $payload['FromEmailAddressIdentityArn'] = $header->getBodyAsString(); + } return $payload; } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php index 58ae25e792190..8f2177186bf14 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php @@ -83,6 +83,10 @@ protected function getRequest(SentMessage $message): SendEmailRequest && $configurationSetHeader = $message->getOriginalMessage()->getHeaders()->get('X-SES-CONFIGURATION-SET')) { $request['ConfigurationSetName'] = $configurationSetHeader->getBodyAsString(); } + if (($message->getOriginalMessage() instanceof Message) + && $sourceArnHeader = $message->getOriginalMessage()->getHeaders()->get('X-SES-SOURCE-ARN')) { + $request['FromEmailAddressIdentityArn'] = $sourceArnHeader->getBodyAsString(); + } return new SendEmailRequest($request); } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index 8e9e9de5f7b25..779566788af32 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -77,6 +77,11 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $request['body']['ConfigurationSetName'] = $configurationSetHeader->getBodyAsString(); } + if ($message->getOriginalMessage() instanceof Message + && $sourceArnHeader = $message->getOriginalMessage()->getHeaders()->get('X-SES-SOURCE-ARN')) { + $request['body']['FromEmailAddressIdentityArn'] = $sourceArnHeader->getBodyAsString(); + } + $response = $this->client->request('POST', 'https://'.$this->getEndpoint(), $request); $result = new \SimpleXMLElement($response->getContent(false)); diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 2d0f1faddbf1a..5461a2a4e0fa0 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * added the `mailer` monolog channel and set it on all transport definitions + * Add support for `X-SES-SOURCE-ARN` in `symfony/amazon-mailer` 5.2.0 ----- From 64ab6a28505ed03d8ac59c8f0f67f4f44f145143 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 12 Jan 2021 19:42:50 +0100 Subject: [PATCH 159/188] [DependencyInjection] Add `#[Autoconfigure]` to help define autoconfiguration rules --- .../Attribute/Autoconfigure.php | 34 +++++++ .../Attribute/AutoconfigureTag.php | 30 ++++++ .../DependencyInjection/CHANGELOG.md | 1 + .../Compiler/PassConfig.php | 1 + .../RegisterAutoconfigureAttributesPass.php | 92 +++++++++++++++++++ .../DependencyInjection/Loader/FileLoader.php | 10 +- .../Loader/YamlFileLoader.php | 10 +- ...egisterAutoconfigureAttributesPassTest.php | 81 ++++++++++++++++ .../Fixtures/AutoconfigureAttributed.php | 29 ++++++ .../Fixtures/AutoconfiguredInterface.php | 10 ++ .../Tests/Fixtures/Prototype/FooInterface.php | 3 + .../Tests/Loader/FileLoaderTest.php | 7 +- 12 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php create mode 100644 src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfiguredInterface.php diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php b/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php new file mode 100644 index 0000000000000..abab040101532 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.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\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell how a base type should be autoconfigured. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class Autoconfigure +{ + public function __construct( + public ?array $tags = null, + public ?array $calls = null, + public ?array $bind = null, + public bool|string|null $lazy = null, + public ?bool $public = null, + public ?bool $shared = null, + public ?bool $autowire = null, + public ?array $properties = null, + public array|string|null $configurator = null, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php b/src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php new file mode 100644 index 0000000000000..ed5807ca02670 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.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\Attribute; + +/** + * An attribute to tell how a base type should be tagged. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class AutoconfigureTag extends Autoconfigure +{ + public function __construct(string $name = null, array $attributes = []) + { + parent::__construct( + tags: [ + [$name ?? 0 => $attributes], + ] + ); + } +} diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index c801155694754..4dd8a1da63306 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `ServicesConfigurator::remove()` in the PHP-DSL * Add `%env(not:...)%` processor to negate boolean values + * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 961711fd28cad..29e7218bc64e3 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -42,6 +42,7 @@ public function __construct() $this->beforeOptimizationPasses = [ 100 => [ new ResolveClassPass(), + new RegisterAutoconfigureAttributesPass(), new ResolveInstanceofConditionalsPass(), new RegisterEnvVarProcessorsPass(), ], diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php new file mode 100644 index 0000000000000..d3324062717e7 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php @@ -0,0 +1,92 @@ + + * + * 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\Autoconfigure; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** + * Reads #[Autoconfigure] attributes on definitions that are autoconfigured + * and don't have the "container.ignore_attributes" tag. + * + * @author Nicolas Grekas + */ +final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface +{ + private $ignoreAttributesTag; + private $registerForAutoconfiguration; + + public function __construct(string $ignoreAttributesTag = 'container.ignore_attributes') + { + if (80000 > \PHP_VERSION_ID) { + return; + } + + $this->ignoreAttributesTag = $ignoreAttributesTag; + + $parseDefinitions = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinitions'); + $parseDefinitions->setAccessible(true); + $yamlLoader = $parseDefinitions->getDeclaringClass()->newInstanceWithoutConstructor(); + + $this->registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) { + $attribute = (array) $attribute->newInstance(); + + foreach ($attribute['tags'] ?? [] as $i => $tag) { + if (\is_array($tag) && [0] === array_keys($tag)) { + $attribute['tags'][$i] = [$class->name => $tag[0]]; + } + } + + $parseDefinitions->invoke( + $yamlLoader, + [ + 'services' => [ + '_instanceof' => [ + $class->name => [$container->registerForAutoconfiguration($class->name)] + $attribute, + ], + ], + ], + $class->getFileName() + ); + }; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (80000 > \PHP_VERSION_ID) { + return; + } + + foreach ($container->getDefinitions() as $id => $definition) { + if ($this->accept($definition) && null !== $class = $container->getReflectionClass($definition->getClass())) { + $this->processClass($container, $class); + } + } + } + + public function accept(Definition $definition): bool + { + return 80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() && !$definition->hasTag($this->ignoreAttributesTag); + } + + public function processClass(ContainerBuilder $container, \ReflectionClass $class) + { + foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + ($this->registerForAutoconfiguration)($container, $class, $attribute); + } + } +} diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index f8f9fc4523206..b0d3e952e9cce 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -18,6 +18,7 @@ use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Config\Resource\GlobResource; 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; @@ -96,7 +97,8 @@ public function registerClasses(Definition $prototype, string $namespace, string throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace)); } - $classes = $this->findClasses($namespace, $resource, (array) $exclude); + $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); + $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null); // prepare for deep cloning $serializedPrototype = serialize($prototype); @@ -149,7 +151,7 @@ protected function setDefinition(string $id, Definition $definition) } } - private function findClasses(string $namespace, string $pattern, array $excludePatterns): array + private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes): array { $parameterBag = $this->container->getParameterBag(); @@ -207,6 +209,10 @@ private function findClasses(string $namespace, string $pattern, array $excludeP if ($r->isInstantiable() || $r->isInterface()) { $classes[$class] = null; } + + if ($autoconfigureAttributes && !$r->isInstantiable()) { + $autoconfigureAttributes->processClass($this->container, $r); + } } // track only for new & removed files diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 8eeb40ad8e1b7..22f029cca85fb 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -389,6 +389,9 @@ private function parseDefinition(string $id, $service, string $file, array $defa ]; } + $definition = isset($service[0]) && $service[0] instanceof Definition ? array_shift($service) : null; + $return = null === $definition ? $return : true; + $this->checkDefinition($id, $service, $file); if (isset($service['alias'])) { @@ -423,7 +426,9 @@ private function parseDefinition(string $id, $service, string $file, array $defa return $return ? $alias : $this->container->setAlias($id, $alias); } - if ($this->isLoadingInstanceof) { + if (null !== $definition) { + // no-op + } elseif ($this->isLoadingInstanceof) { $definition = new ChildDefinition(''); } elseif (isset($service['parent'])) { if ('' !== $service['parent'] && '@' === $service['parent'][0]) { @@ -627,7 +632,8 @@ private function parseDefinition(string $id, $service, string $file, array $defa if (isset($defaults['bind']) || isset($service['bind'])) { // deep clone, to avoid multiple process of the same instance in the passes - $bindings = isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : []; + $bindings = $definition->getBindings(); + $bindings += isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : []; if (isset($service['bind'])) { if (!\is_array($service['bind'])) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php new file mode 100644 index 0000000000000..274cde655ed15 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureAttributed; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface; + +/** + * @requires PHP 8 + */ +class RegisterAutoconfigureAttributesPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfigureAttributed::class) + ->setAutoconfigured(true); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $argument = new BoundArgument(1, true, BoundArgument::INSTANCEOF_BINDING, realpath(__DIR__.'/../Fixtures/AutoconfigureAttributed.php')); + $values = $argument->getValues(); + --$values[1]; + $argument->setValues($values); + + $expected = (new ChildDefinition('')) + ->setLazy(true) + ->setPublic(true) + ->setAutowired(true) + ->setShared(true) + ->setProperties(['bar' => 'baz']) + ->setConfigurator(new Reference('bla')) + ->addTag('a_tag') + ->addTag('another_tag', ['attr' => 234]) + ->addMethodCall('setBar', [2, 3]) + ->setBindings(['$bar' => $argument]) + ; + $this->assertEquals([AutoconfigureAttributed::class => $expected], $container->getAutoconfiguredInstanceof()); + } + + public function testIgnoreAttribute() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfigureAttributed::class) + ->addTag('container.ignore_attributes') + ->setAutoconfigured(true); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $this->assertSame([], $container->getAutoconfiguredInstanceof()); + } + + public function testAutoconfiguredTag() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfiguredInterface::class) + ->setAutoconfigured(true); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->addTag(AutoconfiguredInterface::class, ['foo' => 123]) + ; + $this->assertEquals([AutoconfiguredInterface::class => $expected], $container->getAutoconfiguredInstanceof()); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php new file mode 100644 index 0000000000000..7761e7134bb22 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php @@ -0,0 +1,29 @@ + 'baz', + ], + configurator: '@bla', + tags: [ + 'a_tag', + ['another_tag' => ['attr' => 234]], + ], + calls: [ + ['setBar' => [2, 3]] + ], + bind: [ + '$bar' => 1, + ], +)] +class AutoconfigureAttributed +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfiguredInterface.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfiguredInterface.php new file mode 100644 index 0000000000000..413a0630cf2ee --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfiguredInterface.php @@ -0,0 +1,10 @@ + 123])] +interface AutoconfiguredInterface +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/FooInterface.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/FooInterface.php index 1855dcfc59e0e..c6fc34fb68731 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/FooInterface.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/FooInterface.php @@ -2,6 +2,9 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; + +#[Autoconfigure(tags: ['foo'])] interface FooInterface { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 2d9fcef4e051e..fbf825e299f22 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -15,6 +15,7 @@ use Psr\Container\ContainerInterface as PsrContainerInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -171,7 +172,7 @@ public function testNestedRegisterClasses() $container = new ContainerBuilder(); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); - $prototype = new Definition(); + $prototype = (new Definition())->setAutoconfigured(true); $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*'); $this->assertTrue($container->has(Bar::class)); @@ -191,6 +192,10 @@ public function testNestedRegisterClasses() $this->assertSame(Foo::class, (string) $alias); $this->assertFalse($alias->isPublic()); $this->assertTrue($alias->isPrivate()); + + if (\PHP_VERSION_ID >= 80000) { + $this->assertEquals([FooInterface::class => (new ChildDefinition(''))->addTag('foo')], $container->getAutoconfiguredInstanceof()); + } } public function testMissingParentClass() From 79de1da6ca95674f9d8b311d4c06b7a7ba8f8e3b Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Tue, 16 Feb 2021 17:55:35 +0100 Subject: [PATCH 160/188] [Security] Fix some broken BC layers --- .../Security/Core/Encoder/UserPasswordEncoderInterface.php | 4 ++-- .../Guard/Provider/GuardAuthenticationProvider.php | 7 +++---- .../Http/EventListener/PasswordMigratingListener.php | 7 +++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php index 858e83676835e..99ce44144d2cc 100644 --- a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" on hasher implementations that deal with salts instead.', UserPasswordEncoderInterface::class, UserPasswordHasherInterface::class)); +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" instead.', UserPasswordEncoderInterface::class, UserPasswordHasherInterface::class)); -use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\Security\Core\User\UserInterface; /** * UserPasswordEncoderInterface is the interface for the password encoder service. diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 4d2ec108999a3..806b1b6504ac9 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Security\Guard\Provider; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationExpiredException; @@ -27,7 +27,6 @@ use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface; use Symfony\Component\Security\Guard\Token\GuardTokenInterface; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; -use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; /** * Responsible for accepting the PreAuthenticationGuardToken and calling @@ -130,8 +129,8 @@ private function authenticateViaGuard(AuthenticatorInterface $guardAuthenticator throw new BadCredentialsException(sprintf('Authentication failed because "%s::checkCredentials()" did not return true.', get_debug_type($guardAuthenticator))); } - if ($this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordHasher && (null !== $password = $guardAuthenticator->getPassword($token->getCredentials())) && method_exists($this->passwordHasher, 'needsRehash') && $this->passwordHasher->needsRehash($user)) { - if ($this->passwordHasher instanceof PasswordEncoderInterface) { + if ($this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordHasher && (null !== $password = $guardAuthenticator->getPassword($token->getCredentials())) && $this->passwordHasher->needsRehash($user)) { + if ($this->passwordHasher instanceof UserPasswordEncoderInterface) { // @deprecated since Symfony 5.3 $this->userProvider->upgradePassword($user, $this->passwordHasher->encodePassword($user, $password)); } else { diff --git a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php index a3755ca3ab405..d19121dec81c4 100644 --- a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php @@ -12,15 +12,14 @@ namespace Symfony\Component\Security\Http\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; /** * @author Wouter de Jong @@ -82,7 +81,7 @@ public function onLoginSuccess(LoginSuccessEvent $event): void } } - $passwordUpgrader->upgradePassword($user, $passwordHasher->hash($plaintextPassword, $user->getSalt())); + $passwordUpgrader->upgradePassword($user, $passwordHasher instanceof PasswordHasherInterface ? $passwordHasher->hash($plaintextPassword, $user->getSalt()) : $passwordHasher->encodePassword($plaintextPassword, $user->getSalt())); } public static function getSubscribedEvents(): array From 2ab3caf0806ace9e5c1fdaec14bb94391b0fc8e1 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 11 Feb 2021 17:42:06 +0100 Subject: [PATCH 161/188] [DependencyInjection] Autoconfigurable attributes --- .../FrameworkExtension.php | 5 + .../DependencyInjection/CHANGELOG.md | 1 + .../AttributeAutoconfigurationPass.php | 57 +++++++++ .../Compiler/PassConfig.php | 1 + .../DependencyInjection/ContainerBuilder.php | 31 +++++ .../Tests/Compiler/IntegrationTest.php | 121 ++++++++++++++++++ .../Attribute/CustomAutoconfiguration.php | 22 ++++ .../Tests/Fixtures/TaggedService1.php | 20 +++ .../Tests/Fixtures/TaggedService2.php | 19 +++ .../Tests/Fixtures/TaggedService3.php | 31 +++++ .../Fixtures/TaggedService3Configurator.php | 20 +++ .../Attribute/EventListener.php | 28 ++++ .../Component/EventDispatcher/CHANGELOG.md | 5 + .../RegisterListenersPassTest.php | 95 +++++++++++++- .../Tests/Fixtures/CustomEvent.php | 16 +++ .../Fixtures/TaggedInvokableListener.php | 22 ++++ .../Tests/Fixtures/TaggedMultiListener.php | 32 +++++ 17 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php create mode 100644 src/Symfony/Component/EventDispatcher/Attribute/EventListener.php create mode 100644 src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.php create mode 100644 src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.php create mode 100644 src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 3b7f8ef35dd6d..d41e0a97b9569 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -58,6 +58,7 @@ use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\EventDispatcher\Attribute\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Finder\Finder; @@ -549,6 +550,10 @@ public function load(array $configs, ContainerBuilder $container) $container->registerForAutoconfiguration(LoggerAwareInterface::class) ->addMethodCall('setLogger', [new Reference('logger')]); + $container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void { + $definition->addTag('kernel.event_listener', get_object_vars($attribute)); + }); + if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers $container->getDefinition('config_cache_factory')->setArguments([]); diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 4dd8a1da63306..a6c60db0e25fa 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `ServicesConfigurator::remove()` in the PHP-DSL * Add `%env(not:...)%` processor to negate boolean values * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 + * Add autoconfigurable attributes 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php new file mode 100644 index 0000000000000..ba6478d66ddb7 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.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\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Alexander M. Turek + */ +final class AttributeAutoconfigurationPass implements CompilerPassInterface +{ + private $ignoreAttributesTag; + + public function __construct(string $ignoreAttributesTag = 'container.ignore_attributes') + { + $this->ignoreAttributesTag = $ignoreAttributesTag; + } + + public function process(ContainerBuilder $container): void + { + if (80000 > \PHP_VERSION_ID) { + return; + } + + $autoconfiguredAttributes = $container->getAutoconfiguredAttributes(); + + foreach ($container->getDefinitions() as $id => $definition) { + if (!$definition->isAutoconfigured() + || $definition->isAbstract() + || $definition->hasTag($this->ignoreAttributesTag) + || !($reflector = $container->getReflectionClass($definition->getClass(), false)) + ) { + continue; + } + + $instanceof = $definition->getInstanceofConditionals(); + $conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition(''); + foreach ($reflector->getAttributes() as $attribute) { + if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $reflector); + } + } + $instanceof[$reflector->getName()] = $conditionals; + $definition->setInstanceofConditionals($instanceof); + } + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 29e7218bc64e3..15febf9f70fd1 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -43,6 +43,7 @@ public function __construct() 100 => [ new ResolveClassPass(), new RegisterAutoconfigureAttributesPass(), + new AttributeAutoconfigurationPass(), new ResolveInstanceofConditionalsPass(), new RegisterEnvVarProcessorsPass(), ], diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 15d6d16adcd75..a3be650343152 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -123,6 +123,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private $autoconfiguredInstanceof = []; + /** + * @var callable[] + */ + private $autoconfiguredAttributes = []; + private $removedIds = []; private $removedBindingIds = []; @@ -671,6 +676,14 @@ public function merge(self $container) $this->autoconfiguredInstanceof[$interface] = $childDefinition; } + + foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) { + if (isset($this->autoconfiguredAttributes[$attribute])) { + throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute)); + } + + $this->autoconfiguredAttributes[$attribute] = $configurator; + } } /** @@ -1309,6 +1322,16 @@ public function registerForAutoconfiguration(string $interface) return $this->autoconfiguredInstanceof[$interface]; } + /** + * Registers an attribute that will be used for autoconfiguring annotated classes. + * + * The configurator will receive a Definition instance and an instance of the attribute, in that order. + */ + public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator): void + { + $this->autoconfiguredAttributes[$attributeClass] = $configurator; + } + /** * Registers an autowiring alias that only binds to a specific argument name. * @@ -1338,6 +1361,14 @@ public function getAutoconfiguredInstanceof() return $this->autoconfiguredInstanceof; } + /** + * @return callable[] + */ + public function getAutoconfiguredAttributes(): array + { + return $this->autoconfiguredAttributes; + } + /** * Resolves env parameter placeholders in a string or an array. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 9f059a80d9891..6ab2cce23885c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -16,14 +16,22 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3Configurator; use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -506,6 +514,109 @@ public function testTaggedServiceLocatorWithDefaultIndex() ]; $this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]); } + + /** + * @requires PHP 8 + */ + public function testTagsViaAttribute() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration( + CustomAutoconfiguration::class, + static function (ChildDefinition $definition, CustomAutoconfiguration $attribute, \ReflectionClass $reflector) { + $definition->addTag('app.custom_tag', get_object_vars($attribute) + ['class' => $reflector->getName()]); + } + ); + + $container->register('one', TaggedService1::class) + ->setPublic(true) + ->setAutoconfigured(true); + $container->register('two', TaggedService2::class) + ->addTag('app.custom_tag', ['info' => 'This tag is not autoconfigured']) + ->setPublic(true) + ->setAutoconfigured(true); + + $collector = new TagCollector(); + $container->addCompilerPass($collector); + + $container->compile(); + + self::assertSame([ + 'one' => [ + ['someAttribute' => 'one', 'priority' => 0, 'class' => TaggedService1::class], + ['someAttribute' => 'two', 'priority' => 0, 'class' => TaggedService1::class], + ], + 'two' => [ + ['info' => 'This tag is not autoconfigured'], + ['someAttribute' => 'prio 100', 'priority' => 100, 'class' => TaggedService2::class], + ], + ], $collector->collectedTags); + } + + /** + * @requires PHP 8 + */ + public function testAttributesAreIgnored() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration( + CustomAutoconfiguration::class, + static function (Definition $definition, CustomAutoconfiguration $attribute) { + $definition->addTag('app.custom_tag', get_object_vars($attribute)); + } + ); + + $container->register('one', TaggedService1::class) + ->setPublic(true) + ->addTag('container.ignore_attributes') + ->setAutoconfigured(true); + $container->register('two', TaggedService2::class) + ->setPublic(true) + ->setAutoconfigured(true); + + $collector = new TagCollector(); + $container->addCompilerPass($collector); + + $container->compile(); + + self::assertSame([ + 'two' => [ + ['someAttribute' => 'prio 100', 'priority' => 100], + ], + ], $collector->collectedTags); + } + + /** + * @requires PHP 8 + */ + public function testAutoconfigureViaAttribute() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration( + CustomAutoconfiguration::class, + static function (ChildDefinition $definition) { + $definition + ->addMethodCall('doSomething', [1, 2, 3]) + ->setBindings(['string $foo' => 'bar']) + ->setConfigurator(new Reference('my_configurator')) + ; + } + ); + + $container->register('my_configurator', TaggedService3Configurator::class); + $container->register('three', TaggedService3::class) + ->setPublic(true) + ->setAutoconfigured(true); + + $container->compile(); + + /** @var TaggedService3 $service */ + $service = $container->get('three'); + + self::assertSame('bar', $service->foo); + self::assertSame(6, $service->sum); + self::assertTrue($service->hasBeenConfigured); + } } class ServiceSubscriberStub implements ServiceSubscriberInterface @@ -566,3 +677,13 @@ public function setSunshine($type) { } } + +final class TagCollector implements CompilerPassInterface +{ + public $collectedTags; + + public function process(ContainerBuilder $container): void + { + $this->collectedTags = $container->findTaggedServiceIds('app.custom_tag'); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.php new file mode 100644 index 0000000000000..e668834debbee --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.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\Tests\Fixtures\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +final class CustomAutoconfiguration +{ + public function __construct( + public string $someAttribute, + public int $priority = 0, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php new file mode 100644 index 0000000000000..ce05326022274 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; + +#[CustomAutoconfiguration(someAttribute: 'one')] +#[CustomAutoconfiguration(someAttribute: 'two')] +final class TaggedService1 +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php new file mode 100644 index 0000000000000..c282a88541508 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; + +#[CustomAutoconfiguration(someAttribute: 'prio 100', priority: 100)] +final class TaggedService2 +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.php new file mode 100644 index 0000000000000..d13341aa9b4d9 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.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\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; + +#[CustomAutoconfiguration(someAttribute: 'three')] +final class TaggedService3 +{ + public int $sum = 0; + public bool $hasBeenConfigured = false; + + public function __construct( + public string $foo, + ) { + } + + public function doSomething(int $a, int $b, int $c): void + { + $this->sum = $a + $b + $c; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php new file mode 100644 index 0000000000000..ae5c1307f2b5f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +final class TaggedService3Configurator +{ + public function __invoke(TaggedService3 $service) + { + $service->hasBeenConfigured = true; + } +} diff --git a/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php new file mode 100644 index 0000000000000..e82c69e891351 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.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\EventDispatcher\Attribute; + +/** + * Service tag to autoconfigure event listeners. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class EventListener +{ + public function __construct( + public ?string $event = null, + public ?string $method = null, + public int $priority = 0 + ) { + } +} diff --git a/src/Symfony/Component/EventDispatcher/CHANGELOG.md b/src/Symfony/Component/EventDispatcher/CHANGELOG.md index 92a3b8bfc4d9e..4af07cdc33a7c 100644 --- a/src/Symfony/Component/EventDispatcher/CHANGELOG.md +++ b/src/Symfony/Component/EventDispatcher/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3 +--- + + * Add `EventListener` attribute for declaring listeners on PHP 8. + 5.1.0 ----- diff --git a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php index bf2cebf6c0660..7ff08506582e8 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -13,12 +13,19 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\Attribute\EventListener; use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Tests\Fixtures\CustomEvent; +use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedInvokableListener; +use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedMultiListener; class RegisterListenersPassTest extends TestCase { @@ -231,6 +238,90 @@ public function testInvokableEventListener() $this->assertEquals($expectedCalls, $definition->getMethodCalls()); } + /** + * @requires PHP 8 + */ + public function testTaggedInvokableEventListener() + { + if (!class_exists(AttributeAutoconfigurationPass::class)) { + self::markTestSkipped('This test requires Symfony DependencyInjection >= 5.3'); + } + + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void { + $definition->addTag('kernel.event_listener', get_object_vars($attribute)); + }); + $container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true); + $container->register('event_dispatcher', \stdClass::class); + + (new AttributeAutoconfigurationPass())->process($container); + (new ResolveInstanceofConditionalsPass())->process($container); + (new RegisterListenersPass())->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expectedCalls = [ + [ + 'addListener', + [ + CustomEvent::class, + [new ServiceClosureArgument(new Reference('foo')), '__invoke'], + 0, + ], + ], + ]; + $this->assertEquals($expectedCalls, $definition->getMethodCalls()); + } + + /** + * @requires PHP 8 + */ + public function testTaggedMultiEventListener() + { + if (!class_exists(AttributeAutoconfigurationPass::class)) { + self::markTestSkipped('This test requires Symfony DependencyInjection >= 5.3'); + } + + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void { + $definition->addTag('kernel.event_listener', get_object_vars($attribute)); + }); + $container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true); + $container->register('event_dispatcher', \stdClass::class); + + (new AttributeAutoconfigurationPass())->process($container); + (new ResolveInstanceofConditionalsPass())->process($container); + (new RegisterListenersPass())->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expectedCalls = [ + [ + 'addListener', + [ + CustomEvent::class, + [new ServiceClosureArgument(new Reference('foo')), 'onCustomEvent'], + 0, + ], + ], + [ + 'addListener', + [ + 'foo', + [new ServiceClosureArgument(new Reference('foo')), 'onFoo'], + 42, + ], + ], + [ + 'addListener', + [ + 'bar', + [new ServiceClosureArgument(new Reference('foo')), 'onBarEvent'], + 0, + ], + ], + ]; + $this->assertEquals($expectedCalls, $definition->getMethodCalls()); + } + public function testAliasedEventListener() { $container = new ContainerBuilder(); @@ -416,10 +507,6 @@ final class AliasedEvent { } -final class CustomEvent -{ -} - final class TypedListener { public function __invoke(AliasedEvent $event): void diff --git a/src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.php b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.php new file mode 100644 index 0000000000000..41d951c7abd04 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.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\EventDispatcher\Tests\Fixtures; + +final class CustomEvent +{ +} diff --git a/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.php b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.php new file mode 100644 index 0000000000000..00a5e14d9e120 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.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\EventDispatcher\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Attribute\EventListener; + +#[EventListener] +final class TaggedInvokableListener +{ + public function __invoke(CustomEvent $event): void + { + } +} diff --git a/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php new file mode 100644 index 0000000000000..65a66b8aa221b --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.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\EventDispatcher\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Attribute\EventListener; + +#[EventListener(event: CustomEvent::class, method: 'onCustomEvent')] +#[EventListener(event: 'foo', priority: 42)] +#[EventListener(event: 'bar', method: 'onBarEvent')] +final class TaggedMultiListener +{ + public function onCustomEvent(CustomEvent $event): void + { + } + + public function onFoo(): void + { + } + + public function onBarEvent(): void + { + } +} From 0cbc9cc6725db6fe09b17bc42325efd5d5ac8108 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 18 Feb 2021 11:38:22 +0100 Subject: [PATCH 162/188] [Console] Add `ConsoleCommand` attribute for declaring commands on PHP 8 --- .../Console/Attribute/ConsoleCommand.php | 36 +++++++++++++++++++ src/Symfony/Component/Console/CHANGELOG.md | 1 + .../Component/Console/Command/Command.php | 11 ++++++ .../Console/Tests/Command/CommandTest.php | 15 ++++++++ .../Component/EventDispatcher/CHANGELOG.md | 2 +- 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Console/Attribute/ConsoleCommand.php diff --git a/src/Symfony/Component/Console/Attribute/ConsoleCommand.php b/src/Symfony/Component/Console/Attribute/ConsoleCommand.php new file mode 100644 index 0000000000000..90e8c2cb137ef --- /dev/null +++ b/src/Symfony/Component/Console/Attribute/ConsoleCommand.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\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS)] +class ConsoleCommand +{ + public function __construct( + public string $name, + public ?string $description = null, + array $aliases = [], + bool $hidden = false, + ) { + if (!$hidden && !$aliases) { + return; + } + + $name = explode('|', $name); + $name = array_merge($name, $aliases); + + if ($hidden && '' !== $name[0]) { + array_unshift($name, ''); + } + + $this->name = implode('|', $name); + } +} diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index aa2b0aaedacd2..bdcd6f2959658 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG on the `console.command` tag to allow the `list` command to instantiate commands lazily * Add option `--short` to the `list` command * Add support for bright colors + * Add `ConsoleCommand` attribute for declaring commands on PHP 8 5.2.0 ----- diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index db4bbee7d878e..30f3796e6d202 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Attribute\ConsoleCommand; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; @@ -65,6 +66,11 @@ class Command public static function getDefaultName() { $class = static::class; + + if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(ConsoleCommand::class)) { + return $attribute[0]->newInstance()->name; + } + $r = new \ReflectionProperty($class, 'defaultName'); return $class === $r->class ? static::$defaultName : null; @@ -76,6 +82,11 @@ public static function getDefaultName() public static function getDefaultDescription(): ?string { $class = static::class; + + if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(ConsoleCommand::class)) { + return $attribute[0]->newInstance()->description; + } + $r = new \ReflectionProperty($class, 'defaultDescription'); return $class === $r->class ? static::$defaultDescription : null; diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php index ead75ebd3fb6a..fd6c4ba06e493 100644 --- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Attribute\ConsoleCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Helper\FormatterHelper; @@ -404,6 +405,15 @@ public function testSetCodeWithStaticAnonymousFunction() $this->assertEquals('interact called'.\PHP_EOL.'not bound'.\PHP_EOL, $tester->getDisplay()); } + + /** + * @requires PHP 8 + */ + public function testConsoleCommandAttribute() + { + $this->assertSame('|foo|f', Php8Command::getDefaultName()); + $this->assertSame('desc', Php8Command::getDefaultDescription()); + } } // In order to get an unbound closure, we should create it outside a class @@ -414,3 +424,8 @@ function createClosure() $output->writeln($this instanceof Command ? 'bound to the command' : 'not bound to the command'); }; } + +#[ConsoleCommand(name: 'foo', description: 'desc', hidden: true, aliases: ['f'])] +class Php8Command extends Command +{ +} diff --git a/src/Symfony/Component/EventDispatcher/CHANGELOG.md b/src/Symfony/Component/EventDispatcher/CHANGELOG.md index 4af07cdc33a7c..f1a3b2220f4ec 100644 --- a/src/Symfony/Component/EventDispatcher/CHANGELOG.md +++ b/src/Symfony/Component/EventDispatcher/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 5.3 --- - * Add `EventListener` attribute for declaring listeners on PHP 8. + * Add `EventListener` attribute for declaring listeners on PHP 8 5.1.0 ----- From 4718fc2b1d6bc89a9393e84bed254913e8f21f0a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 18 Feb 2021 17:19:14 +0100 Subject: [PATCH 163/188] [EventDispatcher] add missing "dispatcher" property on #[EventListener] --- .../Component/EventDispatcher/Attribute/EventListener.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php index e82c69e891351..a752673e565e1 100644 --- a/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php +++ b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php @@ -22,7 +22,8 @@ class EventListener public function __construct( public ?string $event = null, public ?string $method = null, - public int $priority = 0 + public int $priority = 0, + public ?string $dispatcher = null, ) { } } From 79665179769f924011692a8f4b30dcfd16454267 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 18 Feb 2021 17:47:29 +0100 Subject: [PATCH 164/188] [PropertyInfo] fix direct deprecation --- src/Symfony/Component/PropertyInfo/CHANGELOG.md | 4 ++-- .../Component/PropertyInfo/Extractor/PhpDocExtractor.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/CHANGELOG.md b/src/Symfony/Component/PropertyInfo/CHANGELOG.md index 4671a7be64165..5f9979c08dfff 100644 --- a/src/Symfony/Component/PropertyInfo/CHANGELOG.md +++ b/src/Symfony/Component/PropertyInfo/CHANGELOG.md @@ -4,8 +4,8 @@ CHANGELOG 5.3 --- -* Added support for multiple types for collection keys & values -* Deprecated the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead. + * Add support for multiple types for collection keys & values + * Deprecate the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead 5.2.0 ----- diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php index f8b62b5e917fb..3e2a68630d0ef 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php @@ -165,7 +165,7 @@ public function getTypes(string $class, string $property, array $context = []): continue 2; } - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyType(), $type->getCollectionValueType()); + $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); } } } From 108375b0687a48adb3ca4d90a764769c7e1c3c82 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 16 Feb 2021 15:43:22 +0100 Subject: [PATCH 165/188] [FrameworkBundle] allow container/routing configurators to vary by env --- .../FrameworkExtension.php | 5 +- .../Kernel/MicroKernelTrait.php | 4 +- .../Resources/config/routing.php | 6 +++ .../Bundle/FrameworkBundle/composer.json | 4 +- .../Component/Config/Loader/FileLoader.php | 3 +- .../Component/Config/Loader/Loader.php | 6 +++ .../DependencyInjection/CHANGELOG.md | 1 + .../Loader/ClosureLoader.php | 5 +- .../Configurator/ContainerConfigurator.php | 21 +++++++- .../DependencyInjection/Loader/FileLoader.php | 4 +- .../Loader/IniFileLoader.php | 6 +++ .../Loader/PhpFileLoader.php | 2 +- .../Loader/XmlFileLoader.php | 49 ++++++++++++------- .../Loader/YamlFileLoader.php | 18 ++++++- .../schema/dic/services/services-1.0.xsd | 22 +++++++++ .../Tests/Fixtures/config/when-env.php | 20 ++++++++ .../Tests/Fixtures/ini/when-env.ini | 10 ++++ .../Tests/Fixtures/xml/when-env.xml | 18 +++++++ .../Tests/Fixtures/yaml/when-env.yaml | 12 +++++ .../Tests/Loader/ClosureLoaderTest.php | 7 +-- .../Tests/Loader/IniFileLoaderTest.php | 9 ++++ .../Tests/Loader/PhpFileLoaderTest.php | 9 ++++ .../Tests/Loader/XmlFileLoaderTest.php | 9 ++++ .../Tests/Loader/YamlFileLoaderTest.php | 9 ++++ .../DependencyInjection/composer.json | 4 +- src/Symfony/Component/HttpKernel/Kernel.php | 15 +++--- .../Component/Routing/Annotation/Route.php | 15 +++++- src/Symfony/Component/Routing/CHANGELOG.md | 7 +-- .../Routing/Loader/AnnotationClassLoader.php | 14 +++++- .../Routing/Loader/ClosureLoader.php | 2 +- .../Configurator/RoutingConfigurator.php | 19 ++++++- .../Routing/Loader/ContainerLoader.php | 3 +- .../Component/Routing/Loader/ObjectLoader.php | 2 +- .../Routing/Loader/PhpFileLoader.php | 2 +- .../Routing/Loader/XmlFileLoader.php | 10 ++++ .../Routing/Loader/YamlFileLoader.php | 18 +++++++ .../Loader/schema/routing/routing-1.0.xsd | 9 ++++ .../AnnotationFixtures/RouteWithEnv.php | 25 ++++++++++ .../AttributeFixtures/RouteWithEnv.php | 19 +++++++ .../Routing/Tests/Fixtures/when-env.php | 18 +++++++ .../Routing/Tests/Fixtures/when-env.xml | 17 +++++++ .../Routing/Tests/Fixtures/when-env.yml | 9 ++++ .../Loader/AnnotationClassLoaderTest.php | 11 +++++ ...notationClassLoaderWithAnnotationsTest.php | 4 +- ...nnotationClassLoaderWithAttributesTest.php | 4 +- .../Tests/Loader/ClosureLoaderTest.php | 6 ++- .../Routing/Tests/Loader/ObjectLoaderTest.php | 14 ++++-- .../Tests/Loader/PhpFileLoaderTest.php | 10 ++++ .../Tests/Loader/XmlFileLoaderTest.php | 10 ++++ .../Tests/Loader/YamlFileLoaderTest.php | 10 ++++ src/Symfony/Component/Routing/composer.json | 4 +- 51 files changed, 474 insertions(+), 66 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/when-env.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/ini/when-env.ini create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/when-env.xml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/when-env.yaml create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/RouteWithEnv.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/when-env.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/when-env.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ac4bd6da0b257..43d5d081a8010 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1011,7 +1011,10 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co $container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class) ->setPublic(false) ->addTag('routing.loader', ['priority' => -10]) - ->addArgument(new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + ->setArguments([ + new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), + '%kernel.environment%', + ]); $container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class) ->setPublic(false) diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index 52587cc7c756f..c77c452d99a1a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -152,7 +152,7 @@ public function registerContainerConfiguration(LoaderInterface $loader) }; try { - $this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file), $loader); + $this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader); } finally { $instanceof = []; $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); @@ -193,7 +193,7 @@ public function loadRoutes(LoaderInterface $loader) return $routes->build(); } - $this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file)); + $this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment())); foreach ($collection as $route) { $controller = $route->getDefault('_controller'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php index bd44427bf65a1..09e340ff8aedd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php @@ -49,36 +49,42 @@ ->set('routing.loader.xml', XmlFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.yml', YamlFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.php', PhpFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.glob', GlobFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.directory', DirectoryLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.container', ContainerLoader::class) ->args([ tagged_locator('routing.route_loader'), + '%kernel.environment%', ]) ->tag('routing.loader') diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 0f66c585ed0f2..5cada9c672733 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-xml": "*", "symfony/cache": "^5.2", - "symfony/config": "^5.0", + "symfony/config": "^5.3", "symfony/dependency-injection": "^5.3", "symfony/deprecation-contracts": "^2.1", "symfony/event-dispatcher": "^5.1", @@ -30,7 +30,7 @@ "symfony/polyfill-php80": "^1.15", "symfony/filesystem": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", - "symfony/routing": "^5.2" + "symfony/routing": "^5.3" }, "require-dev": { "doctrine/annotations": "^1.10.4", diff --git a/src/Symfony/Component/Config/Loader/FileLoader.php b/src/Symfony/Component/Config/Loader/FileLoader.php index 0b01df49f3d36..4c2a43b14a47a 100644 --- a/src/Symfony/Component/Config/Loader/FileLoader.php +++ b/src/Symfony/Component/Config/Loader/FileLoader.php @@ -31,9 +31,10 @@ abstract class FileLoader extends Loader private $currentDir; - public function __construct(FileLocatorInterface $locator) + public function __construct(FileLocatorInterface $locator, string $env = null) { $this->locator = $locator; + parent::__construct($env); } /** diff --git a/src/Symfony/Component/Config/Loader/Loader.php b/src/Symfony/Component/Config/Loader/Loader.php index 62cae685e2981..92b894f7d81ca 100644 --- a/src/Symfony/Component/Config/Loader/Loader.php +++ b/src/Symfony/Component/Config/Loader/Loader.php @@ -21,6 +21,12 @@ abstract class Loader implements LoaderInterface { protected $resolver; + protected $env; + + public function __construct(string $env = null) + { + $this->env = $env; + } /** * {@inheritdoc} diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index a6c60db0e25fa..c08e7cb75df25 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Add `%env(not:...)%` processor to negate boolean values * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 * Add autoconfigurable attributes + * Add support for per-env configuration in loaders 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php index 57aa83d6a0a74..a8337d4e66e62 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php @@ -25,9 +25,10 @@ class ClosureLoader extends Loader { private $container; - public function __construct(ContainerBuilder $container) + public function __construct(ContainerBuilder $container, string $env = null) { $this->container = $container; + parent::__construct($env); } /** @@ -35,7 +36,7 @@ public function __construct(ContainerBuilder $container) */ public function load($resource, string $type = null) { - $resource($this->container); + $resource($this->container, $this->env); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php index 7c4377f7aa117..5fc8f5b6c803d 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php @@ -35,14 +35,16 @@ class ContainerConfigurator extends AbstractConfigurator private $path; private $file; private $anonymousCount = 0; + private $env; - public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file) + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, string $env = null) { $this->container = $container; $this->loader = $loader; $this->instanceof = &$instanceof; $this->path = $path; $this->file = $file; + $this->env = $env; } final public function extension(string $namespace, array $config) @@ -71,6 +73,23 @@ final public function services(): ServicesConfigurator return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount); } + /** + * @return static + */ + final public function when(string $env): self + { + if ($env === $this->env) { + return clone $this; + } + + $instanceof = $this->instanceof; + $clone = clone $this; + $clone->container = new ContainerBuilder(clone $this->container->getParameterBag()); + $clone->instanceof = &$instanceof; + + return $clone; + } + /** * @return static */ diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index b0d3e952e9cce..75606c5abf127 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -39,11 +39,11 @@ abstract class FileLoader extends BaseFileLoader protected $singlyImplemented = []; protected $autoRegisterAliasesForSinglyImplementedInterfaces = true; - public function __construct(ContainerBuilder $container, FileLocatorInterface $locator) + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null) { $this->container = $container; - parent::__construct($locator); + parent::__construct($locator, $env); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php index f313cbcae4d24..fbf313878b807 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php @@ -44,6 +44,12 @@ public function load($resource, string $type = null) $this->container->setParameter($key, $this->phpize($value)); } } + + if ($this->env && \is_array($result['parameters@'.$this->env] ?? null)) { + foreach ($result['parameters@'.$this->env] as $key => $value) { + $this->container->setParameter($key, $this->phpize($value)); + } + } } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index 5c7b628eab3ad..130d7cfba7388 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -47,7 +47,7 @@ public function load($resource, string $type = null) $callback = $load($path); if (\is_object($callback) && \is_callable($callback)) { - $callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource), $this->container, $this); + $callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $this->container, $this); } } finally { $this->instanceof = []; diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index a5b42f8835612..b694b7791cdbf 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -50,23 +50,36 @@ public function load($resource, string $type = null) $this->container->fileExists($path); - $defaults = $this->getServiceDefaults($xml, $path); + $this->loadXml($xml, $path); + + if ($this->env) { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + foreach ($xpath->query(sprintf('//container:when[@env="%s"]', $this->env)) ?: [] as $root) { + $this->loadXml($xml, $path, $root); + } + } + } + + private function loadXml(\DOMDocument $xml, string $path, \DOMNode $root = null): void + { + $defaults = $this->getServiceDefaults($xml, $path, $root); // anonymous services - $this->processAnonymousServices($xml, $path); + $this->processAnonymousServices($xml, $path, $root); // imports - $this->parseImports($xml, $path); + $this->parseImports($xml, $path, $root); // parameters - $this->parseParameters($xml, $path); + $this->parseParameters($xml, $path, $root); // extensions - $this->loadFromExtensions($xml); + $this->loadFromExtensions($xml, $root); // services try { - $this->parseDefinitions($xml, $path, $defaults); + $this->parseDefinitions($xml, $path, $defaults, $root); } finally { $this->instanceof = []; $this->registerAliasesForSinglyImplementedInterfaces(); @@ -89,19 +102,19 @@ public function supports($resource, string $type = null) return 'xml' === $type; } - private function parseParameters(\DOMDocument $xml, string $file) + private function parseParameters(\DOMDocument $xml, string $file, \DOMNode $root = null) { - if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) { + 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) + private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (false === $imports = $xpath->query('//container:imports/container:import')) { + if (false === $imports = $xpath->query('.//container:imports/container:import', $root)) { return; } @@ -112,19 +125,19 @@ private function parseImports(\DOMDocument $xml, string $file) } } - private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults) + private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, \DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype|//container:services/container:stack')) { + if (false === $services = $xpath->query('.//container:services/container:service|.//container:services/container:prototype|.//container:services/container:stack', $root)) { return; } $this->setCurrentDir(\dirname($file)); $this->instanceof = []; $this->isLoadingInstanceof = true; - $instanceof = $xpath->query('//container:services/container:instanceof'); + $instanceof = $xpath->query('.//container:services/container:instanceof', $root); foreach ($instanceof as $service) { $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition())); } @@ -170,12 +183,12 @@ private function parseDefinitions(\DOMDocument $xml, string $file, Definition $d } } - private function getServiceDefaults(\DOMDocument $xml, string $file): Definition + private function getServiceDefaults(\DOMDocument $xml, string $file, \DOMNode $root = null): Definition { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) { + if (null === $defaultsNode = $xpath->query('.//container:services/container:defaults', $root)->item(0)) { return new Definition(); } @@ -393,7 +406,7 @@ private function parseFileToDOM(string $file): \DOMDocument /** * Processes anonymous services. */ - private function processAnonymousServices(\DOMDocument $xml, string $file) + private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null) { $definitions = []; $count = 0; @@ -403,7 +416,7 @@ private function processAnonymousServices(\DOMDocument $xml, string $file) $xpath->registerNamespace('container', self::NS); // anonymous services as arguments/properties - if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]|//container:bind[not(@id)]|//container:factory[not(@service)]|//container:configurator[not(@service)]')) { + if (false !== $nodes = $xpath->query('.//container:argument[@type="service"][not(@id)]|.//container:property[@type="service"][not(@id)]|.//container:bind[not(@id)]|.//container:factory[not(@service)]|.//container:configurator[not(@service)]', $root)) { foreach ($nodes as $node) { if ($services = $this->getChildren($node, 'service')) { // give it a unique name @@ -422,7 +435,7 @@ private function processAnonymousServices(\DOMDocument $xml, string $file) } // anonymous services "in the wild" - if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) { + if (false !== $nodes = $xpath->query('.//container:services/container:service[not(@id)]', $root)) { foreach ($nodes as $node) { throw new InvalidArgumentException(sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo())); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 22f029cca85fb..530f88d4b55e7 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -129,6 +129,20 @@ public function load($resource, string $type = null) return; } + $this->loadContent($content, $path); + + // per-env configuration + if ($this->env && isset($content['when@'.$this->env])) { + if (!\is_array($content['when@'.$this->env])) { + throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path)); + } + + $this->loadContent($content['when@'.$this->env], $path); + } + } + + private function loadContent($content, $path) + { // imports $this->parseImports($content, $path); @@ -770,7 +784,7 @@ private function validate($content, string $file): ?array } foreach ($content as $namespace => $data) { - if (\in_array($namespace, ['imports', 'parameters', 'services'])) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { continue; } @@ -907,7 +921,7 @@ private function resolveServices($value, string $file, bool $isParameter = false private function loadFromExtensions(array $content) { foreach ($content as $namespace => $values) { - if (\in_array($namespace, ['imports', 'parameters', 'services'])) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 03988686ba9a4..f295f7e6f7442 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -23,6 +23,27 @@ The root element of a service file. ]]> + + + + + + + + + + + + + + + + + + + + + @@ -38,6 +59,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/when-env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/when-env.php new file mode 100644 index 0000000000000..5170261ad88d6 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/when-env.php @@ -0,0 +1,20 @@ +parameters() + ->set('foo', 123); + + $c->when('some-env') + ->parameters() + ->set('foo', 234) + ->set('bar', 345); + + $c->when('some-other-env') + ->parameters() + ->set('foo', 456) + ->set('baz', 567); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ini/when-env.ini b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ini/when-env.ini new file mode 100644 index 0000000000000..053fe86f86fbf --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ini/when-env.ini @@ -0,0 +1,10 @@ +[parameters@some-env] + foo = 234 + bar = 345 + +[parameters@some-other-env] + foo = 456 + baz = 567 + +[parameters] + foo = 123 diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/when-env.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/when-env.xml new file mode 100644 index 0000000000000..1d12221a2fb50 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/when-env.xml @@ -0,0 +1,18 @@ + + + + 123 + + + + 234 + 345 + + + + + 456 + 567 + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/when-env.yaml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/when-env.yaml new file mode 100644 index 0000000000000..22590d9b80a7d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/when-env.yaml @@ -0,0 +1,12 @@ +when@some-env: + parameters: + foo: 234 + bar: 345 + +when@some-other-env: + parameters: + foo: 456 + baz: 567 + +parameters: + foo: 123 diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php index 125e09b6cf8f0..a42ecf5a81b33 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php @@ -27,12 +27,13 @@ public function testSupports() public function testLoad() { - $loader = new ClosureLoader($container = new ContainerBuilder()); + $loader = new ClosureLoader($container = new ContainerBuilder(), 'some-env'); - $loader->load(function ($container) { + $loader->load(function ($container, $env) { $container->setParameter('foo', 'foo'); + $container->setParameter('env', $env); }); - $this->assertEquals('foo', $container->getParameter('foo'), '->load() loads a \Closure resource'); + $this->assertSame(['foo' => 'foo', 'env' => 'some-env'], $container->getParameterBag()->all()); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php index b4cf503237bc8..07f23f3137af5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php @@ -120,4 +120,13 @@ public function testSupports() $this->assertFalse($loader->supports('foo.foo'), '->supports() returns false if the resource is not loadable'); $this->assertTrue($loader->supports('with_wrong_ext.yml', 'ini'), '->supports() returns true if the resource with forced type is loadable'); } + + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new IniFileLoader($container, new FileLocator(realpath(__DIR__.'/../Fixtures/').'/ini'), 'some-env'); + $loader->load('when-env.ini'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php index bf53669cac6a8..50f3c58dd89e3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php @@ -138,6 +138,15 @@ public function testStack() $this->assertEquals($expected, $container->get('stack_d')); } + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new PhpFileLoader($container, new FileLocator(realpath(__DIR__.'/../Fixtures').'/config'), 'some-env'); + $loader->load('when-env.php'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } + /** * @group legacy */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 8714905f71a8d..1e446d0ada71f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -1083,4 +1083,13 @@ public function testStack() $expected->label = 'Z'; $this->assertEquals($expected, $container->get('stack_d')); } + + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'), 'some-env'); + $loader->load('when-env.xml'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 508ecb8cb8d28..5c2b29559e270 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -1002,4 +1002,13 @@ public function testStack() ]; $this->assertEquals($expected, $container->get('stack_e')); } + + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'), 'some-env'); + $loader->load('when-env.yaml'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } } diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index c5752e46d0214..efec2a3bc3882 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -24,7 +24,7 @@ }, "require-dev": { "symfony/yaml": "^4.4|^5.0", - "symfony/config": "^5.1", + "symfony/config": "^5.3", "symfony/expression-language": "^4.4|^5.0" }, "suggest": { @@ -35,7 +35,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { - "symfony/config": "<5.1", + "symfony/config": "<5.3", "symfony/finder": "<4.4", "symfony/proxy-manager-bridge": "<4.4", "symfony/yaml": "<4.4" diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 5ecad3b9a9bc3..ad6a7e1ec0301 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -751,15 +751,16 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container */ protected function getContainerLoader(ContainerInterface $container) { + $env = $this->getEnvironment(); $locator = new FileLocator($this); $resolver = new LoaderResolver([ - new XmlFileLoader($container, $locator), - new YamlFileLoader($container, $locator), - new IniFileLoader($container, $locator), - new PhpFileLoader($container, $locator), - new GlobFileLoader($container, $locator), - new DirectoryLoader($container, $locator), - new ClosureLoader($container), + new XmlFileLoader($container, $locator, $env), + new YamlFileLoader($container, $locator, $env), + new IniFileLoader($container, $locator, $env), + new PhpFileLoader($container, $locator, $env), + new GlobFileLoader($container, $locator, $env), + new DirectoryLoader($container, $locator, $env), + new ClosureLoader($container, $env), ]); return new DelegatingLoader($resolver); diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index f51b74c38727c..418887da26afd 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -34,6 +34,7 @@ class Route private $schemes = []; private $condition; private $priority; + private $env; /** * @param array|string $data data array managed by the Doctrine Annotations library or the path @@ -59,7 +60,8 @@ public function __construct( string $locale = null, string $format = null, bool $utf8 = null, - bool $stateless = null + bool $stateless = null, + string $env = null ) { if (\is_string($data)) { $data = ['path' => $data]; @@ -84,6 +86,7 @@ public function __construct( $data['format'] = $data['format'] ?? $format; $data['utf8'] = $data['utf8'] ?? $utf8; $data['stateless'] = $data['stateless'] ?? $stateless; + $data['env'] = $data['env'] ?? $env; $data = array_filter($data, static function ($value): bool { return null !== $value; @@ -241,4 +244,14 @@ public function getPriority(): ?int { return $this->priority; } + + public function setEnv(?string $env): void + { + $this->env = $env; + } + + public function getEnv(): ?string + { + return $this->env; + } } diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index 840df13441909..b664e03aea738 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -1,10 +1,11 @@ CHANGELOG ========= -5.3.0 ------ +5.3 +--- - * already encoded slashes are not decoded nor double-encoded anymore when generating URLs + * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs + * Add support for per-env configuration in loaders 5.2.0 ----- diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index bf8fe2af368b1..0b362ad1e0e04 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -73,6 +73,7 @@ abstract class AnnotationClassLoader implements LoaderInterface { protected $reader; + protected $env; /** * @var string @@ -84,9 +85,10 @@ abstract class AnnotationClassLoader implements LoaderInterface */ protected $defaultRouteIndex = 0; - public function __construct(Reader $reader = null) + public function __construct(Reader $reader = null, string $env = null) { $this->reader = $reader; + $this->env = $env; } /** @@ -122,6 +124,10 @@ public function load($class, string $type = null) $collection = new RouteCollection(); $collection->addResource(new FileResource($class->getFileName())); + if ($globals['env'] && $this->env !== $globals['env']) { + return $collection; + } + foreach ($class->getMethods() as $method) { $this->defaultRouteIndex = 0; foreach ($this->getAnnotations($method) as $annot) { @@ -144,6 +150,10 @@ public function load($class, string $type = null) */ protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method) { + if ($annot->getEnv() && $annot->getEnv() !== $this->env) { + return; + } + $name = $annot->getName(); if (null === $name) { $name = $this->getDefaultRouteName($class, $method); @@ -317,6 +327,7 @@ protected function getGlobals(\ReflectionClass $class) } $globals['priority'] = $annot->getPriority() ?? 0; + $globals['env'] = $annot->getEnv(); foreach ($globals['requirements'] as $placeholder => $requirement) { if (\is_int($placeholder)) { @@ -342,6 +353,7 @@ private function resetGlobals(): array 'condition' => '', 'name' => '', 'priority' => 0, + 'env' => null, ]; } diff --git a/src/Symfony/Component/Routing/Loader/ClosureLoader.php b/src/Symfony/Component/Routing/Loader/ClosureLoader.php index cea5f9c1947d8..2407307482ea0 100644 --- a/src/Symfony/Component/Routing/Loader/ClosureLoader.php +++ b/src/Symfony/Component/Routing/Loader/ClosureLoader.php @@ -33,7 +33,7 @@ class ClosureLoader extends Loader */ public function load($closure, string $type = null) { - return $closure(); + return $closure($this->env); } /** diff --git a/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php index e5086e2441b85..70d68dfc3c737 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php @@ -24,13 +24,15 @@ class RoutingConfigurator private $loader; private $path; private $file; + private $env; - public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file) + public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, string $env = null) { $this->collection = $collection; $this->loader = $loader; $this->path = $path; $this->file = $file; + $this->env = $env; } /** @@ -58,6 +60,21 @@ final public function collection(string $name = ''): CollectionConfigurator return new CollectionConfigurator($this->collection, $name); } + /** + * @return static + */ + final public function when(string $env): self + { + if ($env === $this->env) { + return clone $this; + } + + $clone = clone $this; + $clone->collection = new RouteCollection(); + + return $clone; + } + /** * @return static */ diff --git a/src/Symfony/Component/Routing/Loader/ContainerLoader.php b/src/Symfony/Component/Routing/Loader/ContainerLoader.php index 92bf2a096bf48..8128b742135b3 100644 --- a/src/Symfony/Component/Routing/Loader/ContainerLoader.php +++ b/src/Symfony/Component/Routing/Loader/ContainerLoader.php @@ -22,9 +22,10 @@ class ContainerLoader extends ObjectLoader { private $container; - public function __construct(ContainerInterface $container) + public function __construct(ContainerInterface $container, string $env = null) { $this->container = $container; + parent::__construct($env); } /** diff --git a/src/Symfony/Component/Routing/Loader/ObjectLoader.php b/src/Symfony/Component/Routing/Loader/ObjectLoader.php index d6ec1a727765e..062453908c948 100644 --- a/src/Symfony/Component/Routing/Loader/ObjectLoader.php +++ b/src/Symfony/Component/Routing/Loader/ObjectLoader.php @@ -59,7 +59,7 @@ public function load($resource, string $type = null) throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, get_debug_type($loaderObject), $resource)); } - $routeCollection = $loaderObject->$method($this); + $routeCollection = $loaderObject->$method($this, $this->env); if (!$routeCollection instanceof RouteCollection) { $type = get_debug_type($routeCollection); diff --git a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php index e000c5a0ebaed..2418b0d322abe 100644 --- a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php @@ -71,7 +71,7 @@ protected function callConfigurator(callable $result, string $path, string $file { $collection = new RouteCollection(); - $result(new RoutingConfigurator($collection, $this, $path, $file)); + $result(new RoutingConfigurator($collection, $this, $path, $file, $this->env)); return $collection; } diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 9951625be93df..4f38ce95ccadc 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -88,6 +88,16 @@ protected function parseNode(RouteCollection $collection, \DOMElement $node, str case 'import': $this->parseImport($collection, $node, $path, $file); break; + case 'when': + if (!$this->env || $node->getAttribute('env') !== $this->env) { + break; + } + foreach ($node->childNodes as $node) { + if ($node instanceof \DOMElement) { + $this->parseNode($collection, $node, $path, $file); + } + } + break; default: throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); } diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index b236e4b54e8f6..1ec8a810a2b31 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -84,6 +84,24 @@ public function load($file, string $type = null) } foreach ($parsedConfig as $name => $config) { + if (0 === strpos($name, 'when@')) { + if (!$this->env || 'when@'.$this->env !== $name) { + continue; + } + + foreach ($config as $name => $config) { + $this->validate($config, $name.'" when "@'.$this->env, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + continue; + } + $this->validate($config, $name, $path); if (isset($config['resource'])) { diff --git a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd index 846d126724d5e..e03114791ec8b 100644 --- a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd +++ b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd @@ -21,9 +21,18 @@ + + + + + + + + + diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/RouteWithEnv.php b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/RouteWithEnv.php new file mode 100644 index 0000000000000..dcc94e7a174e3 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/RouteWithEnv.php @@ -0,0 +1,25 @@ +when('some-env') + ->add('a', '/a2') + ->add('b', '/b'); + + $routes + ->when('some-other-env') + ->add('a', '/a3') + ->add('c', '/c'); + + $routes + ->add('a', '/a1'); +}; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml new file mode 100644 index 0000000000000..50d1fd8b397fe --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/when-env.yml b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.yml new file mode 100644 index 0000000000000..0f97ab22f2261 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.yml @@ -0,0 +1,9 @@ +when@some-env: + a: {path: /a2} + b: {path: /b} + +when@some-other-env: + a: {path: /a3} + c: {path: /c} + +a: {path: /a1} diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php index 3b82499c4f550..a4f6e3e3a6d13 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -247,5 +247,16 @@ public function testLoadingRouteWithPrefix() $this->assertEquals('/prefix/path', $routes->get('action')->getPath()); } + public function testWhenEnv() + { + $routes = $this->loader->load($this->getNamespace().'\RouteWithEnv'); + $this->assertCount(0, $routes); + + $this->setUp('some-env'); + $routes = $this->loader->load($this->getNamespace().'\RouteWithEnv'); + $this->assertCount(1, $routes); + $this->assertSame('/path', $routes->get('action')->getPath()); + } + abstract protected function getNamespace(): string; } diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php index ef9ca39e827fe..d2fe627ef99ea 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php @@ -9,10 +9,10 @@ class AnnotationClassLoaderWithAnnotationsTest extends AnnotationClassLoaderTest { - protected function setUp(): void + protected function setUp(string $env = null): void { $reader = new AnnotationReader(); - $this->loader = new class($reader) extends AnnotationClassLoader { + $this->loader = new class($reader, $env) extends AnnotationClassLoader { protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void { } diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php index 1545253e56d96..ea2a5c573b49a 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php @@ -10,9 +10,9 @@ */ class AnnotationClassLoaderWithAttributesTest extends AnnotationClassLoaderTest { - protected function setUp(): void + protected function setUp(string $env = null): void { - $this->loader = new class() extends AnnotationClassLoader { + $this->loader = new class(null, $env) extends AnnotationClassLoader { protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void { } diff --git a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php index 5d963f86fb01b..da8ad090dd4d8 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php @@ -33,10 +33,12 @@ public function testSupports() public function testLoad() { - $loader = new ClosureLoader(); + $loader = new ClosureLoader('some-env'); $route = new Route('/'); - $routes = $loader->load(function () use ($route) { + $routes = $loader->load(function (string $env = null) use ($route) { + $this->assertSame('some-env', $env); + $routes = new RouteCollection(); $routes->add('foo', $route); diff --git a/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php index 50e75b5fd4cc2..54d3643b1f584 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php @@ -20,14 +20,14 @@ class ObjectLoaderTest extends TestCase { public function testLoadCallsServiceAndReturnsCollection() { - $loader = new TestObjectLoader(); + $loader = new TestObjectLoader('some-env'); // create a basic collection that will be returned $collection = new RouteCollection(); $collection->add('foo', new Route('/foo')); $loader->loaderMap = [ - 'my_route_provider_service' => new TestObjectLoaderRouteService($collection), + 'my_route_provider_service' => new TestObjectLoaderRouteService($collection, 'some-env'), ]; $actualRoutes = $loader->load( @@ -112,14 +112,20 @@ protected function getObject(string $id) class TestObjectLoaderRouteService { private $collection; + private $env; - public function __construct($collection) + public function __construct($collection, string $env = null) { $this->collection = $collection; + $this->env = $env; } - public function loadRoutes() + public function loadRoutes(TestObjectLoader $loader, string $env = null) { + if ($this->env !== $env) { + throw new \InvalidArgumentException(sprintf('Expected env "%s", "%s" given.', $this->env, $env)); + } + return $this->collection; } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php index 7ed941c711e3b..fd3fca4154a1c 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php @@ -284,4 +284,14 @@ public function testImportingRoutesWithSingleHostInImporter() $this->assertEquals($expectedRoutes('php'), $routes); } + + public function testWhenEnv() + { + $loader = new PhpFileLoader(new FileLocator([__DIR__.'/../Fixtures']), 'some-env'); + $routes = $loader->load('when-env.php'); + + $this->assertSame(['b', 'a'], array_keys($routes->all())); + $this->assertSame('/b', $routes->get('b')->getPath()); + $this->assertSame('/a1', $routes->get('a')->getPath()); + } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php index 9ab49fc1311aa..8518129a5b495 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php @@ -563,4 +563,14 @@ public function testImportingRoutesWithSingleHostsInImporter() $this->assertEquals($expectedRoutes('xml'), $routes); } + + public function testWhenEnv() + { + $loader = new XmlFileLoader(new FileLocator([__DIR__.'/../Fixtures']), 'some-env'); + $routes = $loader->load('when-env.xml'); + + $this->assertSame(['b', 'a'], array_keys($routes->all())); + $this->assertSame('/b', $routes->get('b')->getPath()); + $this->assertSame('/a1', $routes->get('a')->getPath()); + } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 56915c5ce5e6c..09e3f07951024 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -435,4 +435,14 @@ public function testImportingRoutesWithSingleHostInImporter() $this->assertEquals($expectedRoutes('yml'), $routes); } + + public function testWhenEnv() + { + $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures']), 'some-env'); + $routes = $loader->load('when-env.yml'); + + $this->assertSame(['b', 'a'], array_keys($routes->all())); + $this->assertSame('/b', $routes->get('b')->getPath()); + $this->assertSame('/a1', $routes->get('a')->getPath()); + } } diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index 164dbb2bfdc99..11da29d649e55 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -21,7 +21,7 @@ "symfony/polyfill-php80": "^1.15" }, "require-dev": { - "symfony/config": "^5.0", + "symfony/config": "^5.3", "symfony/http-foundation": "^4.4|^5.0", "symfony/yaml": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", @@ -30,7 +30,7 @@ "psr/log": "~1.0" }, "conflict": { - "symfony/config": "<5.0", + "symfony/config": "<5.3", "symfony/dependency-injection": "<4.4", "symfony/yaml": "<4.4" }, From dab91f78e18beb27eb5a81eca8c33821ec4bf163 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 19 Feb 2021 16:39:40 +0100 Subject: [PATCH 166/188] [Intl] Add `Currencies::getCashFractionDigits()` and `Currencies::getCashRoundingIncrement()` --- src/Symfony/Component/Intl/CHANGELOG.md | 5 +++++ src/Symfony/Component/Intl/Currencies.php | 25 +++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index b7fb561e7c080..012a894c50179 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3 +--- + + * Add `Currencies::getCashFractionDigits()` and `Currencies::getCashRoundingIncrement()` + 5.0.0 ----- diff --git a/src/Symfony/Component/Intl/Currencies.php b/src/Symfony/Component/Intl/Currencies.php index c155c8f09f425..60dbfcd6f1d36 100644 --- a/src/Symfony/Component/Intl/Currencies.php +++ b/src/Symfony/Component/Intl/Currencies.php @@ -25,6 +25,8 @@ final class Currencies extends ResourceBundle private const INDEX_NAME = 1; private const INDEX_FRACTION_DIGITS = 0; private const INDEX_ROUNDING_INCREMENT = 1; + private const INDEX_CASH_FRACTION_DIGITS = 2; + private const INDEX_CASH_ROUNDING_INCREMENT = 3; /** * @return string[] @@ -94,10 +96,7 @@ public static function getFractionDigits(string $currency): int } } - /** - * @return float|int - */ - public static function getRoundingIncrement(string $currency) + public static function getRoundingIncrement(string $currency): int { try { return self::readEntry(['Meta', $currency, self::INDEX_ROUNDING_INCREMENT], 'meta'); @@ -106,6 +105,24 @@ public static function getRoundingIncrement(string $currency) } } + public static function getCashFractionDigits(string $currency): int + { + try { + return self::readEntry(['Meta', $currency, self::INDEX_CASH_FRACTION_DIGITS], 'meta'); + } catch (MissingResourceException $e) { + return self::readEntry(['Meta', 'DEFAULT', self::INDEX_CASH_FRACTION_DIGITS], 'meta'); + } + } + + public static function getCashRoundingIncrement(string $currency): int + { + try { + return self::readEntry(['Meta', $currency, self::INDEX_CASH_ROUNDING_INCREMENT], 'meta'); + } catch (MissingResourceException $e) { + return self::readEntry(['Meta', 'DEFAULT', self::INDEX_CASH_ROUNDING_INCREMENT], 'meta'); + } + } + /** * @throws MissingResourceException if the currency code has no numeric code */ From a9dea1db562a118b47f45019f5f0bfe96a14fe9d Mon Sep 17 00:00:00 2001 From: Timo Bakx Date: Sat, 5 Dec 2020 10:55:10 +0100 Subject: [PATCH 167/188] [Security] Added debug:firewall command --- .../Bundle/SecurityBundle/CHANGELOG.md | 1 + .../Command/DebugFirewallCommand.php | 275 ++++++++++++++++++ .../DependencyInjection/SecurityExtension.php | 15 +- .../Resources/config/debug_console.php | 28 ++ 4 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Resources/config/debug_console.php diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index d50c23959b74c..2473dfd9ced74 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3 --- + * Add the `debug:firewall` command. * Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command, use `UserPasswordHashCommand` and `user:hash-password` instead * Deprecate the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases, diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php new file mode 100644 index 0000000000000..56c3cf14476f3 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Command; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallContext; +use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; +use Symfony\Component\Console\Command\Command; +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\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; + +/** + * @author Timo Bakx + */ +final class DebugFirewallCommand extends Command +{ + protected static $defaultName = 'debug:firewall'; + protected static $defaultDescription = 'Displays information about your security firewall(s)'; + + private $firewallNames; + private $contexts; + private $eventDispatchers; + private $authenticators; + private $authenticatorManagerEnabled; + + /** + * @param string[] $firewallNames + * @param AuthenticatorInterface[][] $authenticators + */ + public function __construct(array $firewallNames, ContainerInterface $contexts, ContainerInterface $eventDispatchers, array $authenticators, bool $authenticatorManagerEnabled) + { + $this->firewallNames = $firewallNames; + $this->contexts = $contexts; + $this->eventDispatchers = $eventDispatchers; + $this->authenticators = $authenticators; + $this->authenticatorManagerEnabled = $authenticatorManagerEnabled; + + parent::__construct(); + } + + protected function configure(): void + { + $exampleName = $this->getExampleName(); + + $this + ->setDescription(self::$defaultDescription) + ->setHelp(<<%command.name% command displays the firewalls that are configured +in your application: + + php %command.full_name% + +You can pass a firewall name to display more detailed information about +a specific firewall: + + php %command.full_name% $exampleName + +To include all events and event listeners for a specific firewall, use the +events option: + + php %command.full_name% --events $exampleName + +EOF) + ->setDefinition([ + new InputArgument('name', InputArgument::OPTIONAL, sprintf('A firewall name (for example "%s")', $exampleName)), + new InputOption('events', null, InputOption::VALUE_NONE, 'Include a list of event listeners (only available in combination with the "name" argument)'), + ]); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $name = $input->getArgument('name'); + + if (null === $name) { + $this->displayFirewallList($io); + + return 0; + } + + $serviceId = sprintf('security.firewall.map.context.%s', $name); + + if (!$this->contexts->has($serviceId)) { + $io->error(sprintf('Firewall %s was not found. Available firewalls are: %s', $name, implode(', ', $this->firewallNames))); + + return 1; + } + + /** @var FirewallContext $context */ + $context = $this->contexts->get($serviceId); + + $io->title(sprintf('Firewall "%s"', $name)); + + $this->displayFirewallSummary($name, $context, $io); + + $this->displaySwitchUser($context, $io); + + if ($input->getOption('events')) { + $this->displayEventListeners($name, $context, $io); + } + + if ($this->authenticatorManagerEnabled) { + $this->displayAuthenticators($name, $io); + } + + return 0; + } + + protected function displayFirewallList(SymfonyStyle $io): void + { + $io->title('Firewalls'); + $io->text('The following firewalls are defined:'); + + $io->listing($this->firewallNames); + + $io->comment(sprintf('To view details of a specific firewall, re-run this command with a firewall name. (e.g. debug:firewall %s)', $this->getExampleName())); + } + + protected function displayFirewallSummary(string $name, FirewallContext $context, SymfonyStyle $io): void + { + if (null === $context->getConfig()) { + return; + } + + $rows = [ + ['Name', $name], + ['Context', $context->getConfig()->getContext()], + ['Lazy', $context instanceof LazyFirewallContext ? 'Yes' : 'No'], + ['Stateless', $context->getConfig()->isStateless() ? 'Yes' : 'No'], + ['User Checker', $context->getConfig()->getUserChecker()], + ['Provider', $context->getConfig()->getProvider()], + ['Entry Point', $context->getConfig()->getEntryPoint()], + ['Access Denied URL', $context->getConfig()->getAccessDeniedUrl()], + ['Access Denied Handler', $context->getConfig()->getAccessDeniedHandler()], + ]; + + $io->table( + ['Option', 'Value'], + $rows + ); + } + + private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io) + { + if ((null === $config = $context->getConfig()) || (null === $switchUser = $config->getSwitchUser())) { + return; + } + + $io->section('User switching'); + + $io->table(['Option', 'Value'], [ + ['Parameter', $switchUser['parameter'] ?? ''], + ['Provider', $switchUser['provider'] ?? $config->getProvider()], + ['User Role', $switchUser['role'] ?? ''], + ]); + } + + protected function displayEventListeners(string $name, FirewallContext $context, SymfonyStyle $io): void + { + $io->title(sprintf('Event listeners for firewall "%s"', $name)); + + $dispatcherId = sprintf('security.event_dispatcher.%s', $name); + + if (!$this->eventDispatchers->has($dispatcherId)) { + $io->text('No event dispatcher has been registered for this firewall.'); + + return; + } + + /** @var EventDispatcherInterface $dispatcher */ + $dispatcher = $this->eventDispatchers->get($dispatcherId); + + foreach ($dispatcher->getListeners() as $event => $listeners) { + $io->section(sprintf('"%s" event', $event)); + + $rows = []; + foreach ($listeners as $order => $listener) { + $rows[] = [ + sprintf('#%d', $order + 1), + $this->formatCallable($listener), + $dispatcher->getListenerPriority($event, $listener), + ]; + } + + $io->table( + ['Order', 'Callable', 'Priority'], + $rows + ); + } + } + + private function displayAuthenticators(string $name, SymfonyStyle $io): void + { + $io->title(sprintf('Authenticators for firewall "%s"', $name)); + + $authenticators = $this->authenticators[$name] ?? []; + + if (0 === \count($authenticators)) { + $io->text('No authenticators have been registered for this firewall.'); + + return; + } + + $io->table( + ['Classname'], + array_map( + static function ($authenticator) { + return [ + \get_class($authenticator), + ]; + }, + $authenticators + ) + ); + } + + private function formatCallable($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], $callable[1]); + } + + if (\is_string($callable)) { + return sprintf('%s()', $callable); + } + + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + if (false !== strpos($r->name, '{closure}')) { + return 'Closure()'; + } + if ($class = $r->getClosureScopeClass()) { + return sprintf('%s::%s()', $class->name, $r->name); + } + + return $r->name.'()'; + } + + if (method_exists($callable, '__invoke')) { + return sprintf('%s::__invoke()', \get_class($callable)); + } + + throw new \InvalidArgumentException('Callable is not describable.'); + } + + private function getExampleName(): string + { + $name = 'main'; + + if (!\in_array($name, $this->firewallNames, true)) { + $name = reset($this->firewallNames); + } + + return $name; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 368f4acf9f3d1..5c782a25bf87e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -164,6 +164,12 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']); $container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']); + if (class_exists(Application::class)) { + $loader->load('debug_console.php'); + $debugCommand = $container->getDefinition('security.command.debug_firewall'); + $debugCommand->replaceArgument(4, $this->authenticatorManagerEnabled); + } + $this->createFirewalls($config, $container); $this->createAuthorization($config, $container); $this->createRoleHierarchy($config, $container); @@ -298,7 +304,10 @@ private function createFirewalls(array $config, ContainerBuilder $container) $contextRefs[$contextId] = new Reference($contextId); $map[$contextId] = $matcher; } - $mapDef->replaceArgument(0, ServiceLocatorTagPass::register($container, $contextRefs)); + + $container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs)); + + $mapDef->replaceArgument(0, new Reference('security.firewall.context_locator')); $mapDef->replaceArgument(1, new IteratorArgument($map)); if (!$this->authenticatorManagerEnabled) { @@ -503,6 +512,10 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); $listeners[] = new Reference('security.firewall.authenticator.'.$id); + + // Add authenticators to the debug:firewall command + $debugCommand = $container->getDefinition('security.command.debug_firewall'); + $debugCommand->replaceArgument(3, array_merge($debugCommand->getArgument(3), [$id => $authenticators])); } $config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/debug_console.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/debug_console.php new file mode 100644 index 0000000000000..242722f72452a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/debug_console.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\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\Command\DebugFirewallCommand; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.command.debug_firewall', DebugFirewallCommand::class) + ->args([ + param('security.firewalls'), + service('security.firewall.context_locator'), + tagged_locator('event_dispatcher.dispatcher'), + [], + false, + ]) + ->tag('console.command', ['command' => 'debug:firewall']) + ; +}; From b8f5b7ab3b355fd74c1fcabb51f6b27fbc78ff13 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Fri, 19 Feb 2021 19:16:31 +0100 Subject: [PATCH 168/188] [#39326] Fix PHP 7.2 heredoc syntax --- .../Bundle/SecurityBundle/Command/DebugFirewallCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index 56c3cf14476f3..c97bff11b454b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -74,7 +74,8 @@ protected function configure(): void php %command.full_name% --events $exampleName -EOF) +EOF + ) ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, sprintf('A firewall name (for example "%s")', $exampleName)), new InputOption('events', null, InputOption::VALUE_NONE, 'Include a list of event listeners (only available in combination with the "name" argument)'), From 483f840a0819dbf6d34e15b18324823b170aff00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 23 Feb 2021 23:31:03 +0100 Subject: [PATCH 169/188] Remove service session.storage.mock_file when user configure factory --- .../FrameworkBundle/DependencyInjection/FrameworkExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 43d5d081a8010..ca1f829f10357 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1050,6 +1050,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $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'); } From 29b0f96046b54f9517df7991933d2ffa0d3b33e4 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 21 Feb 2021 11:40:41 +0100 Subject: [PATCH 170/188] [Routing] Construct Route annotations using named arguments --- UPGRADE-5.3.md | 5 +++ UPGRADE-6.0.md | 1 + composer.json | 3 +- .../Component/Routing/Annotation/Route.php | 3 ++ src/Symfony/Component/Routing/CHANGELOG.md | 1 + .../Routing/Tests/Annotation/RouteTest.php | 36 ++++++++++++++++--- .../Tests/Loader/AnnotationFileLoaderTest.php | 2 +- src/Symfony/Component/Routing/composer.json | 3 +- 8 files changed, 47 insertions(+), 7 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 83435825d6d6c..e6d07e7232573 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -68,6 +68,11 @@ PropertyInfo * Deprecated the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead +Routing +------- + + * Deprecated creating instances of the `Route` annotation class by passing an array of parameters, use named arguments instead + Security -------- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 64dc873af4a86..97521d71efad2 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -164,6 +164,7 @@ Routing * Removed `RouteCollectionBuilder`. * Added argument `$priority` to `RouteCollection::add()` * Removed the `RouteCompiler::REGEX_DELIMITER` constant + * Removed the `$data` parameter from the constructor of the `Route` annotation class Security -------- diff --git a/composer.json b/composer.json index 41e1911b6eca3..eca08b56363b4 100644 --- a/composer.json +++ b/composer.json @@ -123,7 +123,7 @@ "async-aws/sqs": "^1.0", "cache/integration-tests": "dev-master", "composer/package-versions-deprecated": "^1.8", - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "^1.12", "doctrine/cache": "~1.6", "doctrine/collections": "~1.0", "doctrine/data-fixtures": "^1.1", @@ -151,6 +151,7 @@ }, "conflict": { "async-aws/core": "<1.5", + "doctrine/annotations": "<1.12", "doctrine/dbal": "<2.10", "masterminds/html5": "<2.6", "phpdocumentor/reflection-docblock": "<3.2.2", diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index 418887da26afd..4751f14b2d84d 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -15,6 +15,7 @@ * Annotation class for @Route(). * * @Annotation + * @NamedArgumentConstructor * @Target({"CLASS", "METHOD"}) * * @author Fabien Potencier @@ -67,6 +68,8 @@ public function __construct( $data = ['path' => $data]; } elseif (!\is_array($data)) { throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); + } elseif ([] !== $data) { + trigger_deprecation('symfony/routing', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); } if (null !== $path && !\is_string($path) && !\is_array($path)) { throw new \TypeError(sprintf('"%s": Argument $path is expected to be a string, array or null, got "%s".', __METHOD__, get_debug_type($path))); diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index b664e03aea738..5f80dde1a9455 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs * Add support for per-env configuration in loaders + * Deprecate creating instances of the `Route` annotation class by passing an array of parameters 5.2.0 ----- diff --git a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php index a482378bdec79..d0c57113adab8 100644 --- a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php @@ -12,16 +12,25 @@ namespace Symfony\Component\Routing\Tests\Annotation; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Routing\Annotation\Route; class RouteTest extends TestCase { + use ExpectDeprecationTrait; + + /** + * @group legacy + */ public function testInvalidRouteParameter() { $this->expectException(\BadMethodCallException::class); new Route(['foo' => 'bar']); } + /** + * @group legacy + */ public function testTryingToSetLocalesDirectly() { $this->expectException(\BadMethodCallException::class); @@ -29,18 +38,30 @@ public function testTryingToSetLocalesDirectly() } /** + * @requires PHP 8 * @dataProvider getValidParameters */ - public function testRouteParameters($parameter, $value, $getter) + public function testRouteParameters(string $parameter, $value, string $getter) + { + $route = new Route(...[$parameter => $value]); + $this->assertEquals($route->$getter(), $value); + } + + /** + * @group legacy + * @dataProvider getLegacyValidParameters + */ + public function testLegacyRouteParameters(string $parameter, $value, string $getter) { + $this->expectDeprecation('Since symfony/routing 5.3: Passing an array as first argument to "Symfony\Component\Routing\Annotation\Route::__construct" is deprecated. Use named arguments instead.'); + $route = new Route([$parameter => $value]); $this->assertEquals($route->$getter(), $value); } - public function getValidParameters() + public function getValidParameters(): iterable { return [ - ['value', '/Blog', 'getPath'], ['requirements', ['locale' => 'en'], 'getRequirements'], ['options', ['compiler_class' => 'RouteCompiler'], 'getOptions'], ['name', 'blog_index', 'getName'], @@ -49,7 +70,14 @@ public function getValidParameters() ['methods', ['GET', 'POST'], 'getMethods'], ['host', '{locale}.example.com', 'getHost'], ['condition', 'context.getMethod() == "GET"', 'getCondition'], - ['value', ['nl' => '/hier', 'en' => '/here'], 'getLocalizedPaths'], ]; } + + public function getLegacyValidParameters(): iterable + { + yield from $this->getValidParameters(); + + yield ['value', '/Blog', 'getPath']; + yield ['value', ['nl' => '/hier', 'en' => '/here'], 'getLocalizedPaths']; + } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php index ee66ef1804ed3..0e1331bf8147a 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php @@ -51,7 +51,7 @@ public function testLoadFileWithoutStartTag() public function testLoadVariadic() { - $route = new Route(['path' => '/path/to/{id}']); + $route = new Route('/path/to/{id}'); $this->reader->expects($this->once())->method('getClassAnnotation'); $this->reader->expects($this->once())->method('getMethodAnnotations') ->willReturn([$route]); diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index 11da29d649e55..aeccf0681f3ee 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -26,10 +26,11 @@ "symfony/yaml": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "^1.12", "psr/log": "~1.0" }, "conflict": { + "doctrine/annotations": "<1.12", "symfony/config": "<5.3", "symfony/dependency-injection": "<4.4", "symfony/yaml": "<4.4" From e872db4a1c0f3b951984eba9a4423f7df56c7b71 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 24 Feb 2021 14:32:19 +0100 Subject: [PATCH 171/188] Add default issue templates back --- .github/ISSUE_TEMPLATE/1_Bug_report.md | 22 +++++++++++++++++++ .github/ISSUE_TEMPLATE/2_Feature_request.md | 12 ++++++++++ .github/ISSUE_TEMPLATE/3_Support_question.md | 11 ++++++++++ .../ISSUE_TEMPLATE/4_Documentation_issue.md | 10 +++++++++ 4 files changed, 55 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/1_Bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/2_Feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/3_Support_question.md create mode 100644 .github/ISSUE_TEMPLATE/4_Documentation_issue.md diff --git a/.github/ISSUE_TEMPLATE/1_Bug_report.md b/.github/ISSUE_TEMPLATE/1_Bug_report.md new file mode 100644 index 0000000000000..aef16611e0f77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_Bug_report.md @@ -0,0 +1,22 @@ +--- +name: 🐛 Bug Report +about: ⚠️ See below for security reports +labels: Bug + +--- + +**Symfony version(s) affected**: x.y.z + +**Description** + + +**How to reproduce** + + +**Possible Solution** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/2_Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md new file mode 100644 index 0000000000000..908c5ee52664d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_Feature_request.md @@ -0,0 +1,12 @@ +--- +name: 🚀 Feature Request +about: RFC and ideas for new features and improvements + +--- + +**Description** + + +**Example** + diff --git a/.github/ISSUE_TEMPLATE/3_Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md new file mode 100644 index 0000000000000..9480710c15655 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3_Support_question.md @@ -0,0 +1,11 @@ +--- +name: ⛔ Support Question +about: See https://symfony.com/support for questions about using Symfony and its components + +--- + +We use GitHub issues only to discuss about Symfony bugs and new features. For +this kind of questions about using Symfony or third-party bundles, please use +any of the support alternatives shown in https://symfony.com/support + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md new file mode 100644 index 0000000000000..0855c3c5f1e12 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md @@ -0,0 +1,10 @@ +--- +name: ⛔ Documentation Issue +about: See https://github.com/symfony/symfony-docs/issues for documentation issues + +--- + +Symfony Documentation has its own dedicated repository. Please open your +documentation-related issue at https://github.com/symfony/symfony-docs/issues + +Thanks! From 8e3058d95acc7d177516bfacce794f64695c2b0d Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 24 Feb 2021 01:33:55 +0100 Subject: [PATCH 172/188] Deprecate passing null as $message or $code to exceptions --- .../FileLoaderImportCircularReferenceException.php | 6 ++++++ .../Config/Exception/LoaderLoadException.php | 6 ++++++ .../Exception/AccessDeniedHttpException.php | 6 ++++++ .../HttpKernel/Exception/BadRequestHttpException.php | 6 ++++++ .../HttpKernel/Exception/ConflictHttpException.php | 6 ++++++ .../HttpKernel/Exception/GoneHttpException.php | 6 ++++++ .../Component/HttpKernel/Exception/HttpException.php | 11 +++++++++++ .../Exception/LengthRequiredHttpException.php | 6 ++++++ .../Exception/MethodNotAllowedHttpException.php | 11 +++++++++++ .../Exception/NotAcceptableHttpException.php | 6 ++++++ .../HttpKernel/Exception/NotFoundHttpException.php | 6 ++++++ .../Exception/PreconditionFailedHttpException.php | 6 ++++++ .../Exception/PreconditionRequiredHttpException.php | 6 ++++++ .../Exception/ServiceUnavailableHttpException.php | 11 +++++++++++ .../Exception/TooManyRequestsHttpException.php | 11 +++++++++++ .../Exception/UnauthorizedHttpException.php | 11 +++++++++++ .../Exception/UnprocessableEntityHttpException.php | 6 ++++++ .../Exception/UnsupportedMediaTypeHttpException.php | 6 ++++++ .../Mailer/Exception/HttpTransportException.php | 6 ++++++ .../Routing/Exception/MethodNotAllowedException.php | 6 ++++++ 20 files changed, 145 insertions(+) diff --git a/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php b/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php index aa27f9869b7e2..e235ea04956a6 100644 --- a/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php +++ b/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php @@ -20,6 +20,12 @@ class FileLoaderImportCircularReferenceException extends LoaderLoadException { 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/src/Symfony/Component/Config/Exception/LoaderLoadException.php b/src/Symfony/Component/Config/Exception/LoaderLoadException.php index 86886058668a6..24302b7cba02a 100644 --- a/src/Symfony/Component/Config/Exception/LoaderLoadException.php +++ b/src/Symfony/Component/Config/Exception/LoaderLoadException.php @@ -27,6 +27,12 @@ class LoaderLoadException extends \Exception */ public function __construct(string $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; + } + $message = ''; if ($previous) { // Include the previous exception, to help the user see what might be the underlying cause diff --git a/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php index f0c81111db543..58680a327838c 100644 --- a/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php @@ -24,6 +24,12 @@ class AccessDeniedHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php b/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php index 8eccce1e163e7..f530f7db4927c 100644 --- a/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php @@ -23,6 +23,12 @@ class BadRequestHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php b/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php index 72b8aa1274fe2..79c36041c3f55 100644 --- a/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php @@ -23,6 +23,12 @@ class ConflictHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php b/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php index 6bba8159a2dce..9ea65057b38f5 100644 --- a/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php @@ -23,6 +23,12 @@ class GoneHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/HttpException.php b/src/Symfony/Component/HttpKernel/Exception/HttpException.php index f3c0c3362f949..249fe366d5b76 100644 --- a/src/Symfony/Component/HttpKernel/Exception/HttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/HttpException.php @@ -23,6 +23,17 @@ class HttpException extends \RuntimeException implements HttpExceptionInterface 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; diff --git a/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php b/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php index 92f9c74daf5e1..fcac13785220a 100644 --- a/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php @@ -23,6 +23,12 @@ class LengthRequiredHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php index 665ae355b47a2..37576bcacb354 100644 --- a/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php @@ -24,6 +24,17 @@ class MethodNotAllowedHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php b/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php index a985e86b9b02d..5a422406ba715 100644 --- a/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php @@ -23,6 +23,12 @@ class NotAcceptableHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php b/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php index 3be305ee9ea5b..a475113c5fe81 100644 --- a/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php @@ -23,6 +23,12 @@ class NotFoundHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php index bdedea143d2e7..e23740a28dcf2 100644 --- a/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php @@ -23,6 +23,12 @@ class PreconditionFailedHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php b/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php index bc26804830012..5c31fae822b0c 100644 --- a/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php @@ -25,6 +25,12 @@ class PreconditionRequiredHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php b/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php index 1fb793dbf0b31..d5681bbeb3bc8 100644 --- a/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php @@ -24,6 +24,17 @@ class ServiceUnavailableHttpException extends HttpException */ public function __construct($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/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php b/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php index e1e47d048b248..fd74402b5d033 100644 --- a/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php @@ -26,6 +26,17 @@ class TooManyRequestsHttpException extends HttpException */ public function __construct($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/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php index ddb48f116fb52..aeb9713a3ded6 100644 --- a/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php @@ -24,6 +24,17 @@ class UnauthorizedHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php b/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php index 237340a574d79..7b828b1d92ccb 100644 --- a/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php @@ -23,6 +23,12 @@ class UnprocessableEntityHttpException extends HttpException */ 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/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php b/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php index 74ddbfccbc9f3..7908423f42580 100644 --- a/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php @@ -23,6 +23,12 @@ class UnsupportedMediaTypeHttpException extends HttpException */ 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/src/Symfony/Component/Mailer/Exception/HttpTransportException.php b/src/Symfony/Component/Mailer/Exception/HttpTransportException.php index 3428b70e07eae..c72eb6cf6e3ee 100644 --- a/src/Symfony/Component/Mailer/Exception/HttpTransportException.php +++ b/src/Symfony/Component/Mailer/Exception/HttpTransportException.php @@ -22,6 +22,12 @@ class HttpTransportException extends TransportException public function __construct(?string $message, ResponseInterface $response, int $code = 0, \Throwable $previous = null) { + if (null === $message) { + trigger_deprecation('symfony/mailer', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + parent::__construct($message, $code, $previous); $this->response = $response; diff --git a/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php b/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php index 8a437140352cf..27cf2125e2b8e 100644 --- a/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php +++ b/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php @@ -27,6 +27,12 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn */ public function __construct(array $allowedMethods, ?string $message = '', int $code = 0, \Throwable $previous = null) { + if (null === $message) { + trigger_deprecation('symfony/routing', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + $this->allowedMethods = array_map('strtoupper', $allowedMethods); parent::__construct($message, $code, $previous); From 47c471e2c468e3d30e7df3daeca548563ad06c60 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 9 Feb 2021 12:13:47 +0100 Subject: [PATCH 173/188] [DependencyInjection] Add ContainerBuilder::willBeAvailable() to help with conditional configuration --- .../DependencyInjection/Configuration.php | 19 +-- .../DependencyInjection/Configuration.php | 120 ++++++++++-------- .../FrameworkExtension.php | 87 ++++++++----- .../DependencyInjection/ConfigurationTest.php | 3 +- .../DependencyInjection/SecurityExtension.php | 11 +- .../Bundle/SecurityBundle/composer.json | 2 +- .../Compiler/ExtensionPass.php | 14 +- .../DependencyInjection/TwigExtension.php | 9 +- src/Symfony/Bundle/TwigBundle/composer.json | 4 +- .../DependencyInjection/CHANGELOG.md | 1 + .../Compiler/AbstractRecursivePass.php | 2 +- .../DependencyInjection/ContainerBuilder.php | 29 +++++ 12 files changed, 180 insertions(+), 121 deletions(-) diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php index e3a724cd2448a..3ed12fc3f692d 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php @@ -13,7 +13,6 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; /** * DebugExtension configuration structure. @@ -51,21 +50,13 @@ public function getConfigTreeBuilder() ->example('php://stderr, or tcp://%env(VAR_DUMPER_SERVER)% when using the "server:dump" command') ->defaultNull() ->end() - ->end() - ; - - if (method_exists(HtmlDumper::class, 'setTheme')) { - $rootNode - ->children() - ->enumNode('theme') - ->info('Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light"') - ->example('dark') - ->values(['dark', 'light']) - ->defaultValue('dark') - ->end() + ->enumNode('theme') + ->info('Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light"') + ->example('dark') + ->values(['dark', 'light']) + ->defaultValue('dark') ->end() ; - } return $treeBuilder; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index a2bb4d8e2638e..ee44ac7d56a0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -21,6 +21,7 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; 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\HttpClient\HttpClient; @@ -30,6 +31,7 @@ use Symfony\Component\Mailer\Mailer; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Notifier\Notifier; +use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; use Symfony\Component\Serializer\Serializer; @@ -108,8 +110,19 @@ public function getConfigTreeBuilder() ->end() ; + $willBeAvailable = static function (string $package, string $class, string $parentPackage = null) { + $parentPackages = (array) $parentPackage; + $parentPackages[] = 'symfony/framework-bundle'; + + 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'; + }; + $this->addCsrfSection($rootNode); - $this->addFormSection($rootNode); + $this->addFormSection($rootNode, $enableIfStandalone); $this->addHttpCacheSection($rootNode); $this->addEsiSection($rootNode); $this->addSsiSection($rootNode); @@ -119,25 +132,25 @@ public function getConfigTreeBuilder() $this->addRouterSection($rootNode); $this->addSessionSection($rootNode); $this->addRequestSection($rootNode); - $this->addAssetsSection($rootNode); - $this->addTranslatorSection($rootNode); - $this->addValidationSection($rootNode); - $this->addAnnotationsSection($rootNode); - $this->addSerializerSection($rootNode); - $this->addPropertyAccessSection($rootNode); - $this->addPropertyInfoSection($rootNode); - $this->addCacheSection($rootNode); + $this->addAssetsSection($rootNode, $enableIfStandalone); + $this->addTranslatorSection($rootNode, $enableIfStandalone); + $this->addValidationSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addAnnotationsSection($rootNode, $willBeAvailable); + $this->addSerializerSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addPropertyAccessSection($rootNode, $willBeAvailable); + $this->addPropertyInfoSection($rootNode, $enableIfStandalone); + $this->addCacheSection($rootNode, $willBeAvailable); $this->addPhpErrorsSection($rootNode); - $this->addWebLinkSection($rootNode); - $this->addLockSection($rootNode); - $this->addMessengerSection($rootNode); + $this->addWebLinkSection($rootNode, $enableIfStandalone); + $this->addLockSection($rootNode, $enableIfStandalone); + $this->addMessengerSection($rootNode, $enableIfStandalone); $this->addRobotsIndexSection($rootNode); - $this->addHttpClientSection($rootNode); - $this->addMailerSection($rootNode); + $this->addHttpClientSection($rootNode, $enableIfStandalone); + $this->addMailerSection($rootNode, $enableIfStandalone); $this->addSecretsSection($rootNode); - $this->addNotifierSection($rootNode); - $this->addRateLimiterSection($rootNode); - $this->addUidSection($rootNode); + $this->addNotifierSection($rootNode, $enableIfStandalone); + $this->addRateLimiterSection($rootNode, $enableIfStandalone); + $this->addUidSection($rootNode, $enableIfStandalone); return $treeBuilder; } @@ -176,13 +189,13 @@ private function addCsrfSection(ArrayNodeDefinition $rootNode) ; } - private function addFormSection(ArrayNodeDefinition $rootNode) + private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('form') ->info('form configuration') - ->{!class_exists(FullStack::class) && class_exists(Form::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/form', Form::class)}() ->children() ->arrayNode('csrf_protection') ->treatFalseLike(['enabled' => false]) @@ -675,13 +688,13 @@ private function addRequestSection(ArrayNodeDefinition $rootNode) ; } - private function addAssetsSection(ArrayNodeDefinition $rootNode) + private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('assets') ->info('assets configuration') - ->{!class_exists(FullStack::class) && class_exists(Package::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/asset', Package::class)}() ->fixXmlConfig('base_url') ->children() ->scalarNode('version_strategy')->defaultNull()->end() @@ -763,13 +776,13 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) ; } - private function addTranslatorSection(ArrayNodeDefinition $rootNode) + private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('translator') ->info('translator configuration') - ->{!class_exists(FullStack::class) && class_exists(Translator::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/translation', Translator::class)}() ->fixXmlConfig('fallback') ->fixXmlConfig('path') ->fixXmlConfig('enabled_locale') @@ -816,16 +829,16 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ; } - private function addValidationSection(ArrayNodeDefinition $rootNode) + private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('validation') ->info('validation configuration') - ->{!class_exists(FullStack::class) && class_exists(Validation::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/validator', Validation::class)}() ->children() ->scalarNode('cache')->end() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() + ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/validator') ? 'defaultTrue' : 'defaultFalse'}()->end() ->arrayNode('static_method') ->defaultValue(['loadValidatorMetadata']) ->prototype('scalar')->end() @@ -906,15 +919,15 @@ private function addValidationSection(ArrayNodeDefinition $rootNode) ; } - private function addAnnotationsSection(ArrayNodeDefinition $rootNode) + private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('annotations') ->info('annotation configuration') - ->{class_exists(Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$willBeAvailable('doctrine/annotations', Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() - ->scalarNode('cache')->defaultValue(interface_exists(Cache::class) ? 'php_array' : 'none')->end() + ->scalarNode('cache')->defaultValue($willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotation') ? 'php_array' : 'none')->end() ->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end() ->booleanNode('debug')->defaultValue($this->debug)->end() ->end() @@ -923,15 +936,15 @@ private function addAnnotationsSection(ArrayNodeDefinition $rootNode) ; } - private function addSerializerSection(ArrayNodeDefinition $rootNode) + private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, $willBeAvailable) { $rootNode ->children() ->arrayNode('serializer') ->info('serializer configuration') - ->{!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/serializer', Serializer::class)}() ->children() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() + ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/serializer') ? 'defaultTrue' : 'defaultFalse'}()->end() ->scalarNode('name_converter')->end() ->scalarNode('circular_reference_handler')->end() ->scalarNode('max_depth_handler')->end() @@ -950,13 +963,14 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode) ; } - private function addPropertyAccessSection(ArrayNodeDefinition $rootNode) + private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('property_access') ->addDefaultsIfNotSet() ->info('Property access configuration') + ->{$willBeAvailable('symfony/property-access', PropertyAccessor::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() ->booleanNode('magic_call')->defaultFalse()->end() ->booleanNode('magic_get')->defaultTrue()->end() @@ -969,19 +983,19 @@ private function addPropertyAccessSection(ArrayNodeDefinition $rootNode) ; } - private function addPropertyInfoSection(ArrayNodeDefinition $rootNode) + private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('property_info') ->info('Property info configuration') - ->{!class_exists(FullStack::class) && interface_exists(PropertyInfoExtractorInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/property-info', PropertyInfoExtractorInterface::class)}() ->end() ->end() ; } - private function addCacheSection(ArrayNodeDefinition $rootNode) + private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() @@ -1008,7 +1022,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode) ->scalarNode('default_psr6_provider')->end() ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() - ->scalarNode('default_pdo_provider')->defaultValue(class_exists(Connection::class) ? 'database_connection' : null)->end() + ->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) ? 'database_connection' : null)->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') @@ -1117,13 +1131,13 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) ; } - private function addLockSection(ArrayNodeDefinition $rootNode) + private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('lock') ->info('Lock configuration') - ->{!class_exists(FullStack::class) && class_exists(Lock::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/lock', Lock::class)}() ->beforeNormalization() ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) ->end() @@ -1179,25 +1193,25 @@ private function addLockSection(ArrayNodeDefinition $rootNode) ; } - private function addWebLinkSection(ArrayNodeDefinition $rootNode) + private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('web_link') ->info('web links configuration') - ->{!class_exists(FullStack::class) && class_exists(HttpHeaderSerializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/weblink', HttpHeaderSerializer::class)}() ->end() ->end() ; } - private function addMessengerSection(ArrayNodeDefinition $rootNode) + private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('messenger') ->info('Messenger configuration') - ->{!class_exists(FullStack::class) && interface_exists(MessageBusInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/messenger', MessageBusInterface::class)}() ->fixXmlConfig('transport') ->fixXmlConfig('bus', 'buses') ->validate() @@ -1392,13 +1406,13 @@ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) ; } - private function addHttpClientSection(ArrayNodeDefinition $rootNode) + private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('http_client') ->info('HTTP Client configuration') - ->{!class_exists(FullStack::class) && class_exists(HttpClient::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/http-client', HttpClient::class)}() ->fixXmlConfig('scoped_client') ->beforeNormalization() ->always(function ($config) { @@ -1728,13 +1742,13 @@ private function addHttpClientRetrySection() ; } - private function addMailerSection(ArrayNodeDefinition $rootNode) + private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('mailer') ->info('Mailer configuration') - ->{!class_exists(FullStack::class) && class_exists(Mailer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/mailer', Mailer::class)}() ->validate() ->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); }) ->thenInvalid('"dsn" and "transports" cannot be used together.') @@ -1784,13 +1798,13 @@ private function addMailerSection(ArrayNodeDefinition $rootNode) ; } - private function addNotifierSection(ArrayNodeDefinition $rootNode) + private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('notifier') ->info('Notifier configuration') - ->{!class_exists(FullStack::class) && class_exists(Notifier::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/notifier', Notifier::class)}() ->fixXmlConfig('chatter_transport') ->children() ->arrayNode('chatter_transports') @@ -1833,13 +1847,13 @@ private function addNotifierSection(ArrayNodeDefinition $rootNode) ; } - private function addRateLimiterSection(ArrayNodeDefinition $rootNode) + private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('rate_limiter') ->info('Rate limiter configuration') - ->{!class_exists(FullStack::class) && class_exists(TokenBucketLimiter::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}() ->fixXmlConfig('limiter') ->beforeNormalization() ->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); }) @@ -1901,13 +1915,13 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode) ; } - private function addUidSection(ArrayNodeDefinition $rootNode) + private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('uid') ->info('Uid configuration') - ->{class_exists(UuidFactory::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/uid', UuidFactory::class)}() ->addDefaultsIfNotSet() ->children() ->enumNode('default_uuid_version') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 158e5291abe7b..57a1d057937f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -14,6 +14,7 @@ use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; use Http\Client\HttpClient; +use phpDocumentor\Reflection\DocBlockFactoryInterface; use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; @@ -63,6 +64,7 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Finder\Finder; use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\Form; use Symfony\Component\Form\FormTypeExtensionInterface; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeInterface; @@ -149,6 +151,7 @@ use Symfony\Component\RateLimiter\Storage\CacheStorage; use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Encoder\DecoderInterface; @@ -167,6 +170,7 @@ use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\ObjectInitializerInterface; +use Symfony\Component\Validator\Validation; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; use Symfony\Component\Workflow\WorkflowInterface; @@ -200,6 +204,7 @@ class FrameworkExtension extends Extension private $mailerConfigEnabled = false; private $httpClientConfigEnabled = false; private $notifierConfigEnabled = false; + private $propertyAccessConfigEnabled = false; private $lockConfigEnabled = false; /** @@ -216,7 +221,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('fragment_renderer.php'); $loader->load('error_renderer.php'); - if (interface_exists(PsrEventDispatcherInterface::class)) { + if (ContainerBuilder::willBeAvailable('psr/event-dispatcher', PsrEventDispatcherInterface::class, ['symfony/framework-bundle'])) { $container->setAlias(PsrEventDispatcherInterface::class, 'event_dispatcher'); } @@ -256,11 +261,11 @@ public function load(array $configs, ContainerBuilder $container) } // If the slugger is used but the String component is not available, we should throw an error - if (!interface_exists(SluggerInterface::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'])) { $container->register('slugger', 'stdClass') ->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".'); } else { - if (!interface_exists(LocaleAwareInterface::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'])) { $container->register('slugger', 'stdClass') ->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".'); } @@ -329,19 +334,19 @@ public function load(array $configs, ContainerBuilder $container) } if (null === $config['csrf_protection']['enabled']) { - $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class); + $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle']); } $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); if ($this->isConfigEnabled($container, $config['form'])) { - if (!class_exists(\Symfony\Component\Form\Form::class)) { + 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 (class_exists(\Symfony\Component\Validator\Validation::class)) { + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'])) { $config['validation']['enabled'] = true; } else { $container->setParameter('validator.translation_domain', 'validators'); @@ -469,7 +474,7 @@ public function load(array $configs, ContainerBuilder $container) 'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController', ]); - if (class_exists(MimeTypes::class)) { + if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'])) { $loader->load('mime_type.php'); } @@ -602,7 +607,7 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont $container->setParameter('form.type_extension.csrf.enabled', false); } - if (!class_exists(Translator::class)) { + 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')) { @@ -986,7 +991,7 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co $container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]); } - if (!class_exists(ExpressionLanguage::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'])) { $container->removeDefinition('router.expression_language_provider'); } @@ -1225,18 +1230,18 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $dirs = []; $transPaths = []; $nonExistingDirs = []; - if (class_exists(\Symfony\Component\Validator\Validation::class)) { - $r = new \ReflectionClass(\Symfony\Component\Validator\Validation::class); + 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 (class_exists(\Symfony\Component\Form\Form::class)) { - $r = new \ReflectionClass(\Symfony\Component\Form\Form::class); + 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 (class_exists(\Symfony\Component\Security\Core\Exception\AuthenticationException::class)) { - $r = new \ReflectionClass(\Symfony\Component\Security\Core\Exception\AuthenticationException::class); + 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'; } @@ -1336,7 +1341,7 @@ private function registerValidationConfiguration(array $config, ContainerBuilder return; } - if (!class_exists(\Symfony\Component\Validator\Validation::class)) { + if (!class_exists(Validation::class)) { throw new LogicException('Validation support cannot be enabled as the Validator component is not installed. Try running "composer require symfony/validator".'); } @@ -1403,8 +1408,8 @@ private function registerValidatorMapping(ContainerBuilder $container, array $co $files['yaml' === $extension ? 'yml' : $extension][] = $path; }; - if (interface_exists(\Symfony\Component\Form\FormInterface::class)) { - $reflClass = new \ReflectionClass(\Symfony\Component\Form\FormInterface::class); + 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'); } @@ -1466,7 +1471,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde } if (!class_exists(\Doctrine\Common\Annotations\Annotation::class)) { - throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed.'); + 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'); @@ -1478,7 +1483,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde if ('none' !== $config['cache']) { if (!class_exists(\Doctrine\Common\Cache\CacheProvider::class)) { - throw new LogicException('Annotations cannot be enabled as the Doctrine Cache library is not installed.'); + throw new LogicException('Annotations cannot be cached as the Doctrine Cache library is not installed. Try running "composer require doctrine/cache".'); } $cacheService = $config['cache']; @@ -1521,7 +1526,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!class_exists(PropertyAccessor::class)) { + if (!$this->propertyAccessConfigEnabled = $this->isConfigEnabled($container, $config)) { return; } @@ -1570,7 +1575,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var'])); } - if (class_exists(LazyString::class)) { + 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']})%"); @@ -1610,7 +1615,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); - if (!class_exists(PropertyAccessor::class)) { + if (!$this->propertyAccessConfigEnabled) { $container->removeAlias('serializer.property_accessor'); $container->removeDefinition('serializer.normalizer.object'); } @@ -1619,7 +1624,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.encoder.yaml'); } - if (!class_exists(UnwrappingDenormalizer::class) || !class_exists(PropertyAccessor::class)) { + if (!class_exists(UnwrappingDenormalizer::class) || !$this->propertyAccessConfigEnabled) { $container->removeDefinition('serializer.denormalizer.unwrapping'); } @@ -1703,7 +1708,7 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container, $loader->load('property_info.php'); - if (interface_exists(\phpDocumentor\Reflection\DocBlockFactoryInterface::class)) { + if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'])) { $definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor'); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); @@ -1784,19 +1789,19 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $loader->load('messenger.php'); - if (class_exists(AmqpTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory'); } - if (class_exists(RedisTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory'); } - if (class_exists(AmazonSqsTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory'); } - if (class_exists(BeanstalkdTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory'); } @@ -2070,12 +2075,12 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder unset($options['retry_failed']); $container->getDefinition('http_client')->setArguments([$options, $config['max_host_connections'] ?? 6]); - if (!$hasPsr18 = interface_exists(ClientInterface::class)) { + 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 (!interface_exists(HttpClient::class)) { + if (!ContainerBuilder::willBeAvailable('php-http/httplug', HttpClient::class, ['symfony/framework-bundle', 'symfony/http-client'])) { $container->removeDefinition(HttpClient::class); } @@ -2205,7 +2210,9 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co ]; foreach ($classToServices as $class => $service) { - if (!class_exists($class)) { + $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); } } @@ -2302,16 +2309,26 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', ]; + $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; + foreach ($classToServices as $class => $service) { - if (!class_exists($class)) { + switch ($package = substr($service, \strlen('notifier.transport_factory.'))) { + case 'freemobile': $package = 'free-mobile'; break; + case 'googlechat': $package = 'google-chat'; break; + case 'linkedin': $package = 'linked-in'; break; + case 'ovhcloud': $package = 'ovh-cloud'; break; + case 'rocketchat': $package = 'rocket-chat'; break; + } + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages)) { $container->removeDefinition($service); } } - if (class_exists(MercureTransportFactory::class) && class_exists(MercureBundle::class)) { + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) { $container->getDefinition($classToServices[MercureTransportFactory::class]) ->replaceArgument('$publisherLocator', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true))); - } elseif (class_exists(MercureTransportFactory::class)) { + } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { $container->removeDefinition($classToServices[MercureTransportFactory::class]); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index bab22e1d23769..20e8720eb396f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -443,6 +443,7 @@ protected static function getBundleDefaultConfig() 'mapping' => ['paths' => []], ], 'property_access' => [ + 'enabled' => true, 'magic_call' => false, 'magic_get' => true, 'magic_set' => true, @@ -566,7 +567,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'limiters' => [], ], 'uid' => [ - 'enabled' => class_exists(UuidFactory::class), + 'enabled' => !class_exists(FullStack::class) && class_exists(UuidFactory::class), 'default_uuid_version' => 6, 'name_based_uuid_version' => 5, 'time_based_uuid_version' => 6, diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 5c782a25bf87e..23b20c1aedc45 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection; +use Symfony\Bridge\Twig\Extension\LogoutUrlExtension; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; @@ -31,6 +32,7 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; @@ -42,7 +44,6 @@ use Symfony\Component\Security\Core\User\ChainUserProvider; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Event\CheckPassportEvent; -use Twig\Extension\AbstractExtension; /** * SecurityExtension. @@ -130,7 +131,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('security_legacy.php'); } - if (class_exists(AbstractExtension::class)) { + if ($container::willBeAvailable('symfony/twig-bridge', LogoutUrlExtension::class, ['symfony/security-bundle'])) { $loader->load('templating_twig.php'); } @@ -141,7 +142,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('security_debug.php'); } - if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { + if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'])) { $container->removeDefinition('security.expression_language'); $container->removeDefinition('security.access.expression_voter'); } @@ -982,8 +983,8 @@ private function createExpression(ContainerBuilder $container, string $expressio return $this->expressions[$id]; } - if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { - throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'])) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } $container diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 82b239960200e..05688dd016c2b 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-xml": "*", "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^5.2", + "symfony/dependency-injection": "^5.3", "symfony/deprecation-contracts": "^2.1", "symfony/event-dispatcher": "^5.1", "symfony/http-kernel": "^5.0", diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index eb6eecc95fac8..ae3f9df3befd0 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -11,10 +11,14 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; +use Symfony\Component\Asset\Packages; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Workflow\Workflow; +use Symfony\Component\Yaml\Yaml; /** * @author Jean-François Simon @@ -23,19 +27,19 @@ class ExtensionPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!class_exists(\Symfony\Component\Asset\Packages::class)) { + if (!$container::willBeAvailable('symfony/asset', Packages::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.assets'); } - if (!class_exists(\Symfony\Component\ExpressionLanguage\Expression::class)) { + if (!$container::willBeAvailable('symfony/expression-language', Expression::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.expression'); } - if (!interface_exists(\Symfony\Component\Routing\Generator\UrlGeneratorInterface::class)) { + if (!$container::willBeAvailable('symfony/routing', UrlGeneratorInterface::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.routing'); } - if (!class_exists(\Symfony\Component\Yaml\Yaml::class)) { + if (!$container::willBeAvailable('symfony/yaml', Yaml::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.yaml'); } @@ -111,7 +115,7 @@ public function process(ContainerBuilder $container) $container->getDefinition('twig.extension.expression')->addTag('twig.extension'); } - if (!class_exists(Workflow::class) || !$container->has('workflow.registry')) { + if (!$container::willBeAvailable('symfony/workflow', Workflow::class, ['symfony/twig-bundle']) || !$container->has('workflow.registry')) { $container->removeDefinition('workflow.twig_extension'); } else { $container->getDefinition('workflow.twig_extension')->addTag('twig.extension'); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 5ccc9a1a04c3a..20095eb45a79d 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\Form; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Translation\Translator; @@ -37,19 +38,19 @@ public function load(array $configs, ContainerBuilder $container) $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('twig.php'); - if (class_exists(\Symfony\Component\Form\Form::class)) { + if ($container::willBeAvailable('symfony/form', Form::class, ['symfony/twig-bundle'])) { $loader->load('form.php'); } - if (class_exists(Application::class)) { + if ($container::willBeAvailable('symfony/console', Application::class, ['symfony/twig-bundle'])) { $loader->load('console.php'); } - if (class_exists(Mailer::class)) { + if ($container::willBeAvailable('symfony/mailer', Mailer::class, ['symfony/twig-bundle'])) { $loader->load('mailer.php'); } - if (!class_exists(Translator::class)) { + if (!$container::willBeAvailable('symfony/translation', Translator::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.translation.extractor'); } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 7c5c15fbce4d5..6bc5bf61794bd 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -27,7 +27,7 @@ "require-dev": { "symfony/asset": "^4.4|^5.0", "symfony/stopwatch": "^4.4|^5.0", - "symfony/dependency-injection": "^5.2", + "symfony/dependency-injection": "^5.3", "symfony/expression-language": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", "symfony/form": "^4.4|^5.0", @@ -40,7 +40,7 @@ "doctrine/cache": "~1.0" }, "conflict": { - "symfony/dependency-injection": "<5.2", + "symfony/dependency-injection": "<5.3", "symfony/framework-bundle": "<5.0", "symfony/translation": "<5.0" }, diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index c08e7cb75df25..1e45c669e89e4 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 * Add autoconfigurable attributes * Add support for per-env configuration in loaders + * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index 85478da5e9b16..4881e4fe10320 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -198,7 +198,7 @@ private function getExpressionLanguage(): ExpressionLanguage { if (null === $this->expressionLanguage) { if (!class_exists(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(); diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index a3be650343152..6c266bae0a9b8 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -11,6 +11,7 @@ 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; @@ -1467,6 +1468,34 @@ public function log(CompilerPassInterface $pass, string $message) $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message)); } + /** + * Checks whether a class is available and will remain available in the "no-dev" mode of Composer. + * + * 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. + */ + final public static function willBeAvailable(string $package, string $class, array $parentPackages): bool + { + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + return false; + } + + if (!class_exists(InstalledVersions::class) || !InstalledVersions::isInstalled($package) || InstalledVersions::isInstalled($package, false)) { + return true; + } + + // the package is installed but in dev-mode only, check if this applies to one of the parent packages too + + $rootPackage = InstalledVersions::getRootPackage()['name'] ?? ''; + foreach ($parentPackages as $parentPackage) { + if ($rootPackage === $parentPackage || (InstalledVersions::isInstalled($parentPackage) && !InstalledVersions::isInstalled($parentPackage, false))) { + return true; + } + } + + return false; + } + /** * Gets removed binding ids. * From 223421b6cac94a4efc11015e9e7eabc8a852e370 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Mon, 18 Jan 2021 18:42:10 +0100 Subject: [PATCH 174/188] [Uid] Add Generate and Inspect commands Co-authored-by: Nicolas Grekas --- .../Serializer/Normalizer/UidNormalizer.php | 12 +- .../Tests/Normalizer/UidNormalizerTest.php | 4 +- src/Symfony/Component/Uid/CHANGELOG.md | 1 + .../Uid/Command/GenerateUlidCommand.php | 109 +++++++++ .../Uid/Command/GenerateUuidCommand.php | 200 ++++++++++++++++ .../Uid/Command/InspectUlidCommand.php | 74 ++++++ .../Uid/Command/InspectUuidCommand.php | 89 ++++++++ .../Tests/Command/GenerateUlidCommandTest.php | 92 ++++++++ .../Tests/Command/GenerateUuidCommandTest.php | 212 +++++++++++++++++ .../Tests/Command/InspectUlidCommandTest.php | 49 ++++ .../Tests/Command/InspectUuidCommandTest.php | 216 ++++++++++++++++++ src/Symfony/Component/Uid/composer.json | 3 + 12 files changed, 1053 insertions(+), 8 deletions(-) create mode 100644 src/Symfony/Component/Uid/Command/GenerateUlidCommand.php create mode 100644 src/Symfony/Component/Uid/Command/GenerateUuidCommand.php create mode 100644 src/Symfony/Component/Uid/Command/InspectUlidCommand.php create mode 100644 src/Symfony/Component/Uid/Command/InspectUuidCommand.php create mode 100644 src/Symfony/Component/Uid/Tests/Command/GenerateUlidCommandTest.php create mode 100644 src/Symfony/Component/Uid/Tests/Command/GenerateUuidCommandTest.php create mode 100644 src/Symfony/Component/Uid/Tests/Command/InspectUlidCommandTest.php create mode 100644 src/Symfony/Component/Uid/Tests/Command/InspectUuidCommandTest.php diff --git a/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php index a7152613cbd93..009d334895ee8 100644 --- a/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php @@ -22,9 +22,9 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, public const NORMALIZATION_FORMAT_KEY = 'uid_normalization_format'; public const NORMALIZATION_FORMAT_CANONICAL = 'canonical'; - public const NORMALIZATION_FORMAT_BASE_58 = 'base_58'; - public const NORMALIZATION_FORMAT_BASE_32 = 'base_32'; - public const NORMALIZATION_FORMAT_RFC_4122 = 'rfc_4122'; + public const NORMALIZATION_FORMAT_BASE58 = 'base58'; + public const NORMALIZATION_FORMAT_BASE32 = 'base32'; + public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; private $defaultContext = [ self::NORMALIZATION_FORMAT_KEY => self::NORMALIZATION_FORMAT_CANONICAL, @@ -45,11 +45,11 @@ public function normalize($object, string $format = null, array $context = []) switch ($context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY]) { case self::NORMALIZATION_FORMAT_CANONICAL: return (string) $object; - case self::NORMALIZATION_FORMAT_BASE_58: + case self::NORMALIZATION_FORMAT_BASE58: return $object->toBase58(); - case self::NORMALIZATION_FORMAT_BASE_32: + case self::NORMALIZATION_FORMAT_BASE32: return $object->toBase32(); - case self::NORMALIZATION_FORMAT_RFC_4122: + case self::NORMALIZATION_FORMAT_RFC4122: return $object->toRfc4122(); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php index f1b766ea4c87c..e2e68832a92bf 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php @@ -39,7 +39,7 @@ public function testSupportsNormalization() public function normalizeProvider() { - $uidFormats = [null, 'canonical', 'base_58', 'base_32', 'rfc_4122']; + $uidFormats = [null, 'canonical', 'base58', 'base32', 'rfc4122']; $data = [ [ UuidV1::fromString('9b7541de-6f87-11ea-ab3c-9da9a81562fc'), @@ -149,7 +149,7 @@ public function testDenormalize($uuidString, $class) public function testNormalizeWithNormalizationFormatPassedInConstructor() { $uidNormalizer = new UidNormalizer([ - 'uid_normalization_format' => 'rfc_4122', + 'uid_normalization_format' => 'rfc4122', ]); $ulid = Ulid::fromString('01ETWV01C0GYQ5N92ZK7QRGB10'); diff --git a/src/Symfony/Component/Uid/CHANGELOG.md b/src/Symfony/Component/Uid/CHANGELOG.md index 1acafc0e32770..03e3e9cbdf0ca 100644 --- a/src/Symfony/Component/Uid/CHANGELOG.md +++ b/src/Symfony/Component/Uid/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * [BC BREAK] Replace `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()` * Add `Uuid::NAMESPACE_*` constants from RFC4122 * Add `UlidFactory`, `UuidFactory`, `RandomBasedUuidFactory`, `TimeBasedUuidFactory` and `NameBasedUuidFactory` + * Add commands to generate and inspect UUIDs and ULIDs 5.2.0 ----- diff --git a/src/Symfony/Component/Uid/Command/GenerateUlidCommand.php b/src/Symfony/Component/Uid/Command/GenerateUlidCommand.php new file mode 100644 index 0000000000000..7eb1b76abf23e --- /dev/null +++ b/src/Symfony/Component/Uid/Command/GenerateUlidCommand.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\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Factory\UlidFactory; + +class GenerateUlidCommand extends Command +{ + protected static $defaultName = 'ulid:generate'; + protected static $defaultDescription = 'Generates a ULID'; + + private $factory; + + public function __construct(UlidFactory $factory = null) + { + $this->factory = $factory ?? new UlidFactory(); + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('time', null, InputOption::VALUE_REQUIRED, 'The ULID timestamp: a parsable date/time string'), + new InputOption('count', 'c', InputOption::VALUE_REQUIRED, 'The number of ULID to generate', 1), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'The ULID output format: base32, base58 or rfc4122', 'base32'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% command generates a ULID. + + php %command.full_name% + +To specify the timestamp: + + php %command.full_name% --time="2021-02-16 14:09:08" + +To generate several ULIDs: + + php %command.full_name% --count=10 + +To output a specific format: + + php %command.full_name% --format=rfc4122 +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + if (null !== $time = $input->getOption('time')) { + try { + $time = new \DateTimeImmutable($time); + } catch (\Exception $e) { + $io->error(sprintf('Invalid timestamp "%s": %s', $time, str_replace('DateTimeImmutable::__construct(): ', '', $e->getMessage()))); + + return 1; + } + } + + switch ($input->getOption('format')) { + case 'base32': $format = 'toBase32'; break; + case 'base58': $format = 'toBase58'; break; + case 'rfc4122': $format = 'toRfc4122'; break; + default: + $io->error(sprintf('Invalid format "%s", did you mean "base32", "base58" or "rfc4122"?', $input->getOption('format'))); + + return 1; + } + + $count = (int) $input->getOption('count'); + try { + for ($i = 0; $i < $count; ++$i) { + $output->writeln($this->factory->create($time)->$format()); + } + } catch (\Exception $e) { + $io->error($e->getMessage()); + + return 1; + } + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Command/GenerateUuidCommand.php b/src/Symfony/Component/Uid/Command/GenerateUuidCommand.php new file mode 100644 index 0000000000000..0e602b08afed5 --- /dev/null +++ b/src/Symfony/Component/Uid/Command/GenerateUuidCommand.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\Uuid; + +class GenerateUuidCommand extends Command +{ + protected static $defaultName = 'uuid:generate'; + protected static $defaultDescription = 'Generates a UUID'; + + private $factory; + + public function __construct(UuidFactory $factory = null) + { + $this->factory = $factory ?? new UuidFactory(); + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('time-based', null, InputOption::VALUE_REQUIRED, 'The timestamp, to generate a time-based UUID: a parsable date/time string'), + new InputOption('node', null, InputOption::VALUE_REQUIRED, 'The UUID whose node part should be used as the node of the generated UUID'), + new InputOption('name-based', null, InputOption::VALUE_REQUIRED, 'The name, to generate a name-based UUID'), + new InputOption('namespace', null, InputOption::VALUE_REQUIRED, 'The UUID to use at the namespace for named-based UUIDs'), + new InputOption('random-based', null, InputOption::VALUE_NONE, 'To generate a random-based UUID'), + new InputOption('count', 'c', InputOption::VALUE_REQUIRED, 'The number of UUID to generate', 1), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'The UUID output format: rfc4122, base58 or base32', 'rfc4122'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% generates a UUID. + + php %command.full_name% + +To generate a time-based UUID: + + php %command.full_name% --time-based=now + +To specify a time-based UUID's node: + + php %command.full_name% --time-based=@1613480254 --node=fb3502dc-137e-4849-8886-ac90d07f64a7 + +To generate a name-based UUID: + + php %command.full_name% --name-based=foo + +To specify a name-based UUID's namespace: + + php %command.full_name% --name-based=bar --namespace=fb3502dc-137e-4849-8886-ac90d07f64a7 + +To generate a random-based UUID: + + php %command.full_name% --random-based + +To generate several UUIDs: + + php %command.full_name% --count=10 + +To output a specific format: + + php %command.full_name% --format=base58 +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $time = $input->getOption('time-based'); + $node = $input->getOption('node'); + $name = $input->getOption('name-based'); + $namespace = $input->getOption('namespace'); + $random = $input->getOption('random-based'); + + if (false !== ($time ?? $name ?? $random) && 1 < ((null !== $time) + (null !== $name) + $random)) { + $io->error('Only one of "--time-based", "--name-based" or "--random-based" can be provided at a time.'); + + return 1; + } + + if (null === $time && null !== $node) { + $io->error('Option "--node" can only be used with "--time-based".'); + + return 1; + } + + if (null === $name && null !== $namespace) { + $io->error('Option "--namespace" can only be used with "--name-based".'); + + return 1; + } + + switch (true) { + case null !== $time: + if (null !== $node) { + try { + $node = Uuid::fromString($node); + } catch (\InvalidArgumentException $e) { + $io->error(sprintf('Invalid node "%s": %s', $node, $e->getMessage())); + + return 1; + } + } + + try { + $time = new \DateTimeImmutable($time); + } catch (\Exception $e) { + $io->error(sprintf('Invalid timestamp "%s": %s', $time, str_replace('DateTimeImmutable::__construct(): ', '', $e->getMessage()))); + + return 1; + } + + $create = function () use ($node, $time): Uuid { + return $this->factory->timeBased($node)->create($time); + }; + break; + + case null !== $name: + if ($namespace) { + try { + $namespace = Uuid::fromString($namespace); + } catch (\InvalidArgumentException $e) { + $io->error(sprintf('Invalid namespace "%s": %s', $namespace, $e->getMessage())); + + return 1; + } + } + + $create = function () use ($namespace, $name): Uuid { + try { + $factory = $this->factory->nameBased($namespace); + } catch (\LogicException $e) { + throw new \InvalidArgumentException('Missing namespace: use the "--namespace" option or configure a default namespace in the underlying factory.'); + } + + return $factory->create($name); + }; + break; + + case $random: + $create = [$this->factory->randomBased(), 'create']; + break; + + default: + $create = [$this->factory, 'create']; + break; + } + + switch ($input->getOption('format')) { + case 'base32': $format = 'toBase32'; break; + case 'base58': $format = 'toBase58'; break; + case 'rfc4122': $format = 'toRfc4122'; break; + default: + $io->error(sprintf('Invalid format "%s", did you mean "base32", "base58" or "rfc4122"?', $input->getOption('format'))); + + return 1; + } + + $count = (int) $input->getOption('count'); + try { + for ($i = 0; $i < $count; ++$i) { + $output->writeln($create()->$format()); + } + } catch (\Exception $e) { + $io->error($e->getMessage()); + + return 1; + } + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Command/InspectUlidCommand.php b/src/Symfony/Component/Uid/Command/InspectUlidCommand.php new file mode 100644 index 0000000000000..ba6c45c9b8475 --- /dev/null +++ b/src/Symfony/Component/Uid/Command/InspectUlidCommand.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Ulid; + +class InspectUlidCommand extends Command +{ + protected static $defaultName = 'ulid:inspect'; + protected static $defaultDescription = 'Inspects a ULID'; + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('ulid', InputArgument::REQUIRED, 'The ULID to inspect'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% displays information about a ULID. + + php %command.full_name% 01EWAKBCMWQ2C94EXNN60ZBS0Q + php %command.full_name% 1BVdfLn3ERmbjYBLCdaaLW + php %command.full_name% 01771535-b29c-b898-923b-b5a981f5e417 +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + try { + $ulid = Ulid::fromString($input->getArgument('ulid')); + } catch (\InvalidArgumentException $e) { + $io->error($e->getMessage()); + + return 1; + } + + $io->table(['Label', 'Value'], [ + ['Canonical (Base 32)', (string) $ulid], + ['Base 58', $ulid->toBase58()], + ['RFC 4122', $ulid->toRfc4122()], + new TableSeparator(), + ['Timestamp', ($ulid->getDateTime())->format('Y-m-d H:i:s.v')], + ]); + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Command/InspectUuidCommand.php b/src/Symfony/Component/Uid/Command/InspectUuidCommand.php new file mode 100644 index 0000000000000..6b6bbf3ed3bcc --- /dev/null +++ b/src/Symfony/Component/Uid/Command/InspectUuidCommand.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV1; +use Symfony\Component\Uid\UuidV6; + +class InspectUuidCommand extends Command +{ + protected static $defaultName = 'uuid:inspect'; + protected static $defaultDescription = 'Inspects a UUID'; + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('uuid', InputArgument::REQUIRED, 'The UUID to inspect'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% displays information about a UUID. + + php %command.full_name% a7613e0a-5986-11eb-a861-2bf05af69e52 + php %command.full_name% MfnmaUvvQ1h8B14vTwt6dX + php %command.full_name% 57C4Z0MPC627NTGR9BY1DFD7JJ +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + try { + /** @var Uuid $uuid */ + $uuid = Uuid::fromString($input->getArgument('uuid')); + } catch (\InvalidArgumentException $e) { + $io->error($e->getMessage()); + + return 1; + } + + if (-1 === $version = uuid_type($uuid)) { + $version = 'nil'; + } elseif (0 === $version || 2 === $version || 6 < $version) { + $version = 'unknown'; + } + + $rows = [ + ['Version', $version], + ['Canonical (RFC 4122)', (string) $uuid], + ['Base 58', $uuid->toBase58()], + ['Base 32', $uuid->toBase32()], + ]; + + if ($uuid instanceof UuidV1 || $uuid instanceof UuidV6) { + $rows[] = new TableSeparator(); + $rows[] = ['Timestamp', $uuid->getDateTime()->format('Y-m-d H:i:s.u')]; + } + + $io->table(['Label', 'Value'], $rows); + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/GenerateUlidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/GenerateUlidCommandTest.php new file mode 100644 index 0000000000000..e41e6fc15c94a --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/GenerateUlidCommandTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\GenerateUlidCommand; +use Symfony\Component\Uid\Ulid; + +final class GenerateUlidCommandTest extends TestCase +{ + /** + * @group time-sensitive + */ + public function testDefaults() + { + $time = microtime(false); + $time = substr($time, 11).substr($time, 1, 4); + + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute([])); + + $ulid = Ulid::fromBase32(trim($commandTester->getDisplay())); + $this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', $time), $ulid->getDateTime()); + } + + public function testUnparsableTimestamp() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time' => 'foo'])); + $this->assertStringContainsString('Invalid timestamp "foo"', $commandTester->getDisplay()); + } + + public function testTimestampBeforeUnixEpoch() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time' => '@-42'])); + $this->assertStringContainsString('The timestamp must be positive', $commandTester->getDisplay()); + } + + public function testTimestamp() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute(['--time' => '2021-02-16 18:09:42.999'])); + + $ulid = Ulid::fromBase32(trim($commandTester->getDisplay())); + $this->assertEquals(new \DateTimeImmutable('2021-02-16 18:09:42.999'), $ulid->getDateTime()); + } + + public function testCount() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute(['--count' => '10'])); + + $ulids = explode("\n", trim($commandTester->getDisplay(true))); + $this->assertCount(10, $ulids); + foreach ($ulids as $ulid) { + $this->assertTrue(Ulid::isValid($ulid)); + } + } + + public function testInvalidFormat() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['--format' => 'foo'])); + $this->assertStringContainsString('Invalid format "foo"', $commandTester->getDisplay()); + } + + public function testFormat() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute(['--format' => 'rfc4122'])); + + Ulid::fromRfc4122(trim($commandTester->getDisplay())); + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/GenerateUuidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/GenerateUuidCommandTest.php new file mode 100644 index 0000000000000..27e829fc2b9de --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/GenerateUuidCommandTest.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\GenerateUuidCommand; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV1; +use Symfony\Component\Uid\UuidV3; +use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Uid\UuidV5; +use Symfony\Component\Uid\UuidV6; + +final class GenerateUuidCommandTest extends TestCase +{ + public function testDefaults() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute([])); + $this->assertInstanceOf(UuidV6::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + + $commandTester = new CommandTester(new GenerateUuidCommand(new UuidFactory(UuidV4::class))); + $this->assertSame(0, $commandTester->execute([])); + $this->assertInstanceOf(UuidV4::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + } + + public function testTimeBasedWithInvalidNode() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time-based' => 'now', '--node' => 'foo'])); + $this->assertStringContainsString('Invalid node "foo"', $commandTester->getDisplay()); + } + + public function testTimeBasedWithUnparsableTimestamp() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time-based' => 'foo'])); + $this->assertStringContainsString('Invalid timestamp "foo"', $commandTester->getDisplay()); + } + + public function testTimeBasedWithTimestampBeforeUUIDEpoch() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time-based' => '@-16807797990'])); + $this->assertStringContainsString('The given UUID date cannot be earlier than 1582-10-15.', $commandTester->getDisplay()); + } + + public function testTimeBased() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute(['--time-based' => 'now'])); + $this->assertInstanceOf(UuidV6::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + + $commandTester = new CommandTester(new GenerateUuidCommand(new UuidFactory( + UuidV6::class, + UuidV1::class, + UuidV5::class, + UuidV4::class, + 'b2ba9fa1-d84a-4d49-bb0a-691421b27a00' + ))); + $this->assertSame(0, $commandTester->execute(['--time-based' => '2000-01-02 19:09:17.871524 +00:00'])); + $uuid = Uuid::fromRfc4122(trim($commandTester->getDisplay())); + $this->assertInstanceOf(UuidV1::class, $uuid); + $this->assertStringMatchesFormat('1c31e868-c148-11d3-%s-691421b27a00', (string) $uuid); + } + + public function testNameBasedWithInvalidNamespace() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--name-based' => 'foo', '--namespace' => 'bar'])); + $this->assertStringContainsString('Invalid namespace "bar"', $commandTester->getDisplay()); + } + + public function testNameBasedWithoutNamespace() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--name-based' => 'foo'])); + $this->assertStringContainsString('Missing namespace', $commandTester->getDisplay()); + } + + public function testNameBased() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute(['--name-based' => 'foo', '--namespace' => 'bcdf2a0e-e287-4d20-a92f-103eda39b100'])); + $this->assertInstanceOf(UuidV5::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + + $commandTester = new CommandTester(new GenerateUuidCommand(new UuidFactory( + UuidV6::class, + UuidV1::class, + UuidV3::class, + UuidV4::class, + null, + '6fc5292a-5f9f-4ada-94a4-c4063494d657' + ))); + $this->assertSame(0, $commandTester->execute(['--name-based' => 'bar'])); + $this->assertEquals(new UuidV3('54950ff1-375c-33e8-a992-2109e384091f'), Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + } + + public function testRandomBased() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute(['--random-based' => null])); + $this->assertInstanceOf(UuidV4::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + } + + /** + * @dataProvider provideInvalidCombinationOfBasedOptions + */ + public function testInvalidCombinationOfBasedOptions(array $input) + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute($input)); + $this->assertStringContainsString('Only one of "--time-based", "--name-based" or "--random-based"', $commandTester->getDisplay()); + } + + public function provideInvalidCombinationOfBasedOptions() + { + return [ + [['--time-based' => 'now', '--name-based' => 'foo']], + [['--time-based' => 'now', '--random-based' => null]], + [['--name-based' => 'now', '--random-based' => null]], + [['--time-based' => 'now', '--name-based' => 'now', '--random-based' => null]], + ]; + } + + /** + * @dataProvider provideExtraNodeOption + */ + public function testExtraNodeOption(array $input) + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute($input)); + $this->assertStringContainsString('Option "--node" can only be used with "--time-based"', $commandTester->getDisplay()); + } + + public function provideExtraNodeOption() + { + return [ + [['--node' => 'foo']], + [['--name-based' => 'now', '--node' => 'foo']], + [['--random-based' => null, '--node' => 'foo']], + ]; + } + + /** + * @dataProvider provideExtraNamespaceOption + */ + public function testExtraNamespaceOption(array $input) + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute($input)); + $this->assertStringContainsString('Option "--namespace" can only be used with "--name-based"', $commandTester->getDisplay()); + } + + public function provideExtraNamespaceOption() + { + return [ + [['--namespace' => 'foo']], + [['--time-based' => 'now', '--namespace' => 'foo']], + [['--random-based' => null, '--namespace' => 'foo']], + ]; + } + + public function testCount() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['--count' => '10'])); + + $uuids = explode("\n", trim($commandTester->getDisplay(true))); + $this->assertCount(10, $uuids); + foreach ($uuids as $uuid) { + $this->assertTrue(Uuid::isValid($uuid)); + } + } + + public function testInvalidFormat() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--format' => 'foo'])); + $this->assertStringContainsString('Invalid format "foo"', $commandTester->getDisplay()); + } + + public function testFormat() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['--format' => 'base32'])); + + Uuid::fromBase32(trim($commandTester->getDisplay())); + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/InspectUlidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/InspectUlidCommandTest.php new file mode 100644 index 0000000000000..7bd48bc9ab931 --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/InspectUlidCommandTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\InspectUlidCommand; + +final class InspectUlidCommandTest extends TestCase +{ + public function test() + { + $commandTester = new CommandTester(new InspectUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['ulid' => 'foobar'])); + $this->assertStringContainsString('Invalid ULID: "foobar"', $commandTester->getDisplay()); + + foreach ([ + '01E439TP9XJZ9RPFH3T1PYBCR8', + '1BKocMc5BnrVcuq2ti4Eqm', + '0171069d-593d-97d3-8b3e-23d06de5b308', + ] as $ulid) { + $this->assertSame(0, $commandTester->execute(['ulid' => $ulid])); + $this->assertSame(<<getDisplay(true)); + } + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/InspectUuidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/InspectUuidCommandTest.php new file mode 100644 index 0000000000000..9505b3bcd463a --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/InspectUuidCommandTest.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\InspectUuidCommand; + +final class InspectUuidCommandTest extends TestCase +{ + public function testInvalid() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['uuid' => 'foobar'])); + $this->assertStringContainsString('Invalid UUID: "foobar"', $commandTester->getDisplay()); + } + + public function testNil() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '00000000-0000-0000-0000-000000000000'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testUnknown() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-0dba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-2dba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-7dba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-cdba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV1() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '4c8e3a2a-5993-11eb-a861-2bf05af69e52'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV3() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => 'd108a1a0-957e-3c77-b110-d3f912374439'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV4() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '705c6eab-a535-4f49-bd51-436d0e81206a'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV5() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '4ec6c3ad-de94-5f75-b5f0-ad56661a30c4'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV6() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '1eb59937-b0a7-6288-a861-db3dc2d8d4db'])); + $this->assertSame(<<getDisplay(true)); + } +} diff --git a/src/Symfony/Component/Uid/composer.json b/src/Symfony/Component/Uid/composer.json index 369a3081f4e75..0eae40ea68cbb 100644 --- a/src/Symfony/Component/Uid/composer.json +++ b/src/Symfony/Component/Uid/composer.json @@ -23,6 +23,9 @@ "php": ">=7.2.5", "symfony/polyfill-uuid": "^1.15" }, + "require-dev": { + "symfony/console": "^4.4|^5.0" + }, "autoload": { "psr-4": { "Symfony\\Component\\Uid\\": "" }, "exclude-from-classmap": [ From f90d3ec2035c81036366e287cc397d85f7881fa9 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 24 Feb 2021 19:25:28 +0100 Subject: [PATCH 175/188] [Form] Remove hard dependency on symfony/intl --- UPGRADE-5.3.md | 1 + src/Symfony/Component/Form/CHANGELOG.md | 1 + .../Component/Form/Extension/Core/Type/CountryType.php | 6 ++++++ .../Component/Form/Extension/Core/Type/CurrencyType.php | 6 ++++++ .../Component/Form/Extension/Core/Type/LanguageType.php | 4 ++++ .../Component/Form/Extension/Core/Type/LocaleType.php | 6 ++++++ .../Component/Form/Extension/Core/Type/TimezoneType.php | 5 +++++ src/Symfony/Component/Form/composer.json | 2 +- 8 files changed, 30 insertions(+), 1 deletion(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 83435825d6d6c..480c13f22b693 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -27,6 +27,7 @@ Form * Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead + * Dependency on `symfony/intl` was removed. Install `symfony/intl` if you are using `LocaleType`, `CountryType`, `CurrencyType`, `LanguageType` or `TimezoneType` FrameworkBundle --------------- diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 7e435063d60d1..2ab805eb990b1 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -14,6 +14,7 @@ CHANGELOG * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. * Added a `choice_translation_parameters` option to `ChoiceType` * Add `UuidType` and `UlidType` + * Dependency on `symfony/intl` was removed. Install `symfony/intl` if you are using `LocaleType`, `CountryType`, `CurrencyType`, `LanguageType` or `TimezoneType`. 5.2.0 ----- diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index e0b1976864326..85293bc284c18 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -14,7 +14,9 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ChoiceList; use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Intl\Countries; +use Symfony\Component\Intl\Intl; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -27,6 +29,10 @@ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } + $choiceTranslationLocale = $options['choice_translation_locale']; $alpha3 = $options['alpha3']; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index 7b6f69f48b221..427b493f7e046 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -14,7 +14,9 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ChoiceList; use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Intl\Currencies; +use Symfony\Component\Intl\Intl; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -27,6 +29,10 @@ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } + $choiceTranslationLocale = $options['choice_translation_locale']; return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php index 23e5e50319e79..7bcbda2077a42 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Languages; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -29,6 +30,9 @@ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } $choiceTranslationLocale = $options['choice_translation_locale']; $useAlpha3Codes = $options['alpha3']; $choiceSelfTranslation = $options['choice_self_translation']; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index 461640deeb354..14113e4ac1101 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -14,6 +14,8 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ChoiceList; use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Locales; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -27,6 +29,10 @@ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } + $choiceTranslationLocale = $options['choice_translation_locale']; return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 9829cba2cd7c2..31b5df5c3c9c9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -18,6 +18,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Timezones; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -47,6 +48,10 @@ public function configureOptions(OptionsResolver $resolver) $input = $options['input']; if ($options['intl']) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s" with option "intl=true". Try running "composer require symfony/intl".', static::class)); + } + $choiceTranslationLocale = $options['choice_translation_locale']; return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($input, $choiceTranslationLocale) { diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index b9800490e917e..b99e08620a8d0 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -19,7 +19,6 @@ "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/intl": "^4.4|^5.0", "symfony/options-resolver": "^5.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "^1.21", @@ -37,6 +36,7 @@ "symfony/console": "^4.4|^5.0", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", + "symfony/intl": "^4.4|^5.0", "symfony/security-csrf": "^4.4|^5.0", "symfony/translation": "^4.4|^5.0", "symfony/var-dumper": "^4.4|^5.0", From 45be875e847e7180db8681ff876a9fdc783a0d00 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Tue, 23 Feb 2021 22:14:20 +0100 Subject: [PATCH 176/188] [Security][RateLimiter] Allow to use no lock in the rate limiter/login throttling --- UPGRADE-5.3.md | 2 + .../DependencyInjection/Configuration.php | 2 +- .../FrameworkExtension.php | 16 ++++--- .../Resources/config/rate_limiter.php | 1 + .../PhpFrameworkExtensionTest.php | 47 +++++++++++++++++++ .../Bundle/FrameworkBundle/composer.json | 1 + .../Bundle/SecurityBundle/CHANGELOG.md | 1 + .../Factory/LoginThrottlingFactory.php | 2 + 8 files changed, 64 insertions(+), 8 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index fcf81d72ad38a..057e5b3f600ae 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -83,6 +83,8 @@ Security SecurityBundle -------------- + * [BC break] Add `login_throttling.lock_factory` setting defaulting to `null`. Set this option + to `lock.factory` if you need precise login rate limiting with synchronous requests. * Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command, use `UserPasswordHashCommand` and `user:hash-password` instead * Deprecate the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases, diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ee44ac7d56a0e..565b9d3960d6e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1874,7 +1874,7 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->arrayPrototype() ->children() ->scalarNode('lock_factory') - ->info('The service ID of the lock factory used by this limiter') + ->info('The service ID of the lock factory used by this limiter (or null to disable locking)') ->defaultValue('lock.factory') ->end() ->scalarNode('cache_pool') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 57a1d057937f2..07c033736771f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -205,7 +205,7 @@ class FrameworkExtension extends Extension private $httpClientConfigEnabled = false; private $notifierConfigEnabled = false; private $propertyAccessConfigEnabled = false; - private $lockConfigEnabled = false; + private static $lockConfigEnabled = false; /** * Responds to the app.config configuration parameter. @@ -438,7 +438,7 @@ public function load(array $configs, ContainerBuilder $container) $this->registerPropertyInfoConfiguration($container, $loader); } - if ($this->lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) { + if (self::$lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) { $this->registerLockConfiguration($config['lock'], $container, $loader); } @@ -2344,10 +2344,6 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->lockConfigEnabled) { - throw new LogicException('Rate limiter support cannot be enabled without enabling the Lock component.'); - } - $loader->load('rate_limiter.php'); foreach ($config['limiters'] as $name => $limiterConfig) { @@ -2362,7 +2358,13 @@ public static function registerRateLimiter(ContainerBuilder $container, string $ $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); - $limiter->addArgument(new Reference($limiterConfig['lock_factory'])); + 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)); + } + + $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); + } unset($limiterConfig['lock_factory']); $storageId = $limiterConfig['storage_service'] ?? null; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/rate_limiter.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/rate_limiter.php index 39f92323f09a5..727a1f6364456 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/rate_limiter.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/rate_limiter.php @@ -24,6 +24,7 @@ ->args([ abstract_arg('config'), abstract_arg('storage'), + null, ]) ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index 0b8b4eb8fa406..b46bcf7713c95 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -13,6 +13,8 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\Workflow\Exception\InvalidDefinitionException; @@ -82,4 +84,49 @@ public function testWorkflowValidationStateMachine() ]); }); } + + public function testRateLimiterWithLockFactory() + { + try { + $this->createContainerFromClosure(function (ContainerBuilder $container) { + $container->loadFromExtension('framework', [ + 'rate_limiter' => [ + 'with_lock' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour'], + ], + ]); + }); + + $this->fail('No LogicException thrown'); + } catch (LogicException $e) { + $this->assertEquals('Rate limiter "with_lock" requires the Lock component to be installed and configured.', $e->getMessage()); + } + + $container = $this->createContainerFromClosure(function (ContainerBuilder $container) { + $container->loadFromExtension('framework', [ + 'lock' => true, + 'rate_limiter' => [ + 'with_lock' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour'], + ], + ]); + }); + + $withLock = $container->getDefinition('limiter.with_lock'); + $this->assertEquals('lock.factory', (string) $withLock->getArgument(2)); + } + + public function testRateLimiterLockFactory() + { + $container = $this->createContainerFromClosure(function (ContainerBuilder $container) { + $container->loadFromExtension('framework', [ + 'rate_limiter' => [ + 'without_lock' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour', 'lock_factory' => null], + ], + ]); + }); + + $this->expectException(OutOfBoundsException::class); + $this->expectExceptionMessage('The argument "2" doesn\'t exist.'); + + $container->getDefinition('limiter.without_lock')->getArgument(2); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 5cada9c672733..a313abdf891f6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -51,6 +51,7 @@ "symfony/messenger": "^5.2", "symfony/mime": "^4.4|^5.0", "symfony/process": "^4.4|^5.0", + "symfony/rate-limiter": "^5.2", "symfony/security-bundle": "^5.3", "symfony/serializer": "^5.2", "symfony/stopwatch": "^4.4|^5.0", diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 2473dfd9ced74..d36da75e80e25 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3 --- + * [BC break] Add `login_throttling.lock_factory` setting defaulting to `null` (instead of `lock.factory`) * Add the `debug:firewall` command. * Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command, use `UserPasswordHashCommand` and `user:hash-password` instead diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php index c0aa37ec88712..f8e3416e68819 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php @@ -54,6 +54,7 @@ public function addConfiguration(NodeDefinition $builder) ->children() ->scalarNode('limiter')->info(sprintf('A service id implementing "%s".', RequestRateLimiterInterface::class))->end() ->integerNode('max_attempts')->defaultValue(5)->end() + ->scalarNode('lock_factory')->info('The service ID of the lock factory used by the login rate limiter (or null to disable locking)')->defaultNull()->end() ->end(); } @@ -76,6 +77,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal 'policy' => 'fixed_window', 'limit' => $config['max_attempts'], 'interval' => '1 minute', + 'lock_factory' => $config['lock_factory'], ]; FrameworkExtension::registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions); From 439742ff336dfdd66fa7681c2e4e5037affc9508 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 25 Feb 2021 16:41:26 +0100 Subject: [PATCH 177/188] [HttpClient] Add `HttpClientInterface::withOptions()` --- composer.json | 2 +- .../HttpClient/AsyncDecoratorTrait.php | 11 +++++ src/Symfony/Component/HttpClient/CHANGELOG.md | 5 ++ .../Component/HttpClient/CurlHttpClient.php | 28 ++--------- .../HttpClient/EventSourceHttpClient.php | 5 +- .../Component/HttpClient/HttpClientTrait.php | 13 ++++- .../HttpClient/Internal/CurlClientState.php | 47 +++++++++++++++++++ .../Component/HttpClient/MockHttpClient.php | 17 +++++-- .../HttpClient/NoPrivateNetworkHttpClient.php | 11 +++++ .../HttpClient/ScopingHttpClient.php | 11 +++++ .../HttpClient/TraceableHttpClient.php | 11 +++++ .../Component/HttpClient/composer.json | 4 +- src/Symfony/Contracts/CHANGELOG.md | 5 ++ src/Symfony/Contracts/Cache/composer.json | 2 +- .../Contracts/Deprecation/composer.json | 2 +- .../Contracts/EventDispatcher/composer.json | 2 +- .../HttpClient/HttpClientInterface.php | 2 + .../HttpClient/Test/HttpClientTestCase.php | 16 +++++++ .../Contracts/HttpClient/composer.json | 2 +- src/Symfony/Contracts/Service/composer.json | 2 +- .../Contracts/Translation/composer.json | 2 +- src/Symfony/Contracts/composer.json | 2 +- 22 files changed, 161 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index 41e1911b6eca3..5295ee17ca760 100644 --- a/composer.json +++ b/composer.json @@ -188,7 +188,7 @@ "url": "src/Symfony/Contracts", "options": { "versions": { - "symfony/contracts": "2.3.x-dev" + "symfony/contracts": "2.4.x-dev" } } } diff --git a/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php b/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php index c5d40a251d3d8..2e6267300d138 100644 --- a/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php +++ b/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php @@ -51,4 +51,15 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class)); } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/CHANGELOG.md b/src/Symfony/Component/HttpClient/CHANGELOG.md index f25989e168396..3b97488c93fa1 100644 --- a/src/Symfony/Component/HttpClient/CHANGELOG.md +++ b/src/Symfony/Component/HttpClient/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3 +--- + + * Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4 + 5.2.0 ----- diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 48ecf773d4cfd..3a75e675e25e8 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -341,30 +341,8 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa public function reset() { - if ($this->logger) { - foreach ($this->multi->pushedResponses as $url => $response) { - $this->logger->debug(sprintf('Unused pushed response: "%s"', $url)); - } - } - - $this->multi->pushedResponses = []; - $this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals; - $this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = []; - - if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) { - if (\defined('CURLMOPT_PUSHFUNCTION')) { - curl_multi_setopt($this->multi->handle, \CURLMOPT_PUSHFUNCTION, null); - } - - $active = 0; - while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)); - } - - foreach ($this->multi->openHandles as [$ch]) { - if (\is_resource($ch) || $ch instanceof \CurlHandle) { - curl_setopt($ch, \CURLOPT_VERBOSE, false); - } - } + $this->multi->logger = $this->logger; + $this->multi->reset(); } public function __sleep() @@ -379,7 +357,7 @@ public function __wakeup() public function __destruct() { - $this->reset(); + $this->multi->logger = $this->logger; } private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int diff --git a/src/Symfony/Component/HttpClient/EventSourceHttpClient.php b/src/Symfony/Component/HttpClient/EventSourceHttpClient.php index d6f27a7fae99f..81d9f4bfc8bf8 100644 --- a/src/Symfony/Component/HttpClient/EventSourceHttpClient.php +++ b/src/Symfony/Component/HttpClient/EventSourceHttpClient.php @@ -26,8 +26,9 @@ */ final class EventSourceHttpClient implements HttpClientInterface { - use AsyncDecoratorTrait; - use HttpClientTrait; + use AsyncDecoratorTrait, HttpClientTrait { + AsyncDecoratorTrait::withOptions insteadof HttpClientTrait; + } private $reconnectionTime; diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 545b1aad283de..9d68f9a61f61d 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -17,7 +17,7 @@ /** * Provides the common logic from writing HttpClientInterface implementations. * - * All methods are static to prevent implementers from creating memory leaks via circular references. + * All private methods are static to prevent implementers from creating memory leaks via circular references. * * @author Nicolas Grekas */ @@ -25,6 +25,17 @@ trait HttpClientTrait { private static $CHUNK_SIZE = 16372; + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions); + + return $clone; + } + /** * Validates and normalizes method, URL and options, and merges them with defaults. * diff --git a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php index f381968d143c1..40ed0f1fc8c97 100644 --- a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpClient\Internal; +use Psr\Log\LoggerInterface; + /** * Internal representation of the cURL client's state. * @@ -29,10 +31,55 @@ final class CurlClientState extends ClientState /** @var float[] */ public $pauseExpiries = []; public $execCounter = \PHP_INT_MIN; + /** @var LoggerInterface|null */ + public $logger; public function __construct() { $this->handle = curl_multi_init(); $this->dnsCache = new DnsCache(); } + + public function reset() + { + if ($this->logger) { + foreach ($this->pushedResponses as $url => $response) { + $this->logger->debug(sprintf('Unused pushed response: "%s"', $url)); + } + } + + $this->pushedResponses = []; + $this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals; + $this->dnsCache->removals = $this->dnsCache->hostnames = []; + + if (\is_resource($this->handle) || $this->handle instanceof \CurlMultiHandle) { + if (\defined('CURLMOPT_PUSHFUNCTION')) { + curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, null); + } + + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->handle, $active)); + } + + foreach ($this->openHandles as [$ch]) { + if (\is_resource($ch) || $ch instanceof \CurlHandle) { + curl_setopt($ch, \CURLOPT_VERBOSE, false); + } + } + } + + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->reset(); + } } diff --git a/src/Symfony/Component/HttpClient/MockHttpClient.php b/src/Symfony/Component/HttpClient/MockHttpClient.php index 08571f07c209b..a794faff6e75c 100644 --- a/src/Symfony/Component/HttpClient/MockHttpClient.php +++ b/src/Symfony/Component/HttpClient/MockHttpClient.php @@ -28,8 +28,8 @@ class MockHttpClient implements HttpClientInterface use HttpClientTrait; private $responseFactory; - private $baseUri; private $requestsCount = 0; + private $defaultOptions = []; /** * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory @@ -47,7 +47,7 @@ public function __construct($responseFactory = null, string $baseUri = null) } $this->responseFactory = $responseFactory; - $this->baseUri = $baseUri; + $this->defaultOptions['base_uri'] = $baseUri; } /** @@ -55,7 +55,7 @@ public function __construct($responseFactory = null, string $baseUri = null) */ public function request(string $method, string $url, array $options = []): ResponseInterface { - [$url, $options] = $this->prepareRequest($method, $url, $options, ['base_uri' => $this->baseUri], true); + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); $url = implode('', $url); if (null === $this->responseFactory) { @@ -96,4 +96,15 @@ public function getRequestsCount(): int { return $this->requestsCount; } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php index d4c69cabcea95..b9db846992cf3 100644 --- a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php +++ b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php @@ -110,4 +110,15 @@ public function setLogger(LoggerInterface $logger): void $this->client->setLogger($logger); } } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/ScopingHttpClient.php b/src/Symfony/Component/HttpClient/ScopingHttpClient.php index 66dcccf0e93f2..2a6e70e15d7c3 100644 --- a/src/Symfony/Component/HttpClient/ScopingHttpClient.php +++ b/src/Symfony/Component/HttpClient/ScopingHttpClient.php @@ -110,4 +110,15 @@ public function setLogger(LoggerInterface $logger): void $this->client->setLogger($logger); } } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/TraceableHttpClient.php b/src/Symfony/Component/HttpClient/TraceableHttpClient.php index 34dc01ad2553f..bc842115900de 100644 --- a/src/Symfony/Component/HttpClient/TraceableHttpClient.php +++ b/src/Symfony/Component/HttpClient/TraceableHttpClient.php @@ -105,4 +105,15 @@ public function setLogger(LoggerInterface $logger): void $this->client->setLogger($logger); } } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 880798e83b032..be66a8f5c28e5 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -18,12 +18,12 @@ "php-http/async-client-implementation": "*", "php-http/client-implementation": "*", "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "2.2" + "symfony/http-client-implementation": "2.4" }, "require": { "php": ">=7.2.5", "psr/log": "^1.0", - "symfony/http-client-contracts": "^2.2", + "symfony/http-client-contracts": "^2.4", "symfony/polyfill-php73": "^1.11", "symfony/polyfill-php80": "^1.15", "symfony/service-contracts": "^1.0|^2" diff --git a/src/Symfony/Contracts/CHANGELOG.md b/src/Symfony/Contracts/CHANGELOG.md index b62029adb59d7..25efba02b929f 100644 --- a/src/Symfony/Contracts/CHANGELOG.md +++ b/src/Symfony/Contracts/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.4 +--- + + * Add `HttpClientInterface::withOptions()` + 2.3.0 ----- diff --git a/src/Symfony/Contracts/Cache/composer.json b/src/Symfony/Contracts/Cache/composer.json index 2b83ae24bc52d..0f6989201a133 100644 --- a/src/Symfony/Contracts/Cache/composer.json +++ b/src/Symfony/Contracts/Cache/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Deprecation/composer.json b/src/Symfony/Contracts/Deprecation/composer.json index 88d3055927dea..3884889637a89 100644 --- a/src/Symfony/Contracts/Deprecation/composer.json +++ b/src/Symfony/Contracts/Deprecation/composer.json @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/EventDispatcher/composer.json b/src/Symfony/Contracts/EventDispatcher/composer.json index 8eaf0354fd2d7..9f6dc413a00ff 100644 --- a/src/Symfony/Contracts/EventDispatcher/composer.json +++ b/src/Symfony/Contracts/EventDispatcher/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php index 4388eb84ce48b..ea793ba3943a8 100644 --- a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php +++ b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php @@ -19,6 +19,8 @@ * * @see HttpClientTestCase for a reference test suite * + * @method static withOptions(array $options) Returns a new instance of the client with new default options + * * @author Nicolas Grekas */ interface HttpClientInterface diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index 74997eaafcf92..38b845438dd30 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -1038,4 +1038,20 @@ public function testMaxDuration() $this->assertLessThan(10, $duration); } + + public function testWithOptions() + { + $client = $this->getHttpClient(__FUNCTION__); + if (!method_exists($client, 'withOptions')) { + $this->markTestSkipped(sprintf('Not implementing "%s::withOptions()" is deprecated.', get_debug_type($client))); + } + + $client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']); + + $this->assertNotSame($client, $client2); + $this->assertSame(\get_class($client), \get_class($client2)); + + $response = $client2->request('GET', '/'); + $this->assertSame(200, $response->getStatusCode()); + } } diff --git a/src/Symfony/Contracts/HttpClient/composer.json b/src/Symfony/Contracts/HttpClient/composer.json index 9ab9eaf76cc7a..2633785448429 100644 --- a/src/Symfony/Contracts/HttpClient/composer.json +++ b/src/Symfony/Contracts/HttpClient/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Service/composer.json b/src/Symfony/Contracts/Service/composer.json index 524f6a6c6c1ec..ec22b07db6e4b 100644 --- a/src/Symfony/Contracts/Service/composer.json +++ b/src/Symfony/Contracts/Service/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Translation/composer.json b/src/Symfony/Contracts/Translation/composer.json index 907d28f0a878e..00e27f836a6a8 100644 --- a/src/Symfony/Contracts/Translation/composer.json +++ b/src/Symfony/Contracts/Translation/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/composer.json b/src/Symfony/Contracts/composer.json index 595e744af584a..b424c33fc8edd 100644 --- a/src/Symfony/Contracts/composer.json +++ b/src/Symfony/Contracts/composer.json @@ -49,7 +49,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" } } } From 4aca3edb9ef9ef15fbb5bf72f8bda67639cc2e6c Mon Sep 17 00:00:00 2001 From: Jan Rosier Date: Thu, 25 Feb 2021 19:01:06 +0100 Subject: [PATCH 178/188] Fix deprecation messages --- .../Component/Security/Core/Encoder/BasePasswordEncoder.php | 4 ++-- .../Component/Security/Core/Encoder/EncoderFactory.php | 6 +++--- .../Security/Core/Encoder/EncoderFactoryInterface.php | 6 +++--- .../Security/Core/Encoder/MessageDigestPasswordEncoder.php | 4 ++-- .../Security/Core/Encoder/MigratingPasswordEncoder.php | 4 ++-- .../Security/Core/Encoder/NativePasswordEncoder.php | 5 ++--- .../Security/Core/Encoder/PasswordEncoderInterface.php | 6 +++--- .../Security/Core/Encoder/Pbkdf2PasswordEncoder.php | 5 ++--- .../Security/Core/Encoder/PlaintextPasswordEncoder.php | 4 ++-- .../Security/Core/Encoder/SelfSaltingEncoderInterface.php | 4 ++-- .../Security/Core/Encoder/SodiumPasswordEncoder.php | 4 ++-- .../Component/Security/Core/Encoder/UserPasswordEncoder.php | 6 +++--- .../Security/Core/Encoder/UserPasswordEncoderInterface.php | 4 ++-- 13 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php index 9c014d9ee3f4a..613cddd84a06b 100644 --- a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', BasePasswordEncoder::class, CheckPasswordLengthTrait::class)); - use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', BasePasswordEncoder::class, CheckPasswordLengthTrait::class)); + /** * BasePasswordEncoder is the base class for all password encoders. * diff --git a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php index e90498a3dab46..e2294d5b41c3f 100644 --- a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php +++ b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php @@ -11,11 +11,11 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class)); - -use Symfony\Component\Security\Core\Exception\LogicException; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; +use Symfony\Component\Security\Core\Exception\LogicException; + +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class)); /** * A generic encoder factory implementation. diff --git a/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php b/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php index 65fd12d81eb51..4c2f9fb6e4484 100644 --- a/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/EncoderFactoryInterface.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactoryInterface::class, PasswordHasherFactoryInterface::class)); - -use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\Security\Core\User\UserInterface; + +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactoryInterface::class, PasswordHasherFactoryInterface::class)); /** * EncoderFactoryInterface to support different encoders for different accounts. diff --git a/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php index d4b1fb54b3da2..416e940dcc613 100644 --- a/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MessageDigestPasswordEncoder::class, MessageDigestPasswordHasher::class)); - use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MessageDigestPasswordEncoder::class, MessageDigestPasswordHasher::class)); + /** * MessageDigestPasswordEncoder uses a message digest algorithm. * diff --git a/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php index be178731e1148..f3243258a64f9 100644 --- a/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MigratingPasswordEncoder::class, MigratingPasswordHasher::class)); - use Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MigratingPasswordEncoder::class, MigratingPasswordHasher::class)); + /** * Hashes passwords using the best available encoder. * Validates them using a chain of encoders. diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php index b3bd4b54a285f..f80d1957e5632 100644 --- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php @@ -11,11 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', NativePasswordEncoder::class, NativePasswordHasher::class)); - -use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', NativePasswordEncoder::class, NativePasswordHasher::class)); + /** * Hashes passwords using password_hash(). * diff --git a/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php b/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php index ba9216ebe5160..2b55af05779f2 100644 --- a/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', PasswordEncoderInterface::class, PasswordHasherInterface::class)); - -use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; + +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', PasswordEncoderInterface::class, PasswordHasherInterface::class)); /** * PasswordEncoderInterface is the interface for all encoders. diff --git a/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php index a50ad01ea1c6b..fcc286a042de7 100644 --- a/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/Pbkdf2PasswordEncoder.php @@ -11,11 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', Pbkdf2PasswordEncoder::class, Pbkdf2PasswordHasher::class)); - -use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', Pbkdf2PasswordEncoder::class, Pbkdf2PasswordHasher::class)); + /** * Pbkdf2PasswordEncoder uses the PBKDF2 (Password-Based Key Derivation Function 2). * diff --git a/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php index 65fc8502791b5..3165855b3d9ca 100644 --- a/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', PlaintextPasswordEncoder::class, PlaintextPasswordHasher::class)); - use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', PlaintextPasswordEncoder::class, PlaintextPasswordHasher::class)); + /** * PlaintextPasswordEncoder does not do any encoding but is useful in testing environments. * diff --git a/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php b/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php index 6bb983dd14e69..d1e93e165d512 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/SelfSaltingEncoderInterface.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" on hasher implementations that deal with salts instead.', SelfSaltingEncoderInterface::class, LegacyPasswordHasherInterface::class)); - use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" on hasher implementations that deal with salts instead.', SelfSaltingEncoderInterface::class, LegacyPasswordHasherInterface::class)); + /** * SelfSaltingEncoderInterface is a marker interface for encoders that do not * require a user-generated salt. diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index 480adb4a14f34..95810e597a9a1 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', SodiumPasswordEncoder::class, SodiumPasswordHasher::class)); - use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', SodiumPasswordEncoder::class, SodiumPasswordHasher::class)); + /** * Hashes passwords using libsodium. * diff --git a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php index bfe31a4a0faa2..32ab07c69041c 100644 --- a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', UserPasswordEncoder::class, UserPasswordHasher::class)); - -use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; +use Symfony\Component\Security\Core\User\UserInterface; + +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', UserPasswordEncoder::class, UserPasswordHasher::class)); /** * A generic password encoder. diff --git a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php index 99ce44144d2cc..a113d1085acec 100644 --- a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php +++ b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php @@ -11,11 +11,11 @@ namespace Symfony\Component\Security\Core\Encoder; -trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" instead.', UserPasswordEncoderInterface::class, UserPasswordHasherInterface::class)); - use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Security\Core\User\UserInterface; +trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" interface is deprecated, use "%s" instead.', UserPasswordEncoderInterface::class, UserPasswordHasherInterface::class)); + /** * UserPasswordEncoderInterface is the interface for the password encoder service. * From 28e7b74b47f8fecb3a9e9d09ea10c1db456f146f Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 22 Dec 2020 12:01:12 +0100 Subject: [PATCH 179/188] [Messenger] Add `rediss://` DSN scheme support for TLS to Redis transport --- UPGRADE-5.3.md | 1 + UPGRADE-6.0.md | 1 + .../Messenger/Bridge/Redis/CHANGELOG.md | 6 ++++++ .../Redis/Tests/Transport/ConnectionTest.php | 17 +++++++++++++++++ .../Bridge/Redis/Transport/Connection.php | 8 +++++--- .../Messenger/Transport/TransportFactory.php | 2 +- 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index fcf81d72ad38a..bdb61f495be61 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -50,6 +50,7 @@ Messenger --------- * Deprecated the `prefetch_count` parameter in the AMQP bridge, it has no effect and will be removed in Symfony 6.0 + * Deprecated the use of TLS option for Redis Bridge, use `rediss://127.0.0.1` instead of `redis://127.0.0.1?tls=1` Notifier -------- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 97521d71efad2..5d93fe244800b 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -122,6 +122,7 @@ Messenger * The signature of method `RetryStrategyInterface::isRetryable()` has been updated to `RetryStrategyInterface::isRetryable(Envelope $message, \Throwable $throwable = null)`. * The signature of method `RetryStrategyInterface::getWaitingTime()` has been updated to `RetryStrategyInterface::getWaitingTime(Envelope $message, \Throwable $throwable = null)`. * Removed the `prefetch_count` parameter in the AMQP bridge. + * Removed the use of TLS option for Redis Bridge, use `rediss://127.0.0.1` instead of `redis://127.0.0.1?tls=1` Mime ---- diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md index d8c6240fdab92..fa858097328e4 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +5.3 +--- + + * Add `rediss://` DSN scheme support for TLS protocol + * Deprecate TLS option, use `rediss://127.0.0.1` instead of `redis://127.0.0.1?tls=1` + 5.2.0 ----- diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php index 554b1f92cd2c7..d9eff1f9a75ef 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php @@ -86,6 +86,9 @@ public function testFromDsnWithOptionsAndTrailingSlash() ); } + /** + * @group legacy + */ public function testFromDsnWithTls() { $redis = $this->createMock(\Redis::class); @@ -97,6 +100,9 @@ public function testFromDsnWithTls() Connection::fromDsn('redis://127.0.0.1?tls=1', [], $redis); } + /** + * @group legacy + */ public function testFromDsnWithTlsOption() { $redis = $this->createMock(\Redis::class); @@ -108,6 +114,17 @@ public function testFromDsnWithTlsOption() Connection::fromDsn('redis://127.0.0.1', ['tls' => true], $redis); } + public function testFromDsnWithRedissScheme() + { + $redis = $this->createMock(\Redis::class); + $redis->expects($this->once()) + ->method('connect') + ->with('tls://127.0.0.1', 6379) + ->willReturn(null); + + Connection::fromDsn('rediss://127.0.0.1', [], $redis); + } + public function testFromDsnWithQueryOptions() { $this->assertEquals( diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index 737b24e66f751..cd4d854ffbbe4 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -119,9 +119,10 @@ public function __construct(array $configuration, array $connectionCredentials = public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self { $url = $dsn; + $scheme = 0 === strpos($dsn, 'rediss:') ? 'rediss' : 'redis'; - if (preg_match('#^redis:///([^:@])+$#', $dsn)) { - $url = str_replace('redis:', 'file:', $dsn); + if (preg_match('#^'.$scheme.':///([^:@])+$#', $dsn)) { + $url = str_replace($scheme.':', 'file:', $dsn); } if (false === $parsedUrl = parse_url($url)) { @@ -164,8 +165,9 @@ public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $re unset($redisOptions['dbindex']); } - $tls = false; + $tls = 'rediss' === $scheme; if (\array_key_exists('tls', $redisOptions)) { + trigger_deprecation('symfony/redis-messenger', '5.3', 'Providing "tls" parameter is deprecated, use "rediss://" DSN scheme instead'); $tls = filter_var($redisOptions['tls'], \FILTER_VALIDATE_BOOLEAN); unset($redisOptions['tls']); } diff --git a/src/Symfony/Component/Messenger/Transport/TransportFactory.php b/src/Symfony/Component/Messenger/Transport/TransportFactory.php index 37e7b114bbd10..ee57dd5adff7a 100644 --- a/src/Symfony/Component/Messenger/Transport/TransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/TransportFactory.php @@ -43,7 +43,7 @@ public function createTransport(string $dsn, array $options, SerializerInterface $packageSuggestion = ' Run "composer require symfony/amqp-messenger" to install AMQP transport.'; } elseif (0 === strpos($dsn, 'doctrine://')) { $packageSuggestion = ' Run "composer require symfony/doctrine-messenger" to install Doctrine transport.'; - } elseif (0 === strpos($dsn, 'redis://')) { + } elseif (0 === strpos($dsn, 'redis://') || 0 === strpos($dsn, 'rediss://')) { $packageSuggestion = ' Run "composer require symfony/redis-messenger" to install Redis transport.'; } elseif (0 === strpos($dsn, 'sqs://') || preg_match('#^https://sqs\.[\w\-]+\.amazonaws\.com/.+#', $dsn)) { $packageSuggestion = ' Run "composer require symfony/amazon-sqs-messenger" to install Amazon SQS transport.'; From d771e449ec92958d7cf7c88b3c8092d3e46c611e Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 25 Feb 2021 17:07:22 +0100 Subject: [PATCH 180/188] [HttpKernel] Handle multi-attribute controller arguments --- UPGRADE-5.3.md | 2 + UPGRADE-6.0.md | 2 + .../Attribute/ArgumentInterface.php | 4 ++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 3 ++ .../ControllerMetadata/ArgumentMetadata.php | 52 +++++++++++++++++-- .../ArgumentMetadataFactory.php | 24 ++------- .../ArgumentMetadataFactoryTest.php | 10 ++-- .../ArgumentMetadataTest.php | 28 ++++++++++ .../Tests/Fixtures/Attribute/Foo.php | 2 +- .../Controller/AttributeController.php | 2 +- .../Security/Http/Attribute/CurrentUser.php | 4 +- .../Http/Controller/UserValueResolver.php | 2 +- .../Controller/UserValueResolverTest.php | 5 +- .../Component/Security/Http/composer.json | 2 +- 14 files changed, 103 insertions(+), 39 deletions(-) diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index bdb61f495be61..6444adefd8c59 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -44,6 +44,8 @@ HttpFoundation HttpKernel ---------- + * Deprecate `ArgumentInterface` + * Deprecate `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead * Marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal Messenger diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 5d93fe244800b..a254493d4e0f6 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -92,6 +92,8 @@ HttpFoundation HttpKernel ---------- + * Remove `ArgumentInterface` + * Remove `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead * Made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+ * Removed support for `service:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead. diff --git a/src/Symfony/Component/HttpKernel/Attribute/ArgumentInterface.php b/src/Symfony/Component/HttpKernel/Attribute/ArgumentInterface.php index 8f0c6fb8b060c..78769f1ac0bd2 100644 --- a/src/Symfony/Component/HttpKernel/Attribute/ArgumentInterface.php +++ b/src/Symfony/Component/HttpKernel/Attribute/ArgumentInterface.php @@ -11,8 +11,12 @@ 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 */ interface ArgumentInterface { diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index e20b6c881d2d1..e2509ba1bbeca 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -4,6 +4,9 @@ CHANGELOG 5.3 --- + * Deprecate `ArgumentInterface` + * Add `ArgumentMetadata::getAttributes()` + * Deprecate `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead * marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal 5.2.0 diff --git a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php index 3454ff6e49417..1a9ebc0c3a5d1 100644 --- a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php +++ b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php @@ -20,15 +20,20 @@ */ class ArgumentMetadata { + public const IS_INSTANCEOF = 2; + private $name; private $type; private $isVariadic; private $hasDefaultValue; private $defaultValue; private $isNullable; - private $attribute; + private $attributes; - public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, $defaultValue, bool $isNullable = false, ?ArgumentInterface $attribute = null) + /** + * @param object[] $attributes + */ + public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, $defaultValue, bool $isNullable = false, $attributes = []) { $this->name = $name; $this->type = $type; @@ -36,7 +41,13 @@ public function __construct(string $name, ?string $type, bool $isVariadic, bool $this->hasDefaultValue = $hasDefaultValue; $this->defaultValue = $defaultValue; $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); - $this->attribute = $attribute; + + 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; } /** @@ -114,6 +125,39 @@ public function getDefaultValue() */ public function getAttribute(): ?ArgumentInterface { - return $this->attribute; + 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[] + */ + public function getAttributes(string $name = null, int $flags = 0): array + { + if (!$name) { + return $this->attributes; + } + + $attributes = []; + if ($flags & self::IS_INSTANCEOF) { + foreach ($this->attributes as $attribute) { + if ($attribute instanceof $name) { + $attributes[] = $attribute; + } + } + } else { + foreach ($this->attributes as $attribute) { + if (\get_class($attribute) === $name) { + $attributes[] = $attribute; + } + } + } + + return $attributes; } } diff --git a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php index f53bf065b96b1..a2feb05c10340 100644 --- a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php +++ b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -11,9 +11,6 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; -use Symfony\Component\HttpKernel\Attribute\ArgumentInterface; -use Symfony\Component\HttpKernel\Exception\InvalidMetadataException; - /** * Builds {@see ArgumentMetadata} objects based on the given Controller. * @@ -37,28 +34,15 @@ public function createArgumentMetadata($controller): array } foreach ($reflection->getParameters() as $param) { - $attribute = null; if (\PHP_VERSION_ID >= 80000) { - $reflectionAttributes = $param->getAttributes(ArgumentInterface::class, \ReflectionAttribute::IS_INSTANCEOF); - - if (\count($reflectionAttributes) > 1) { - $representative = $controller; - - if (\is_array($representative)) { - $representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]); - } elseif (\is_object($representative)) { - $representative = \get_class($representative); + foreach ($param->getAttributes() as $reflectionAttribute) { + if (class_exists($reflectionAttribute->getName())) { + $attributes[] = $reflectionAttribute->newInstance(); } - - throw new InvalidMetadataException(sprintf('Controller "%s" has more than one attribute for "$%s" argument.', $representative, $param->getName())); - } - - if (isset($reflectionAttributes[0])) { - $attribute = $reflectionAttributes[0]->newInstance(); } } - $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $reflection), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attribute); + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $reflection), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes ?? []); } return $arguments; diff --git a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php index 3c57c3161c8c5..d952e424c557e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -15,7 +15,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; -use Symfony\Component\HttpKernel\Exception\InvalidMetadataException; use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Foo; use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\AttributeController; use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\BasicTypesController; @@ -128,18 +127,17 @@ public function testAttributeSignature() $arguments = $this->factory->createArgumentMetadata([new AttributeController(), 'action']); $this->assertEquals([ - new ArgumentMetadata('baz', 'string', false, false, null, false, new Foo('bar')), + new ArgumentMetadata('baz', 'string', false, false, null, false, [new Foo('bar')]), ], $arguments); } /** * @requires PHP 8 */ - public function testAttributeSignatureError() + public function testMultipleAttributes() { - $this->expectException(InvalidMetadataException::class); - - $this->factory->createArgumentMetadata([new AttributeController(), 'invalidAction']); + $this->factory->createArgumentMetadata([new AttributeController(), 'multiAttributeArg']); + $this->assertCount(1, $this->factory->createArgumentMetadata([new AttributeController(), 'multiAttributeArg'])[0]->getAttributes()); } private function signature1(self $foo, array $bar, callable $baz) diff --git a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataTest.php b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataTest.php index fef6cd00025f0..45b15e174445f 100644 --- a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataTest.php @@ -12,10 +12,15 @@ namespace Symfony\Component\HttpKernel\Tests\ControllerMetadata; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Component\HttpKernel\Attribute\ArgumentInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Foo; class ArgumentMetadataTest extends TestCase { + use ExpectDeprecationTrait; + public function testWithBcLayerWithDefault() { $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value'); @@ -41,4 +46,27 @@ public function testDefaultValueUnavailable() $this->assertFalse($argument->hasDefaultValue()); $argument->getDefaultValue(); } + + /** + * @group legacy + */ + public function testLegacyAttribute() + { + $attribute = $this->createMock(ArgumentInterface::class); + + $this->expectDeprecation('Since symfony/http-kernel 5.3: The "Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata" constructor expects an array of PHP attributes as last argument, %s given.'); + $this->expectDeprecation('Since symfony/http-kernel 5.3: Method "Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata::getAttribute()" is deprecated, use "getAttributes()" instead.'); + + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value', true, $attribute); + $this->assertSame($attribute, $argument->getAttribute()); + } + + /** + * @requires PHP 8 + */ + public function testGetAttributes() + { + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value', true, [new Foo('bar')]); + $this->assertEquals([new Foo('bar')], $argument->getAttributes()); + } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/Attribute/Foo.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/Attribute/Foo.php index 96a03adaad0bd..e01a5a6e8ddcd 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/Attribute/Foo.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fixtures/Attribute/Foo.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpKernel\Attribute\ArgumentInterface; #[\Attribute(\Attribute::TARGET_PARAMETER)] -class Foo implements ArgumentInterface +class Foo { private $foo; diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/Controller/AttributeController.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/Controller/AttributeController.php index 910f418ae1eb1..d6e0cde58d883 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/Controller/AttributeController.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fixtures/Controller/AttributeController.php @@ -18,6 +18,6 @@ class AttributeController public function action(#[Foo('bar')] string $baz) { } - public function invalidAction(#[Foo('bar'), Foo('bar')] string $baz) { + public function multiAttributeArg(#[Foo('bar'), Undefined('bar')] string $baz) { } } diff --git a/src/Symfony/Component/Security/Http/Attribute/CurrentUser.php b/src/Symfony/Component/Security/Http/Attribute/CurrentUser.php index e9202ed2b35db..413f982ecc5ac 100644 --- a/src/Symfony/Component/Security/Http/Attribute/CurrentUser.php +++ b/src/Symfony/Component/Security/Http/Attribute/CurrentUser.php @@ -11,12 +11,10 @@ namespace Symfony\Component\Security\Http\Attribute; -use Symfony\Component\HttpKernel\Attribute\ArgumentInterface; - /** * Indicates that a controller argument should receive the current logged user. */ #[\Attribute(\Attribute::TARGET_PARAMETER)] -class CurrentUser implements ArgumentInterface +class CurrentUser { } diff --git a/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php b/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php index 4b469edf8b10e..715004318e18c 100644 --- a/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php +++ b/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php @@ -37,7 +37,7 @@ public function supports(Request $request, ArgumentMetadata $argument): bool { // with the attribute, the type can be any UserInterface implementation // otherwise, the type must be UserInterface - if (UserInterface::class !== $argument->getType() && !$argument->getAttribute() instanceof CurrentUser) { + if (UserInterface::class !== $argument->getType() && !$argument->getAttributes(CurrentUser::class, ArgumentMetadata::IS_INSTANCEOF)) { return false; } diff --git a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php index ca3197e5e4f9a..bfded5d20a141 100644 --- a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php @@ -77,7 +77,8 @@ public function testResolveWithAttribute() $tokenStorage->setToken($token); $resolver = new UserValueResolver($tokenStorage); - $metadata = new ArgumentMetadata('foo', null, false, false, null, false, new CurrentUser()); + $metadata = $this->createMock(ArgumentMetadata::class); + $metadata = new ArgumentMetadata('foo', null, false, false, null, false, [new CurrentUser()]); $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); $this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata))); @@ -89,7 +90,7 @@ public function testResolveWithAttributeAndNoUser() $tokenStorage->setToken(new UsernamePasswordToken('username', 'password', 'provider')); $resolver = new UserValueResolver($tokenStorage); - $metadata = new ArgumentMetadata('foo', null, false, false, null, false, new CurrentUser()); + $metadata = new ArgumentMetadata('foo', null, false, false, null, false, [new CurrentUser()]); $this->assertFalse($resolver->supports(Request::create('/'), $metadata)); } diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index c2297fc0535b4..d953ac23a63a3 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -20,7 +20,7 @@ "symfony/deprecation-contracts": "^2.1", "symfony/security-core": "^5.3", "symfony/http-foundation": "^5.2", - "symfony/http-kernel": "^5.2", + "symfony/http-kernel": "^5.3", "symfony/polyfill-php80": "^1.15", "symfony/property-access": "^4.4|^5.0" }, From 4a9c8297ea772de60cf159a9c4a4b861f473519f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 26 Feb 2021 14:19:17 +0100 Subject: [PATCH 181/188] fix tests --- .../Tests/DependencyInjection/ConfigurationTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 20e8720eb396f..1430ee9851b6e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Mailer\Mailer; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Notifier\Notifier; +use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; use Symfony\Component\Uid\Factory\UuidFactory; class ConfigurationTest extends TestCase @@ -563,7 +564,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'private_headers' => [], ], 'rate_limiter' => [ - 'enabled' => false, + 'enabled' => !class_exists(FullStack::class) && class_exists(TokenBucketLimiter::class), 'limiters' => [], ], 'uid' => [ From b169ef92599314698e3f4c910a0295a500f0fecc Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 27 Feb 2021 20:48:21 +0100 Subject: [PATCH 182/188] [FrameworkBundle] Explicitly disable lock to fix FrameworkBundle standalone tests --- .../Tests/DependencyInjection/PhpFrameworkExtensionTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index b46bcf7713c95..e400b95506b73 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -90,6 +90,7 @@ public function testRateLimiterWithLockFactory() try { $this->createContainerFromClosure(function (ContainerBuilder $container) { $container->loadFromExtension('framework', [ + 'lock' => false, 'rate_limiter' => [ 'with_lock' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour'], ], From cc7409502adfe58c9f3e7d8c646af140dfa1e9ac Mon Sep 17 00:00:00 2001 From: Damien Fa Date: Tue, 2 Mar 2021 00:08:58 +0100 Subject: [PATCH 183/188] changes rebased --- .../Bundle/SecurityBundle/CHANGELOG.md | 1 + .../Factory/LoginThrottlingFactory.php | 3 ++- .../Tests/Functional/FormLoginTest.php | 27 ++++++++++++------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index d36da75e80e25..fe22755dd3099 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * [BC break] Add `login_throttling.lock_factory` setting defaulting to `null` (instead of `lock.factory`) + * Add a `login_throttling.interval` (in `security.firewalls`) option to change the default throttling interval. * Add the `debug:firewall` command. * Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command, use `UserPasswordHashCommand` and `user:hash-password` instead diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php index f8e3416e68819..d9c3efd5f0faf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php @@ -54,6 +54,7 @@ public function addConfiguration(NodeDefinition $builder) ->children() ->scalarNode('limiter')->info(sprintf('A service id implementing "%s".', RequestRateLimiterInterface::class))->end() ->integerNode('max_attempts')->defaultValue(5)->end() + ->scalarNode('interval')->defaultValue('1 minute')->end() ->scalarNode('lock_factory')->info('The service ID of the lock factory used by the login rate limiter (or null to disable locking)')->defaultNull()->end() ->end(); } @@ -76,7 +77,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal $limiterOptions = [ 'policy' => 'fixed_window', 'limit' => $config['max_attempts'], - 'interval' => '1 minute', + 'interval' => $config['interval'], 'lock_factory' => $config['lock_factory'], ]; FrameworkExtension::registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php index f79028cb20719..5c1955f282c4c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php @@ -112,7 +112,7 @@ public function testFormLoginRedirectsToProtectedResourceAfterLogin(array $optio * @dataProvider provideInvalidCredentials * @group time-sensitive */ - public function testLoginThrottling($username, $password) + public function testLoginThrottling(string $username, string $password, int $attemptIndex) { if (!class_exists(LoginThrottlingListener::class)) { $this->markTestSkipped('Login throttling requires symfony/security-http:^5.2'); @@ -125,19 +125,28 @@ public function testLoginThrottling($username, $password) $form['_password'] = $password; $client->submit($form); - $client->followRedirect()->selectButton('login')->form(); - $form['_username'] = $username; - $form['_password'] = $password; - $client->submit($form); - $text = $client->followRedirect()->text(null, true); - $this->assertStringMatchesFormat('%sToo many failed login attempts, please try again in %d minute%s', $text); + if (1 === $attemptIndex) { + // First attempt : Invalid credentials (OK) + $this->assertStringMatchesFormat('%sInvalid credentials%s', $text); + } elseif (2 === $attemptIndex) { + // Second attempt : login throttling ! + $this->assertStringMatchesFormat('%sToo many failed login attempts, please try again in 8 minutes%s', $text); + } elseif (3 === $attemptIndex) { + // Third attempt with unexisting username + $this->assertStringMatchesFormat('%sUsername could not be found.%s', $text); + } elseif (4 === $attemptIndex) { + // Fourth attempt : still login throttling ! + $this->assertStringMatchesFormat('%sToo many failed login attempts, please try again in 8 minutes%s', $text); + } } public function provideInvalidCredentials() { - yield 'invalid_password' => ['johannes', 'wrong']; - yield 'invalid_username' => ['wrong', 'wrong']; + yield 'invalid_password' => ['johannes', 'wrong', 1]; + yield 'invalid_password_again' => ['johannes', 'also_wrong', 2]; + yield 'invalid_username' => ['wrong', 'wrong', 3]; + yield 'invalid_password_again_bis' => ['johannes', 'wrong_again', 4]; } public function provideClientOptions() From abb534ab566363bec1461e2edf01a056eeb29307 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Fri, 26 Feb 2021 18:06:15 -0500 Subject: [PATCH 184/188] implement twig serialize filter --- src/Symfony/Bridge/Twig/CHANGELOG.md | 5 ++ .../Twig/Extension/SerializerExtension.php | 28 ++++++++ .../Twig/Extension/SerializerRuntime.php | 33 +++++++++ .../Fixtures/SerializerModelFixture.php | 18 +++++ .../Extension/SerializerExtensionTest.php | 70 +++++++++++++++++++ src/Symfony/Bridge/Twig/composer.json | 1 + src/Symfony/Bundle/TwigBundle/CHANGELOG.md | 7 +- .../Compiler/ExtensionPass.php | 5 ++ .../TwigBundle/Resources/config/twig.php | 7 ++ 9 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bridge/Twig/Extension/SerializerExtension.php create mode 100644 src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php create mode 100644 src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php create mode 100644 src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index d44edb22fc923..b2d25bce4f6f7 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* Added a new `serialize` filter to serialize objects using the Serializer component + 5.2.0 ----- diff --git a/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php b/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php new file mode 100644 index 0000000000000..f38571efaaac8 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/SerializerExtension.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\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Jesse Rushlow + */ +final class SerializerExtension extends AbstractExtension +{ + public function getFilters(): array + { + return [ + new TwigFilter('serialize', [SerializerRuntime::class, 'serialize']), + ]; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php new file mode 100644 index 0000000000000..3a4087aa79e26 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.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\Bridge\Twig\Extension; + +use Symfony\Component\Serializer\SerializerInterface; +use Twig\Extension\RuntimeExtensionInterface; + +/** + * @author Jesse Rushlow + */ +final class SerializerRuntime implements RuntimeExtensionInterface +{ + private $serializer; + + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + public function serialize($data, string $format = 'json', array $context = []): string + { + return $this->serializer->serialize($data, $format, $context); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php new file mode 100644 index 0000000000000..07493ea9d8db7 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php @@ -0,0 +1,18 @@ + + */ +class SerializerModelFixture +{ + /** + * @Groups({"read"}) + */ + public $name = 'howdy'; + + public $title = 'fixture'; +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php new file mode 100644 index 0000000000000..ef54ee2775f15 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.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\Bridge\Twig\Tests\Extension; + +use Doctrine\Common\Annotations\AnnotationReader; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\SerializerExtension; +use Symfony\Bridge\Twig\Extension\SerializerRuntime; +use Symfony\Bridge\Twig\Tests\Extension\Fixtures\SerializerModelFixture; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\YamlEncoder; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; +use Twig\Environment; +use Twig\Loader\ArrayLoader; +use Twig\RuntimeLoader\RuntimeLoaderInterface; + +/** + * @author Jesse Rushlow + */ +class SerializerExtensionTest extends TestCase +{ + /** + * @dataProvider serializerDataProvider + */ + public function testSerializeFilter(string $template, string $expectedResult) + { + $twig = $this->getTwig($template); + + self::assertSame($expectedResult, $twig->render('template', ['object' => new SerializerModelFixture()])); + } + + public function serializerDataProvider(): \Generator + { + yield ['{{ object|serialize }}', '{"name":"howdy","title":"fixture"}']; + yield ['{{ object|serialize(\'yaml\') }}', '{ name: howdy, title: fixture }']; + yield ['{{ object|serialize(\'yaml\', {groups: \'read\'}) }}', '{ name: howdy }']; + } + + private function getTwig(string $template): Environment + { + $meta = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $runtime = new SerializerRuntime(new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()])); + + $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); + $mockRuntimeLoader + ->method('load') + ->willReturnMap([ + ['Symfony\Bridge\Twig\Extension\SerializerRuntime', $runtime], + ]) + ; + + $twig = new Environment(new ArrayLoader(['template' => $template])); + $twig->addExtension(new SerializerExtension()); + $twig->addRuntimeLoader($mockRuntimeLoader); + + return $twig; + } +} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 44fb474553635..29dc959e5c18c 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -22,6 +22,7 @@ "twig/twig": "^2.13|^3.0.4" }, "require-dev": { + "doctrine/annotations": "^1.12", "egulias/email-validator": "^2.1.10", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^4.4|^5.0", diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index be47f246de147..b1642ea1af00b 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* Added support for the new `serialize` filter (from Twig Bridge) + 5.2.0 ----- @@ -33,7 +38,7 @@ CHANGELOG 4.1.0 ----- - * added priority to Twig extensions + * added priority to Twig extensions * deprecated relying on the default value (`false`) of the `twig.strict_variables` configuration option. The `%kernel.debug%` parameter will be the new default in 5.0 4.0.0 diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index ae3f9df3befd0..a18de86e7b02d 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -120,5 +120,10 @@ public function process(ContainerBuilder $container) } else { $container->getDefinition('workflow.twig_extension')->addTag('twig.extension'); } + + if ($container->has('serializer')) { + $container->getDefinition('twig.runtime.serializer')->addTag('twig.runtime'); + $container->getDefinition('twig.extension.serializer')->addTag('twig.extension'); + } } } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php index a7124a30c20aa..aa71e0de86b15 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php @@ -23,6 +23,8 @@ use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; use Symfony\Bridge\Twig\Extension\ProfilerExtension; use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Extension\SerializerExtension; +use Symfony\Bridge\Twig\Extension\SerializerRuntime; use Symfony\Bridge\Twig\Extension\StopwatchExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Extension\WebLinkExtension; @@ -160,5 +162,10 @@ ->factory([TwigErrorRenderer::class, 'isDebug']) ->args([service('request_stack'), param('kernel.debug')]), ]) + + ->set('twig.runtime.serializer', SerializerRuntime::class) + ->args([service('serializer')]) + + ->set('twig.extension.serializer', SerializerExtension::class) ; }; From d1a0342e1eaf23b6f698835f86945c2ddba80165 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Tue, 2 Mar 2021 14:27:07 +0100 Subject: [PATCH 185/188] Fix tests --- .../Tests/Functional/FormLoginTest.php | 60 ++++++++++--------- .../StandardFormLogin/login_throttling.yml | 1 + 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php index 5c1955f282c4c..05b27e82acf47 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php @@ -109,10 +109,9 @@ public function testFormLoginRedirectsToProtectedResourceAfterLogin(array $optio } /** - * @dataProvider provideInvalidCredentials * @group time-sensitive */ - public function testLoginThrottling(string $username, string $password, int $attemptIndex) + public function testLoginThrottling() { if (!class_exists(LoginThrottlingListener::class)) { $this->markTestSkipped('Login throttling requires symfony/security-http:^5.2'); @@ -120,35 +119,40 @@ public function testLoginThrottling(string $username, string $password, int $att $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'login_throttling.yml', 'enable_authenticator_manager' => true]); - $form = $client->request('GET', '/login')->selectButton('login')->form(); - $form['_username'] = $username; - $form['_password'] = $password; - $client->submit($form); - - $text = $client->followRedirect()->text(null, true); - if (1 === $attemptIndex) { - // First attempt : Invalid credentials (OK) - $this->assertStringMatchesFormat('%sInvalid credentials%s', $text); - } elseif (2 === $attemptIndex) { - // Second attempt : login throttling ! - $this->assertStringMatchesFormat('%sToo many failed login attempts, please try again in 8 minutes%s', $text); - } elseif (3 === $attemptIndex) { - // Third attempt with unexisting username - $this->assertStringMatchesFormat('%sUsername could not be found.%s', $text); - } elseif (4 === $attemptIndex) { - // Fourth attempt : still login throttling ! - $this->assertStringMatchesFormat('%sToo many failed login attempts, please try again in 8 minutes%s', $text); + $attempts = [ + ['johannes', 'wrong'], + ['johannes', 'also_wrong'], + ['wrong', 'wrong'], + ['johannes', 'wrong_again'], + ]; + foreach ($attempts as $i => $attempt) { + $form = $client->request('GET', '/login')->selectButton('login')->form(); + $form['_username'] = $attempt[0]; + $form['_password'] = $attempt[1]; + $client->submit($form); + + $text = $client->followRedirect()->text(null, true); + switch ($i) { + case 0: // First attempt : Invalid credentials (OK) + $this->assertStringContainsString('Invalid credentials', $text, 'Invalid response on 1st attempt'); + + break; + case 1: // Second attempt : login throttling ! + $this->assertStringContainsString('Too many failed login attempts, please try again in 8 minutes.', $text, 'Invalid response on 2nd attempt'); + + break; + case 2: // Third attempt with unexisting username + $this->assertStringContainsString('Username could not be found.', $text, 'Invalid response on 3rd attempt'); + + break; + case 3: // Fourth attempt : still login throttling ! + $this->assertStringContainsString('Too many failed login attempts, please try again in 8 minutes.', $text, 'Invalid response on 4th attempt'); + + break; + } } } - public function provideInvalidCredentials() - { - yield 'invalid_password' => ['johannes', 'wrong', 1]; - yield 'invalid_password_again' => ['johannes', 'also_wrong', 2]; - yield 'invalid_username' => ['wrong', 'wrong', 3]; - yield 'invalid_password_again_bis' => ['johannes', 'wrong_again', 4]; - } - public function provideClientOptions() { yield [['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml', 'enable_authenticator_manager' => true]]; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml index 4848567cf3360..c445ce6963841 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml @@ -10,3 +10,4 @@ security: default: login_throttling: max_attempts: 1 + interval: '8 minutes' From 91e844b517ea0ad0eb5e45cd96d2491a412482e1 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Mon, 1 Mar 2021 21:47:33 +0100 Subject: [PATCH 186/188] [FrameworkBundle] Add support for doctrine/annotations:1.13 || 2.0 --- .../Bundle/FrameworkBundle/CHANGELOG.md | 2 + .../DependencyInjection/Configuration.php | 6 +- .../FrameworkExtension.php | 66 ++++++++++++++----- .../Resources/config/annotations.php | 21 +++++- .../FrameworkExtensionTest.php | 7 +- .../Tests/Functional/AutowiringTypesTest.php | 3 +- 6 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index d6c5ece5e02c7..e8924119b9927 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -14,6 +14,8 @@ CHANGELOG * Added `assertResponseFormatSame()` in `BrowserKitAssertionsTrait` * Add support for configuring UUID factory services * Add tag `assets.package` to register asset packages + * Add support to use a PSR-6 compatible cache for Doctrine annotations + * Deprecate all other values than "none", "php_array" and "file" for `framework.annotation.cache` 5.2.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 565b9d3960d6e..0fdb34cbb644f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -12,6 +12,7 @@ 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 Symfony\Bundle\FullStack; @@ -921,13 +922,16 @@ private function addValidationSection(ArrayNodeDefinition $rootNode, callable $e private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { + $doctrineCache = $willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotation'); + $psr6Cache = $willBeAvailable('symfony/cache', PsrCachedReader::class, 'doctrine/annotation'); + $rootNode ->children() ->arrayNode('annotations') ->info('annotation configuration') ->{$willBeAvailable('doctrine/annotations', Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() - ->scalarNode('cache')->defaultValue($willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotation') ? 'php_array' : 'none')->end() + ->scalarNode('cache')->defaultValue(($doctrineCache || $psr6Cache) ? 'php_array' : 'none')->end() ->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end() ->booleanNode('debug')->defaultValue($this->debug)->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 07c033736771f..3ef5e51939e9c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1481,13 +1481,50 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde ->setMethodCalls([['registerLoader', ['class_exists']]]); } - if ('none' !== $config['cache']) { + if ('none' === $config['cache']) { + $container->removeDefinition('annotations.cached_reader'); + $container->removeDefinition('annotations.psr_cached_reader'); + + return; + } + + $cacheService = $config['cache']; + if (\in_array($config['cache'], ['php_array', 'file'])) { + $isPsr6Service = $container->hasDefinition('annotations.psr_cached_reader'); + } else { + $isPsr6Service = false; + 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.'); + } + + if ($isPsr6Service) { + $container->removeDefinition('annotations.cached_reader'); + $container->setDefinition('annotations.cached_reader', $container->getDefinition('annotations.psr_cached_reader')); + + if ('php_array' === $config['cache']) { + $cacheService = 'annotations.psr_cache'; + + // 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) + ; + } + } else { + // Legacy code for doctrine/annotations:<1.13 if (!class_exists(\Doctrine\Common\Cache\CacheProvider::class)) { throw new LogicException('Annotations cannot be cached as the Doctrine Cache library is not installed. Try running "composer require doctrine/cache".'); } - $cacheService = $config['cache']; - if ('php_array' === $config['cache']) { $cacheService = 'annotations.cache'; @@ -1508,20 +1545,19 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde $cacheService = 'annotations.filesystem_cache'; } + } - $container - ->getDefinition('annotations.cached_reader') - ->replaceArgument(2, $config['debug']) - // temporary property to lazy-reference the cache provider without using it until AddAnnotationsCachedReaderPass runs - ->setProperty('cacheProviderBackup', new ServiceClosureArgument(new Reference($cacheService))) - ->addTag('annotations.cached_reader') - ; + $container + ->getDefinition('annotations.cached_reader') + ->replaceArgument(2, $config['debug']) + // temporary property to lazy-reference the cache provider without using it until AddAnnotationsCachedReaderPass runs + ->setProperty('cacheProviderBackup', new ServiceClosureArgument(new Reference($cacheService))) + ->addTag('annotations.cached_reader') + ; - $container->setAlias('annotation_reader', 'annotations.cached_reader'); - $container->setAlias(Reader::class, new Alias('annotations.cached_reader', false)); - } else { - $container->removeDefinition('annotations.cached_reader'); - } + $container->setAlias('annotation_reader', 'annotations.cached_reader'); + $container->setAlias(Reader::class, new Alias('annotations.cached_reader', false)); + $container->removeDefinition('annotations.psr_cached_reader'); } private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php index 187d9da6642d0..a880b75a8b9c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php @@ -14,6 +14,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\CachedReader; +use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Common\Annotations\Reader; use Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -36,7 +37,7 @@ ->args([ service('annotations.reader'), inline_service(DoctrineProvider::class)->args([ - inline_service(ArrayAdapter::class) + inline_service(ArrayAdapter::class), ]), abstract_arg('Debug-Flag'), ]) @@ -74,4 +75,22 @@ ->alias('annotation_reader', 'annotations.reader') ->alias(Reader::class, 'annotation_reader'); + + if (class_exists(PsrCachedReader::class)) { + $container->services() + ->set('annotations.psr_cached_reader', PsrCachedReader::class) + ->args([ + service('annotations.reader'), + inline_service(ArrayAdapter::class), + abstract_arg('Debug-Flag'), + ]) + ->set('annotations.psr_cache', PhpArrayAdapter::class) + ->factory([PhpArrayAdapter::class, 'create']) + ->args([ + param('kernel.cache_dir').'/annotations.php', + service('cache.annotations'), + ]) + ->tag('container.hot_path') + ; + } }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index e80bece911a08..83087e5844550 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; use Doctrine\Common\Annotations\Annotation; +use Doctrine\Common\Annotations\PsrCachedReader; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerAwareInterface; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; @@ -578,7 +579,7 @@ public function testNullSessionHandler() $expected = ['session', 'initialized_session', 'logger', 'session_collector']; $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); - $this->assertSame(false, $container->getDefinition('session.storage.factory.native')->getArgument(3)); + $this->assertFalse($container->getDefinition('session.storage.factory.native')->getArgument(3)); } /** @@ -597,7 +598,7 @@ public function testNullSessionHandlerLegacy() $expected = ['session', 'initialized_session', 'logger', 'session_collector']; $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); - $this->assertSame(false, $container->getDefinition('session.storage.factory.native')->getArgument(3)); + $this->assertFalse($container->getDefinition('session.storage.factory.native')->getArgument(3)); } public function testRequest() @@ -980,7 +981,7 @@ public function testAnnotations() $container->compile(); $this->assertEquals($container->getParameter('kernel.cache_dir').'/annotations', $container->getDefinition('annotations.filesystem_cache_adapter')->getArgument(2)); - $this->assertSame('annotations.filesystem_cache', (string) $container->getDefinition('annotation_reader')->getArgument(1)); + $this->assertSame(class_exists(PsrCachedReader::class) ? 'annotations.filesystem_cache_adapter' : 'annotations.filesystem_cache', (string) $container->getDefinition('annotation_reader')->getArgument(1)); } public function testFileLinkFormat() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php index 1d34e54d17a09..b747b75a9af93 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php @@ -13,6 +13,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\CachedReader; +use Doctrine\Common\Annotations\PsrCachedReader; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; @@ -33,7 +34,7 @@ public function testCachedAnnotationReaderAutowiring() static::bootKernel(); $annotationReader = static::$container->get('test.autowiring_types.autowired_services')->getAnnotationReader(); - $this->assertInstanceOf(CachedReader::class, $annotationReader); + $this->assertInstanceOf(class_exists(PsrCachedReader::class) ? PsrCachedReader::class : CachedReader::class, $annotationReader); } public function testEventDispatcherAutowiring() From bccf736b9929274a42bb2ec912f955411c794136 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Fri, 5 Mar 2021 10:24:52 +0100 Subject: [PATCH 187/188] [Security] Readd accidentally removed property declarations --- .../Security/Core/Encoder/MigratingPasswordEncoder.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php index f3243258a64f9..af881a96e5420 100644 --- a/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/MigratingPasswordEncoder.php @@ -28,6 +28,9 @@ */ final class MigratingPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface { + private $bestEncoder; + private $extraEncoders; + public function __construct(PasswordEncoderInterface $bestEncoder, PasswordEncoderInterface ...$extraEncoders) { $this->bestEncoder = $bestEncoder; From 0dd78da640c7126d2cf53cdb14d0567926563892 Mon Sep 17 00:00:00 2001 From: Ronny Date: Fri, 5 Mar 2021 13:17:18 +0100 Subject: [PATCH 188/188] Correct ZulipTransportFactory tag Changing ZulipTransportFactory to 'chatter.transport_factory' to prevent the exception UnsupportedSchemeException "The "zulip" scheme is not supported." Fixing #40374 --- .../FrameworkBundle/Resources/config/notifier_transports.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index e6e0b39a5c6d2..23a9a47936df5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -105,7 +105,7 @@ ->set('notifier.transport_factory.zulip', ZulipTransportFactory::class) ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') + ->tag('chatter.transport_factory') ->set('notifier.transport_factory.infobip', InfobipTransportFactory::class) ->parent('notifier.transport_factory.abstract')